Init
This commit is contained in:
commit
080a9785ed
484
.gitignore
vendored
Normal file
484
.gitignore
vendored
Normal file
@ -0,0 +1,484 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from `dotnet new gitignore`
|
||||
|
||||
# dotenv files
|
||||
.env
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
.idea/
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# Vim temporary swap files
|
||||
*.swp
|
||||
31
PSTW.sln
Normal file
31
PSTW.sln
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36930.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Udara", "Udara\Udara.csproj", "{9E992754-F4BE-49BB-A4F6-747CDBDB11B1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UdaraWindows", "UdaraWindows\UdaraWindows.csproj", "{D4047220-220B-45B3-A137-C2611774473A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9E992754-F4BE-49BB-A4F6-747CDBDB11B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9E992754-F4BE-49BB-A4F6-747CDBDB11B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9E992754-F4BE-49BB-A4F6-747CDBDB11B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9E992754-F4BE-49BB-A4F6-747CDBDB11B1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D4047220-220B-45B3-A137-C2611774473A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D4047220-220B-45B3-A137-C2611774473A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4047220-220B-45B3-A137-C2611774473A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D4047220-220B-45B3-A137-C2611774473A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6F63CE08-E9F9-4DB5-836F-B97740D42AA9}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
7
Udara/Common/Feature/Csv/CsvParseResult.cs
Normal file
7
Udara/Common/Feature/Csv/CsvParseResult.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Udara.Common.Feature.Csv;
|
||||
|
||||
public sealed class CsvParseResult
|
||||
{
|
||||
public string[] Header { get; init; } = Array.Empty<string>();
|
||||
public IReadOnlyList<string[]> Rows { get; init; } = Array.Empty<string[]>();
|
||||
}
|
||||
43
Udara/Common/Feature/Csv/CsvParser.cs
Normal file
43
Udara/Common/Feature/Csv/CsvParser.cs
Normal file
@ -0,0 +1,43 @@
|
||||
namespace Udara.Common.Feature.Csv;
|
||||
|
||||
public sealed class CsvParser : ICsvParser
|
||||
{
|
||||
public IReadOnlyList<string[]> ParseRaw(string csvContent)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(csvContent))
|
||||
return Array.Empty<string[]>();
|
||||
|
||||
var lines = csvContent
|
||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var rows = new List<string[]>(lines.Length);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var columns = line
|
||||
.TrimEnd('\r')
|
||||
.Split(',', StringSplitOptions.None);
|
||||
|
||||
rows.Add(columns);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
public CsvParseResult Parse(string csvContent)
|
||||
{
|
||||
var raw = ParseRaw(csvContent);
|
||||
|
||||
if (raw.Count == 0)
|
||||
return new CsvParseResult();
|
||||
|
||||
var header = raw[0];
|
||||
var rows = raw.Skip(1).ToList();
|
||||
|
||||
return new CsvParseResult
|
||||
{
|
||||
Header = header,
|
||||
Rows = rows
|
||||
};
|
||||
}
|
||||
}
|
||||
7
Udara/Common/Feature/Csv/ICsvParser.cs
Normal file
7
Udara/Common/Feature/Csv/ICsvParser.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Udara.Common.Feature.Csv;
|
||||
|
||||
public interface ICsvParser
|
||||
{
|
||||
IReadOnlyList<string[]> ParseRaw(string csvContent);
|
||||
CsvParseResult Parse(string csvContent);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace Udara.Common.Feature.Telegram;
|
||||
|
||||
public interface ITelegramSettingsProvider
|
||||
{
|
||||
Task<TelegramApiSettingsModel> GetSettingsAsync(CancellationToken token = default);
|
||||
}
|
||||
11
Udara/Common/Feature/Telegram/TelegramApiSettingsModel.cs
Normal file
11
Udara/Common/Feature/Telegram/TelegramApiSettingsModel.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Udara.Common.Feature.Telegram;
|
||||
|
||||
public sealed class TelegramApiSettingsModel
|
||||
{
|
||||
public string Protocol { get; set; } = null!;
|
||||
public string Host { get; set; } = null!;
|
||||
public string Mode { get; set; } = null!;
|
||||
public string Token { get; set; } = null!;
|
||||
public string Method { get; set; } = null!;
|
||||
public string TargetChatId { get; set; } = null!;
|
||||
}
|
||||
38
Udara/Common/Feature/Telegram/TelegramSettingsProvider.cs
Normal file
38
Udara/Common/Feature/Telegram/TelegramSettingsProvider.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Udara.Database.App;
|
||||
|
||||
namespace Udara.Common.Feature.Telegram;
|
||||
|
||||
public sealed class TelegramSettingsProvider : ITelegramSettingsProvider
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
|
||||
public TelegramSettingsProvider(IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
public async Task<TelegramApiSettingsModel> GetSettingsAsync(CancellationToken token = default)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
var telegramConfigDict = (await db.AppConfigs
|
||||
.Where(x => x.Category == "Telegram")
|
||||
.ToListAsync(token))
|
||||
.ToDictionary(x => x.ConfigKey, x => x.ConfigValue);
|
||||
|
||||
var model = new TelegramApiSettingsModel
|
||||
{
|
||||
Protocol = telegramConfigDict["TelegramApiProtocol"],
|
||||
Host = telegramConfigDict["TelegramApiHost"],
|
||||
Mode = telegramConfigDict["TelegramApiBotIdentifier"],
|
||||
Method = telegramConfigDict["TelegramApiSendMessageMethod"],
|
||||
Token = telegramConfigDict["TelegramApiBotToken"],
|
||||
TargetChatId = telegramConfigDict["TelegramApiTargetChatId"]
|
||||
};
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
6
Udara/Common/Feature/Time/IMinuteAlignment.cs
Normal file
6
Udara/Common/Feature/Time/IMinuteAlignment.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Udara.Common.Feature.Time;
|
||||
|
||||
public interface IMinuteAlignment
|
||||
{
|
||||
Task SecondsUntilNextMinuteAsync(int inputSeconds, CancellationToken token);
|
||||
}
|
||||
14
Udara/Common/Feature/Time/MinuteAlignment.cs
Normal file
14
Udara/Common/Feature/Time/MinuteAlignment.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Udara.Common.Feature.Time;
|
||||
|
||||
public class MinuteAlignment : IMinuteAlignment
|
||||
{
|
||||
public async Task SecondsUntilNextMinuteAsync(int inputSeconds, CancellationToken token)
|
||||
{
|
||||
var nowTime = DateTime.Now;
|
||||
DateTime baseTime = new(nowTime.Year, nowTime.Month, nowTime.Day, nowTime.Hour, nowTime.Minute, 0, 0);
|
||||
DateTime nextTime = baseTime.AddSeconds(
|
||||
Math.Ceiling((nowTime - baseTime).TotalSeconds / inputSeconds) * inputSeconds);
|
||||
TimeSpan delayTime = nextTime - nowTime;
|
||||
await Task.Delay(delayTime, token);
|
||||
}
|
||||
}
|
||||
41
Udara/Database/App/AppDbContext.cs
Normal file
41
Udara/Database/App/AppDbContext.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Udara.Database.App.Model;
|
||||
|
||||
namespace Udara.Database.App;
|
||||
|
||||
public class AppDbContext : DbContext
|
||||
{
|
||||
public DbSet<AppConfigsModel> AppConfigs => Set<AppConfigsModel>();
|
||||
public DbSet<DeviceListModel> DeviceList => Set<DeviceListModel>();
|
||||
|
||||
public DbSet<DeviceParametersModel> DeviceParameters => Set<DeviceParametersModel>();
|
||||
public DbSet<DeviceFlagsModel> DeviceFlags => Set<DeviceFlagsModel>();
|
||||
|
||||
public DbSet<DeviceModbusModel> DeviceModbus => Set<DeviceModbusModel>();
|
||||
public DbSet<DeviceSerialModel> DeviceSerial => Set<DeviceSerialModel>();
|
||||
public DbSet<DeviceSqlServerModel> DeviceSqlServer => Set<DeviceSqlServerModel>();
|
||||
public DbSet<DeviceDatafileModel> DeviceDatafile => Set<DeviceDatafileModel>();
|
||||
|
||||
public DbSet<FtpListModel> FtpList => Set<FtpListModel>();
|
||||
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder model)
|
||||
{
|
||||
model.Entity<AppConfigsModel>(entity =>
|
||||
{
|
||||
entity.ToTable("AppConfigs");
|
||||
|
||||
entity.HasKey(x => x.ConfigKey);
|
||||
|
||||
entity.Property(x => x.ConfigKey).IsRequired();
|
||||
entity.Property(x => x.ConfigValue).IsRequired();
|
||||
entity.Property(x => x.ValueType).IsRequired();
|
||||
|
||||
entity.Property(x => x.UpdatedAt).ValueGeneratedOnAddOrUpdate();
|
||||
});
|
||||
}
|
||||
}
|
||||
10
Udara/Database/App/Model/AppConfigsModel.cs
Normal file
10
Udara/Database/App/Model/AppConfigsModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public sealed class AppConfigsModel
|
||||
{
|
||||
public string ConfigKey { get; set; } = default!;
|
||||
public string ConfigValue { get; set; } = default!;
|
||||
public string ValueType { get; set; } = default!;
|
||||
public string Category { get; set; } = default!;
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
9
Udara/Database/App/Model/DeviceCoils.cs
Normal file
9
Udara/Database/App/Model/DeviceCoils.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceCoils
|
||||
{
|
||||
//public int Id { get; set; }
|
||||
//public int DeviceId { get; set; }
|
||||
//public int Coil { get; set; }
|
||||
//public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
12
Udara/Database/App/Model/DeviceDatafileModel.cs
Normal file
12
Udara/Database/App/Model/DeviceDatafileModel.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceDatafileModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int DeviceId { get; set; }
|
||||
|
||||
public string ProcessName { get; set; } = string.Empty;
|
||||
public string TargetDir { get; set; } = string.Empty;
|
||||
public string FilePattern { get; set; } = string.Empty;
|
||||
public string FileExtension { get; set; } = string.Empty;
|
||||
}
|
||||
9
Udara/Database/App/Model/DeviceFlagsModel.cs
Normal file
9
Udara/Database/App/Model/DeviceFlagsModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceFlagsModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int DeviceId { get; set; }
|
||||
public int FlagId { get; set; }
|
||||
public string FlagName { get; set; } = string.Empty;
|
||||
}
|
||||
10
Udara/Database/App/Model/DeviceListModel.cs
Normal file
10
Udara/Database/App/Model/DeviceListModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceListModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Mode { get; set; } = string.Empty;
|
||||
public int IsEnabled { get; set; }
|
||||
}
|
||||
25
Udara/Database/App/Model/DeviceModbusModel.cs
Normal file
25
Udara/Database/App/Model/DeviceModbusModel.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceModbusModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int DeviceId { get; set; }
|
||||
public string Mode { get; set; } = string.Empty;
|
||||
|
||||
// TCP
|
||||
public string? Host { get; set; }
|
||||
public int? TcpPort { get; set; }
|
||||
|
||||
// RTU
|
||||
public string? PortName { get; set; }
|
||||
public int? BaudRate { get; set; }
|
||||
public int? DataBits { get; set; }
|
||||
public int? StopBits { get; set; }
|
||||
public string? Parity { get; set; }
|
||||
|
||||
// Common
|
||||
public int? SlaveAddress { get; set; }
|
||||
public int? Reference { get; set; }
|
||||
public int? Count { get; set; }
|
||||
public string? DataType { get; set; }
|
||||
}
|
||||
9
Udara/Database/App/Model/DeviceParametersModel.cs
Normal file
9
Udara/Database/App/Model/DeviceParametersModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceParametersModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int DeviceId { get; set; }
|
||||
public int ParameterId { get; set; }
|
||||
public string ParameterName { get; set; } = string.Empty;
|
||||
}
|
||||
9
Udara/Database/App/Model/DeviceRegisters.cs
Normal file
9
Udara/Database/App/Model/DeviceRegisters.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceRegisters
|
||||
{
|
||||
//public int Id { get; set; }
|
||||
//public int DeviceId { get; set; }
|
||||
//public int Register { get; set; }
|
||||
//public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
17
Udara/Database/App/Model/DeviceSerialModel.cs
Normal file
17
Udara/Database/App/Model/DeviceSerialModel.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceSerialModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int DeviceId { get; set; }
|
||||
public string PortName { get; set; } = string.Empty;
|
||||
public int BaudRate { get; set; }
|
||||
public int DataBits { get; set; }
|
||||
public string Parity { get; set; } = string.Empty;
|
||||
public int StopBits { get; set; }
|
||||
public string FlowControl { get; set; } = string.Empty;
|
||||
public string Encoding { get; set; } = string.Empty;
|
||||
public string NewLine { get; set; } = string.Empty;
|
||||
public int ReadTimeout { get; set; }
|
||||
public int WriteTimeout { get; set; }
|
||||
}
|
||||
14
Udara/Database/App/Model/DeviceSqlServerModel.cs
Normal file
14
Udara/Database/App/Model/DeviceSqlServerModel.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public class DeviceSqlServerModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int DeviceId { get; set; }
|
||||
public string Host { get; set; } = string.Empty;
|
||||
public string DatabaseName { get; set; } = string.Empty;
|
||||
public string? PreferedDatabaseName { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string ConnectionOptions { get; set; } = string.Empty;
|
||||
public string Query { get; set; } = string.Empty;
|
||||
}
|
||||
16
Udara/Database/App/Model/FtpListModel.cs
Normal file
16
Udara/Database/App/Model/FtpListModel.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Udara.Database.App.Model;
|
||||
|
||||
public sealed class FtpListModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Host { get; set; } = string.Empty;
|
||||
public int Port { get; set; }
|
||||
public string Protocol { get; set; } = string.Empty;
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public bool IsPassive { get; set; }
|
||||
public string? RemoteDirectory { get; set; }
|
||||
public string? ExtraJson { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
}
|
||||
150
Udara/Database/App/_Schema.txt
Normal file
150
Udara/Database/App/_Schema.txt
Normal file
@ -0,0 +1,150 @@
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE AppConfigs (
|
||||
ConfigKey TEXT PRIMARY KEY,
|
||||
ConfigValue TEXT NOT NULL,
|
||||
ValueType TEXT NOT NULL, -- string, int, bool, json, etc.
|
||||
UpdatedAt TEXT NOT NULL DEFAULT (strftime('%Y-%m-%d %H:%M:%S', 'now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE TRIGGER AppConfigs_UpdateTimestamp
|
||||
AFTER UPDATE ON AppConfigs
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE AppConfigs
|
||||
SET UpdatedAt = strftime('%Y-%m-%d %H:%M:%S', 'now', 'localtime')
|
||||
WHERE ConfigKey = NEW.ConfigKey;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER AppConfigs_InsertTimestamp
|
||||
AFTER INSERT ON AppConfigs
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE AppConfigs
|
||||
SET UpdatedAt = strftime('%Y-%m-%d %H:%M:%S', 'now', 'localtime')
|
||||
WHERE ConfigKey = NEW.ConfigKey;
|
||||
END;
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE "DeviceList" (
|
||||
"Id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"Code" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"Mode" TEXT NOT NULL,
|
||||
"IsEnabled" INTEGER
|
||||
)
|
||||
|
||||
CREATE TABLE FtpList (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Name TEXT NOT NULL,
|
||||
Host TEXT NOT NULL,
|
||||
Port INTEGER NOT NULL,
|
||||
Protocol TEXT NOT NULL,
|
||||
Username TEXT NOT NULL,
|
||||
Password TEXT NOT NULL,
|
||||
IsPassive INTEGER NOT NULL,
|
||||
RemoteDirectory TEXT NULL,
|
||||
ExtraJson TEXT NULL,
|
||||
IsEnabled INTEGER NOT NULL
|
||||
)
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE DeviceFlags (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
DeviceId INTEGER NOT NULL,
|
||||
FlagId INTEGER NOT NULL,
|
||||
FlagName TEXT NOT NULL,
|
||||
FOREIGN KEY(DeviceId) REFERENCES "DeviceList"(Id)
|
||||
)
|
||||
|
||||
CREATE TABLE "DeviceParameters" (
|
||||
"Id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"DeviceId" INTEGER NOT NULL,
|
||||
"ParameterId" INTEGER NOT NULL,
|
||||
"ParameterName" TEXT NOT NULL,
|
||||
FOREIGN KEY("DeviceId") REFERENCES "DeviceList"("Id")
|
||||
)
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE "DeviceModbus" (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
DeviceId INTEGER NOT NULL,
|
||||
|
||||
Mode TEXT NOT NULL,
|
||||
|
||||
-- TCP
|
||||
Host TEXT,
|
||||
TcpPort INTEGER,
|
||||
|
||||
-- RTU
|
||||
PortName TEXT,
|
||||
BaudRate INTEGER,
|
||||
DataBits INTEGER,
|
||||
StopBits INTEGER,
|
||||
Parity TEXT,
|
||||
|
||||
-- Common
|
||||
SlaveAddress INTEGER,
|
||||
Reference INTEGER,
|
||||
Count INTEGER,
|
||||
DataType TEXT
|
||||
)
|
||||
|
||||
CREATE TABLE "DeviceSerial" (
|
||||
"Id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"DeviceId" INTEGER NOT NULL,
|
||||
"PortName" TEXT NOT NULL,
|
||||
"BaudRate" INTEGER NOT NULL,
|
||||
"DataBits" INTEGER NOT NULL,
|
||||
"Parity" TEXT NOT NULL,
|
||||
"StopBits" INTEGER NOT NULL,
|
||||
"FlowControl" TEXT NOT NULL,
|
||||
"Encoding" TEXT NOT NULL,
|
||||
"NewLine" TEXT NOT NULL,
|
||||
"ReadTimeout" INTEGER NOT NULL,
|
||||
"WriteTimeout" INTEGER NOT NULL
|
||||
)
|
||||
|
||||
CREATE TABLE DeviceSqlServer (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
DeviceId INTEGER NOT NULL,
|
||||
Host TEXT NOT NULL,
|
||||
DatabaseName TEXT NOT NULL,
|
||||
PreferedDatabaseName TEXT,
|
||||
Username TEXT NOT NULL,
|
||||
Password TEXT NOT NULL,
|
||||
ConnectionOptions TEXT NOT NULL,
|
||||
Query TEXT NOT NULL
|
||||
)
|
||||
|
||||
CREATE TABLE "DeviceDatafile" (
|
||||
"Id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"DeviceId" INTEGER NOT NULL,
|
||||
"ProcessName" TEXT NOT NULL,
|
||||
"FilePattern" TEXT NOT NULL,
|
||||
"FileExtension" TEXT NOT NULL,
|
||||
FOREIGN KEY("DeviceId") REFERENCES "DeviceList"("Id")
|
||||
)
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
|
||||
CREATE TABLE DeviceCoils (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
DeviceId INTEGER NOT NULL,
|
||||
Coil INTEGER NOT NULL,
|
||||
Name TEXT NOT NULL,
|
||||
FOREIGN KEY(DeviceId) REFERENCES "DeviceList"(Id)
|
||||
)
|
||||
|
||||
CREATE TABLE DeviceRegisters (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
DeviceId INTEGER NOT NULL,
|
||||
Register INTEGER NOT NULL,
|
||||
Name TEXT NOT NULL,
|
||||
FOREIGN KEY(DeviceId) REFERENCES "DeviceList"(Id)
|
||||
)
|
||||
32
Udara/Database/Data/DataDbContext.cs
Normal file
32
Udara/Database/Data/DataDbContext.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Udara.Database.Data.Model;
|
||||
|
||||
namespace Udara.Database.Data;
|
||||
|
||||
public class DataDbContext : DbContext
|
||||
{
|
||||
public DataDbContext(DbContextOptions<DataDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<RawDataModel> RawData => Set<RawDataModel>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<RawDataModel>(entity =>
|
||||
{
|
||||
entity.ToTable("RawData");
|
||||
|
||||
entity.HasKey(x => x.Id);
|
||||
|
||||
entity.Property(x => x.Timestamp)
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(x => x.Log)
|
||||
.IsRequired();
|
||||
});
|
||||
}
|
||||
}
|
||||
8
Udara/Database/Data/Model/RawDataModel.cs
Normal file
8
Udara/Database/Data/Model/RawDataModel.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Udara.Database.Data.Model;
|
||||
|
||||
public class RawDataModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string Log { get; set; } = string.Empty;
|
||||
}
|
||||
5
Udara/Database/Data/_Schema.txt
Normal file
5
Udara/Database/Data/_Schema.txt
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE "RawData" (
|
||||
"Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"Timestamp" TEXT NOT NULL,
|
||||
"Log" TEXT NOT NULL
|
||||
);
|
||||
BIN
Udara/Database/app.db
Normal file
BIN
Udara/Database/app.db
Normal file
Binary file not shown.
@ -0,0 +1,357 @@
|
||||
Record Date
|
||||
Station
|
||||
teom_teom_a_1-hr_mass_conc
|
||||
teom_teom_b_1-hr_mass_conc
|
||||
nox_no
|
||||
nox_no2
|
||||
nox_nox
|
||||
so2_so2
|
||||
co_co
|
||||
o3_o3
|
||||
ozonePrecursor_n-Hexane
|
||||
ozonePrecursor_Benzene
|
||||
ozonePrecursor_2_2_4-Trimethylpentane
|
||||
ozonePrecursor_n-Heptane
|
||||
ozonePrecursor_Toluene
|
||||
ozonePrecursor_n-Octane
|
||||
ozonePrecursor_Ethylbenzene
|
||||
ozonePrecursor_m_p-Xylene
|
||||
ozonePrecursor_Styrene
|
||||
ozonePrecursor_o-Xylene
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene
|
||||
ozonePrecursor_Ethane
|
||||
ozonePrecursor_Ethene
|
||||
ozonePrecursor_Propane
|
||||
ozonePrecursor_Propene
|
||||
ozonePrecursor_i-Butane
|
||||
ozonePrecursor_n-Butane
|
||||
ozonePrecursor_Acetylene
|
||||
ozonePrecursor_trans-2-Butene
|
||||
ozonePrecursor_1-Butene
|
||||
ozonePrecursor_cis-2-Butene
|
||||
ozonePrecursor_i-Pentane
|
||||
ozonePrecursor_n-Pentane
|
||||
ozonePrecursor_1_3-Butadiene
|
||||
ozonePrecursor_trans-2-Pentene
|
||||
ozonePrecursor_1-Pentene
|
||||
ozonePrecursor_cis-2-Pentene
|
||||
ozonePrecursor_2-Methylpentane
|
||||
ozonePrecursor_n-Hexane(VOC)
|
||||
ozonePrecursor_Isoprene
|
||||
aio_wind_speed
|
||||
aio_wind_direction
|
||||
aio_ambient_temperature
|
||||
aio_relative_humidity
|
||||
aio_solar_radiation
|
||||
nox_no_daily_Qccheck_zero_actual
|
||||
nox_no_daily_Qccheck_zero_target
|
||||
nox_no_daily_Qccheck_zero_drift
|
||||
nox_no_daily_Qccheck_span_actual
|
||||
nox_no_daily_Qccheck_span_target
|
||||
nox_no_daily_Qccheck_span_drift
|
||||
nox_no2_daily_Qccheck_zero_target
|
||||
nox_no2_daily_Qccheck_zero_actual
|
||||
nox_no2_daily_Qccheck_zero_drift
|
||||
nox_no2_daily_Qccheck_span_actual
|
||||
nox_no2_daily_Qccheck_span_target
|
||||
nox_no2_daily_Qccheck_span_drift
|
||||
nox_nox_daily_Qccheck_zero_actual
|
||||
nox_nox_daily_Qccheck_zero_target
|
||||
nox_nox_daily_Qccheck_zero_drift
|
||||
nox_nox_daily_Qccheck_span_actual
|
||||
nox_nox_daily_Qccheck_span_target
|
||||
nox_nox_daily_Qccheck_span_drift
|
||||
so2_so2_daily_Qccheck_zero_actual
|
||||
so2_so2_daily_Qccheck_zero_target
|
||||
so2_so2_daily_Qccheck_zero_drift
|
||||
so2_so2_daily_Qccheck_span_actual
|
||||
so2_so2_daily_Qccheck_span_target
|
||||
so2_so2_daily_Qccheck_span_drift
|
||||
co_co_daily_Qccheck_zero_actual
|
||||
co_co_daily_Qccheck_zero_target
|
||||
co_co_daily_Qccheck_zero_drift
|
||||
co_co_daily_Qccheck_span_actual
|
||||
co_co_daily_Qccheck_span_target
|
||||
co_co_daily_Qccheck_span_drift
|
||||
o3_o3_daily_Qccheck_zero_actual
|
||||
o3_o3_daily_Qccheck_zero_target
|
||||
o3_o3_daily_Qccheck_zero_drift
|
||||
o3_o3_daily_Qccheck_span_actual
|
||||
o3_o3_daily_Qccheck_span_target
|
||||
o3_o3_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Hexane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Benzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Heptane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Toluene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Octane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_m_p-Xylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Styrene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_o-Xylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Propane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Propene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_i-Butane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Butane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Acetylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_trans-2-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_cis-2-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_i-Pentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Pentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_3-Butadiene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_trans-2-Pentene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_cis-2-Pentene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_2-Methylpentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Hexane(VOC)_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Isoprene_daily_Qccheck_span_drift
|
||||
nox_no_single_cal_zero_actual
|
||||
nox_no_single_cal_zero_target
|
||||
nox_no_single_cal_zero_drift
|
||||
nox_no_single_cal_span_actual
|
||||
nox_no_single_cal_span_target
|
||||
nox_no_single_cal_span_drift
|
||||
nox_no2_single_cal_zero_actual
|
||||
nox_no2_single_cal_zero_target
|
||||
nox_no2_single_cal_zero_drift
|
||||
nox_no2_single_cal_span_actual
|
||||
nox_no2_single_cal_span_target
|
||||
nox_no2_single_cal_span_drift
|
||||
nox_nox_single_cal_zero_actual
|
||||
nox_nox_single_cal_zero_target
|
||||
nox_nox_single_cal_zero_drift
|
||||
nox_nox_single_cal_span_actual
|
||||
nox_nox_single_cal_span_target
|
||||
nox_nox_single_cal_span_drift
|
||||
so2_so2_single_cal_zero_actual
|
||||
so2_so2_single_cal_zero_target
|
||||
so2_so2_single_cal_zero_drift
|
||||
so2_so2_single_cal_span_actual
|
||||
so2_so2_single_cal_span_target
|
||||
so2_so2_single_cal_span_drift
|
||||
co_co_single_cal_zero_actual
|
||||
co_co_single_cal_zero_target
|
||||
co_co_single_cal_zero_drift
|
||||
co_co_single_cal_span_actual
|
||||
co_co_single_cal_span_target
|
||||
co_co_single_cal_span_drift
|
||||
o3_o3_single_cal_zero_actual
|
||||
o3_o3_single_cal_zero_target
|
||||
o3_o3_single_cal_zero_drift
|
||||
o3_o3_single_cal_span_actual
|
||||
o3_o3_single_cal_span_target
|
||||
o3_o3_single_cal_span_drift
|
||||
nox_no_multi_cal_zero_actual
|
||||
nox_no_multi_cal_zero_target
|
||||
nox_no_multi_cal_zero_drift
|
||||
nox_no_multi_cal_span1_actual
|
||||
nox_no_multi_cal_span1_target
|
||||
nox_no_multi_cal_span1_drift
|
||||
nox_no_multi_cal_span2_actual
|
||||
nox_no_multi_cal_span2_target
|
||||
nox_no_multi_cal_span2_drift
|
||||
nox_no_multi_cal_span3_actual
|
||||
nox_no_multi_cal_span3_target
|
||||
nox_no_multi_cal_span3_drift
|
||||
nox_no_multi_cal_span4_actual
|
||||
nox_no_multi_cal_span4_target
|
||||
nox_no_multi_cal_span4_drift
|
||||
nox_no2_multi_cal_zero_actual
|
||||
nox_no2_multi_cal_zero_target
|
||||
nox_no2_multi_cal_zero_drift
|
||||
nox_no2_multi_cal_span1_actual
|
||||
nox_no2_multi_cal_span1_target
|
||||
nox_no2_multi_cal_span1_drift
|
||||
nox_no2_multi_cal_span2_actual
|
||||
nox_no2_multi_cal_span2_target
|
||||
nox_no2_multi_cal_span2_drift
|
||||
nox_no2_multi_cal_span3_actual
|
||||
nox_no2_multi_cal_span3_target
|
||||
nox_no2_multi_cal_span3_drift
|
||||
nox_no2_multi_cal_span4_actual
|
||||
nox_no2_multi_cal_span4_target
|
||||
nox_no2_multi_cal_span4_drift
|
||||
nox_nox_multi_cal_zero_actual
|
||||
nox_nox_multi_cal_zero_target
|
||||
nox_nox_multi_cal_zero_drift
|
||||
nox_nox_multi_cal_span1_actual
|
||||
nox_nox_multi_cal_span1_target
|
||||
nox_nox_multi_cal_span1_drift
|
||||
nox_nox_multi_cal_span2_actual
|
||||
nox_nox_multi_cal_span2_target
|
||||
nox_nox_multi_cal_span2_drift
|
||||
nox_nox_multi_cal_span3_actual
|
||||
nox_nox_multi_cal_span3_target
|
||||
nox_nox_multi_cal_span3_drift
|
||||
nox_nox_multi_cal_span4_actual
|
||||
nox_nox_multi_cal_span4_target
|
||||
nox_nox_multi_cal_span4_drift
|
||||
so2_so2_multi_cal_zero_actual
|
||||
so2_so2_multi_cal_zero_target
|
||||
so2_so2_multi_cal_zero_drift
|
||||
so2_so2_multi_cal_span1_actual
|
||||
so2_so2_multi_cal_span1_target
|
||||
so2_so2_multi_cal_span1_drift
|
||||
so2_so2_multi_cal_span2_actual
|
||||
so2_so2_multi_cal_span2_target
|
||||
so2_so2_multi_cal_span2_drift
|
||||
so2_so2_multi_cal_span3_actual
|
||||
so2_so2_multi_cal_span3_target
|
||||
so2_so2_multi_cal_span3_drift
|
||||
so2_so2_multi_cal_span4_actual
|
||||
so2_so2_multi_cal_span4_target
|
||||
so2_so2_multi_cal_span4_drift
|
||||
co_co_multi_cal_zero_actual
|
||||
co_co_multi_cal_zero_target
|
||||
co_co_multi_cal_zero_drift
|
||||
co_co_multi_cal_span1_actual
|
||||
co_co_multi_cal_span1_target
|
||||
co_co_multi_cal_span1_drift
|
||||
co_co_multi_cal_span2_actual
|
||||
co_co_multi_cal_span2_target
|
||||
co_co_multi_cal_span2_drift
|
||||
co_co_multi_cal_span3_actual
|
||||
co_co_multi_cal_span3_target
|
||||
co_co_multi_cal_span3_drift
|
||||
co_co_multi_cal_span4_actual
|
||||
co_co_multi_cal_span4_target
|
||||
co_co_multi_cal_span4_drift
|
||||
o3_o3_multi_cal_zero_actual
|
||||
o3_o3_multi_cal_zero_target
|
||||
o3_o3_multi_cal_zero_drift
|
||||
o3_o3_multi_cal_span1_actual
|
||||
o3_o3_multi_cal_span1_target
|
||||
o3_o3_multi_cal_span1_drift
|
||||
o3_o3_multi_cal_span2_actual
|
||||
o3_o3_multi_cal_span2_target
|
||||
o3_o3_multi_cal_span2_drift
|
||||
o3_o3_multi_cal_span3_actual
|
||||
o3_o3_multi_cal_span3_target
|
||||
o3_o3_multi_cal_span3_drift
|
||||
o3_o3_multi_cal_span4_actual
|
||||
o3_o3_multi_cal_span4_target
|
||||
o3_o3_multi_cal_span4_drift
|
||||
ozonePrecursor_n-Hexane_single_cal_span_actual
|
||||
ozonePrecursor_n-Hexane_single_cal_span_target
|
||||
ozonePrecursor_n-Hexane_single_cal_span_drift
|
||||
ozonePrecursor_Benzene_single_cal_span_actual
|
||||
ozonePrecursor_Benzene_single_cal_span_target
|
||||
ozonePrecursor_Benzene_single_cal_span_drift
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_actual
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_target
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Heptane_single_cal_span_actual
|
||||
ozonePrecursor_n-Heptane_single_cal_span_target
|
||||
ozonePrecursor_n-Heptane_single_cal_span_drift
|
||||
ozonePrecursor_Toluene_single_cal_span_actual
|
||||
ozonePrecursor_Toluene_single_cal_span_target
|
||||
ozonePrecursor_Toluene_single_cal_span_drift
|
||||
ozonePrecursor_n-Octane_single_cal_span_actual
|
||||
ozonePrecursor_n-Octane_single_cal_span_target
|
||||
ozonePrecursor_n-Octane_single_cal_span_drift
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_actual
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_target
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_drift
|
||||
ozonePrecursor_Styrene_single_cal_span_actual
|
||||
ozonePrecursor_Styrene_single_cal_span_target
|
||||
ozonePrecursor_Styrene_single_cal_span_drift
|
||||
ozonePrecursor_o-Xylene_single_cal_span_actual
|
||||
ozonePrecursor_o-Xylene_single_cal_span_target
|
||||
ozonePrecursor_o-Xylene_single_cal_span_drift
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_Ethane_single_cal_span_actual
|
||||
ozonePrecursor_Ethane_single_cal_span_target
|
||||
ozonePrecursor_Ethane_single_cal_span_drift
|
||||
ozonePrecursor_Ethene_single_cal_span_actual
|
||||
ozonePrecursor_Ethene_single_cal_span_target
|
||||
ozonePrecursor_Ethene_single_cal_span_drift
|
||||
ozonePrecursor_Propane_single_cal_span_actual
|
||||
ozonePrecursor_Propane_single_cal_span_target
|
||||
ozonePrecursor_Propane_single_cal_span_drift
|
||||
ozonePrecursor_Propene_single_cal_span_actual
|
||||
ozonePrecursor_Propene_single_cal_span_target
|
||||
ozonePrecursor_Propene_single_cal_span_drift
|
||||
ozonePrecursor_i-Butane_single_cal_span_actual
|
||||
ozonePrecursor_i-Butane_single_cal_span_target
|
||||
ozonePrecursor_i-Butane_single_cal_span_drift
|
||||
ozonePrecursor_n-Butane_single_cal_span_actual
|
||||
ozonePrecursor_n-Butane_single_cal_span_target
|
||||
ozonePrecursor_n-Butane_single_cal_span_drift
|
||||
ozonePrecursor_Acetylene_single_cal_span_actual
|
||||
ozonePrecursor_Acetylene_single_cal_span_target
|
||||
ozonePrecursor_Acetylene_single_cal_span_drift
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_actual
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_target
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_drift
|
||||
ozonePrecursor_1-Butene_single_cal_span_actual
|
||||
ozonePrecursor_1-Butene_single_cal_span_target
|
||||
ozonePrecursor_1-Butene_single_cal_span_drift
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_actual
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_target
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_drift
|
||||
ozonePrecursor_i-Pentane_single_cal_span_actual
|
||||
ozonePrecursor_i-Pentane_single_cal_span_target
|
||||
ozonePrecursor_i-Pentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Pentane_single_cal_span_actual
|
||||
ozonePrecursor_n-Pentane_single_cal_span_target
|
||||
ozonePrecursor_n-Pentane_single_cal_span_drift
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_actual
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_target
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_drift
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_target
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_1-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_1-Pentene_single_cal_span_target
|
||||
ozonePrecursor_1-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_target
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_actual
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_target
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_actual
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_target
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_drift
|
||||
ozonePrecursor_Isoprene_single_cal_span_actual
|
||||
ozonePrecursor_Isoprene_single_cal_span_target
|
||||
ozonePrecursor_Isoprene_single_cal_span_drift
|
||||
so2_maintenance
|
||||
nox_maintenance
|
||||
co_maintenance
|
||||
o3_maintenance
|
||||
teom_maintenance
|
||||
ozonePrecursor_maintenance
|
||||
aio_maintenance
|
||||
so2_repair
|
||||
nox_repair
|
||||
co_repair
|
||||
o3_repair
|
||||
teom_repair
|
||||
aio_repair
|
||||
ozonePrecursor_repair
|
||||
so2_flag
|
||||
nox_flag
|
||||
o3_flag
|
||||
co_flag
|
||||
aio_flag
|
||||
ozone_precursor_flag
|
||||
teom_flag
|
||||
|
2
Udara/Demo/CAQM24/data/CAQM_024_20260307_1130_avg.csv
Normal file
2
Udara/Demo/CAQM24/data/CAQM_024_20260307_1130_avg.csv
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,8 @@
|
||||
aio_wind_speed
|
||||
aio_wind_direction
|
||||
aio_ambient_temperature
|
||||
aio_relative_humidity
|
||||
aio_solar_radiation
|
||||
aio_maintenance
|
||||
aio_repair
|
||||
aio_flag
|
||||
|
@ -0,0 +1,31 @@
|
||||
co_co
|
||||
co_co_daily_Qccheck_zero_actual
|
||||
co_co_daily_Qccheck_zero_target
|
||||
co_co_daily_Qccheck_zero_drift
|
||||
co_co_daily_Qccheck_span_actual
|
||||
co_co_daily_Qccheck_span_target
|
||||
co_co_daily_Qccheck_span_drift
|
||||
co_co_single_cal_zero_actual
|
||||
co_co_single_cal_zero_target
|
||||
co_co_single_cal_zero_drift
|
||||
co_co_single_cal_span_actual
|
||||
co_co_single_cal_span_target
|
||||
co_co_single_cal_span_drift
|
||||
co_co_multi_cal_zero_actual
|
||||
co_co_multi_cal_zero_target
|
||||
co_co_multi_cal_zero_drift
|
||||
co_co_multi_cal_span1_actual
|
||||
co_co_multi_cal_span1_target
|
||||
co_co_multi_cal_span1_drift
|
||||
co_co_multi_cal_span2_actual
|
||||
co_co_multi_cal_span2_target
|
||||
co_co_multi_cal_span2_drift
|
||||
co_co_multi_cal_span3_actual
|
||||
co_co_multi_cal_span3_target
|
||||
co_co_multi_cal_span3_drift
|
||||
co_co_multi_cal_span4_actual
|
||||
co_co_multi_cal_span4_target
|
||||
co_co_multi_cal_span4_drift
|
||||
co_maintenance
|
||||
co_repair
|
||||
co_flag
|
||||
|
@ -0,0 +1,87 @@
|
||||
nox_no
|
||||
nox_no2
|
||||
nox_nox
|
||||
nox_no_daily_Qccheck_zero_actual
|
||||
nox_no_daily_Qccheck_zero_target
|
||||
nox_no_daily_Qccheck_zero_drift
|
||||
nox_no_daily_Qccheck_span_actual
|
||||
nox_no_daily_Qccheck_span_target
|
||||
nox_no_daily_Qccheck_span_drift
|
||||
nox_no2_daily_Qccheck_zero_target
|
||||
nox_no2_daily_Qccheck_zero_actual
|
||||
nox_no2_daily_Qccheck_zero_drift
|
||||
nox_no2_daily_Qccheck_span_actual
|
||||
nox_no2_daily_Qccheck_span_target
|
||||
nox_no2_daily_Qccheck_span_drift
|
||||
nox_nox_daily_Qccheck_zero_actual
|
||||
nox_nox_daily_Qccheck_zero_target
|
||||
nox_nox_daily_Qccheck_zero_drift
|
||||
nox_nox_daily_Qccheck_span_actual
|
||||
nox_nox_daily_Qccheck_span_target
|
||||
nox_nox_daily_Qccheck_span_drift
|
||||
nox_no_single_cal_zero_actual
|
||||
nox_no_single_cal_zero_target
|
||||
nox_no_single_cal_zero_drift
|
||||
nox_no_single_cal_span_actual
|
||||
nox_no_single_cal_span_target
|
||||
nox_no_single_cal_span_drift
|
||||
nox_no2_single_cal_zero_actual
|
||||
nox_no2_single_cal_zero_target
|
||||
nox_no2_single_cal_zero_drift
|
||||
nox_no2_single_cal_span_actual
|
||||
nox_no2_single_cal_span_target
|
||||
nox_no2_single_cal_span_drift
|
||||
nox_nox_single_cal_zero_actual
|
||||
nox_nox_single_cal_zero_target
|
||||
nox_nox_single_cal_zero_drift
|
||||
nox_nox_single_cal_span_actual
|
||||
nox_nox_single_cal_span_target
|
||||
nox_nox_single_cal_span_drift
|
||||
nox_no_multi_cal_zero_actual
|
||||
nox_no_multi_cal_zero_target
|
||||
nox_no_multi_cal_zero_drift
|
||||
nox_no_multi_cal_span1_actual
|
||||
nox_no_multi_cal_span1_target
|
||||
nox_no_multi_cal_span1_drift
|
||||
nox_no_multi_cal_span2_actual
|
||||
nox_no_multi_cal_span2_target
|
||||
nox_no_multi_cal_span2_drift
|
||||
nox_no_multi_cal_span3_actual
|
||||
nox_no_multi_cal_span3_target
|
||||
nox_no_multi_cal_span3_drift
|
||||
nox_no_multi_cal_span4_actual
|
||||
nox_no_multi_cal_span4_target
|
||||
nox_no_multi_cal_span4_drift
|
||||
nox_no2_multi_cal_zero_actual
|
||||
nox_no2_multi_cal_zero_target
|
||||
nox_no2_multi_cal_zero_drift
|
||||
nox_no2_multi_cal_span1_actual
|
||||
nox_no2_multi_cal_span1_target
|
||||
nox_no2_multi_cal_span1_drift
|
||||
nox_no2_multi_cal_span2_actual
|
||||
nox_no2_multi_cal_span2_target
|
||||
nox_no2_multi_cal_span2_drift
|
||||
nox_no2_multi_cal_span3_actual
|
||||
nox_no2_multi_cal_span3_target
|
||||
nox_no2_multi_cal_span3_drift
|
||||
nox_no2_multi_cal_span4_actual
|
||||
nox_no2_multi_cal_span4_target
|
||||
nox_no2_multi_cal_span4_drift
|
||||
nox_nox_multi_cal_zero_actual
|
||||
nox_nox_multi_cal_zero_target
|
||||
nox_nox_multi_cal_zero_drift
|
||||
nox_nox_multi_cal_span1_actual
|
||||
nox_nox_multi_cal_span1_target
|
||||
nox_nox_multi_cal_span1_drift
|
||||
nox_nox_multi_cal_span2_actual
|
||||
nox_nox_multi_cal_span2_target
|
||||
nox_nox_multi_cal_span2_drift
|
||||
nox_nox_multi_cal_span3_actual
|
||||
nox_nox_multi_cal_span3_target
|
||||
nox_nox_multi_cal_span3_drift
|
||||
nox_nox_multi_cal_span4_actual
|
||||
nox_nox_multi_cal_span4_target
|
||||
nox_nox_multi_cal_span4_drift
|
||||
nox_maintenance
|
||||
nox_repair
|
||||
nox_flag
|
||||
|
@ -0,0 +1,31 @@
|
||||
o3_o3
|
||||
o3_o3_daily_Qccheck_zero_actual
|
||||
o3_o3_daily_Qccheck_zero_target
|
||||
o3_o3_daily_Qccheck_zero_drift
|
||||
o3_o3_daily_Qccheck_span_actual
|
||||
o3_o3_daily_Qccheck_span_target
|
||||
o3_o3_daily_Qccheck_span_drift
|
||||
o3_o3_single_cal_zero_actual
|
||||
o3_o3_single_cal_zero_target
|
||||
o3_o3_single_cal_zero_drift
|
||||
o3_o3_single_cal_span_actual
|
||||
o3_o3_single_cal_span_target
|
||||
o3_o3_single_cal_span_drift
|
||||
o3_o3_multi_cal_zero_actual
|
||||
o3_o3_multi_cal_zero_target
|
||||
o3_o3_multi_cal_zero_drift
|
||||
o3_o3_multi_cal_span1_actual
|
||||
o3_o3_multi_cal_span1_target
|
||||
o3_o3_multi_cal_span1_drift
|
||||
o3_o3_multi_cal_span2_actual
|
||||
o3_o3_multi_cal_span2_target
|
||||
o3_o3_multi_cal_span2_drift
|
||||
o3_o3_multi_cal_span3_actual
|
||||
o3_o3_multi_cal_span3_target
|
||||
o3_o3_multi_cal_span3_drift
|
||||
o3_o3_multi_cal_span4_actual
|
||||
o3_o3_multi_cal_span4_target
|
||||
o3_o3_multi_cal_span4_drift
|
||||
o3_maintenance
|
||||
o3_repair
|
||||
o3_flag
|
||||
|
@ -0,0 +1,162 @@
|
||||
ozonePrecursor_n-Hexane
|
||||
ozonePrecursor_Benzene
|
||||
ozonePrecursor_2_2_4-Trimethylpentane
|
||||
ozonePrecursor_n-Heptane
|
||||
ozonePrecursor_Toluene
|
||||
ozonePrecursor_n-Octane
|
||||
ozonePrecursor_Ethylbenzene
|
||||
ozonePrecursor_m_p-Xylene
|
||||
ozonePrecursor_Styrene
|
||||
ozonePrecursor_o-Xylene
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene
|
||||
ozonePrecursor_Ethane
|
||||
ozonePrecursor_Ethene
|
||||
ozonePrecursor_Propane
|
||||
ozonePrecursor_Propene
|
||||
ozonePrecursor_i-Butane
|
||||
ozonePrecursor_n-Butane
|
||||
ozonePrecursor_Acetylene
|
||||
ozonePrecursor_trans-2-Butene
|
||||
ozonePrecursor_1-Butene
|
||||
ozonePrecursor_cis-2-Butene
|
||||
ozonePrecursor_i-Pentane
|
||||
ozonePrecursor_n-Pentane
|
||||
ozonePrecursor_1_3-Butadiene
|
||||
ozonePrecursor_trans-2-Pentene
|
||||
ozonePrecursor_1-Pentene
|
||||
ozonePrecursor_cis-2-Pentene
|
||||
ozonePrecursor_2-Methylpentane
|
||||
ozonePrecursor_n-Hexane(VOC)
|
||||
ozonePrecursor_Isoprene
|
||||
ozonePrecursor_n-Hexane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Benzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Heptane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Toluene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Octane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_m_p-Xylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Styrene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_o-Xylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Propane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Propene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_i-Butane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Butane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Acetylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_trans-2-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_cis-2-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_i-Pentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Pentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_3-Butadiene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_trans-2-Pentene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_cis-2-Pentene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_2-Methylpentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Hexane(VOC)_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Isoprene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Hexane_single_cal_span_actual
|
||||
ozonePrecursor_n-Hexane_single_cal_span_target
|
||||
ozonePrecursor_n-Hexane_single_cal_span_drift
|
||||
ozonePrecursor_Benzene_single_cal_span_actual
|
||||
ozonePrecursor_Benzene_single_cal_span_target
|
||||
ozonePrecursor_Benzene_single_cal_span_drift
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_actual
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_target
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Heptane_single_cal_span_actual
|
||||
ozonePrecursor_n-Heptane_single_cal_span_target
|
||||
ozonePrecursor_n-Heptane_single_cal_span_drift
|
||||
ozonePrecursor_Toluene_single_cal_span_actual
|
||||
ozonePrecursor_Toluene_single_cal_span_target
|
||||
ozonePrecursor_Toluene_single_cal_span_drift
|
||||
ozonePrecursor_n-Octane_single_cal_span_actual
|
||||
ozonePrecursor_n-Octane_single_cal_span_target
|
||||
ozonePrecursor_n-Octane_single_cal_span_drift
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_actual
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_target
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_drift
|
||||
ozonePrecursor_Styrene_single_cal_span_actual
|
||||
ozonePrecursor_Styrene_single_cal_span_target
|
||||
ozonePrecursor_Styrene_single_cal_span_drift
|
||||
ozonePrecursor_o-Xylene_single_cal_span_actual
|
||||
ozonePrecursor_o-Xylene_single_cal_span_target
|
||||
ozonePrecursor_o-Xylene_single_cal_span_drift
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_Ethane_single_cal_span_actual
|
||||
ozonePrecursor_Ethane_single_cal_span_target
|
||||
ozonePrecursor_Ethane_single_cal_span_drift
|
||||
ozonePrecursor_Ethene_single_cal_span_actual
|
||||
ozonePrecursor_Ethene_single_cal_span_target
|
||||
ozonePrecursor_Ethene_single_cal_span_drift
|
||||
ozonePrecursor_Propane_single_cal_span_actual
|
||||
ozonePrecursor_Propane_single_cal_span_target
|
||||
ozonePrecursor_Propane_single_cal_span_drift
|
||||
ozonePrecursor_Propene_single_cal_span_actual
|
||||
ozonePrecursor_Propene_single_cal_span_target
|
||||
ozonePrecursor_Propene_single_cal_span_drift
|
||||
ozonePrecursor_i-Butane_single_cal_span_actual
|
||||
ozonePrecursor_i-Butane_single_cal_span_target
|
||||
ozonePrecursor_i-Butane_single_cal_span_drift
|
||||
ozonePrecursor_n-Butane_single_cal_span_actual
|
||||
ozonePrecursor_n-Butane_single_cal_span_target
|
||||
ozonePrecursor_n-Butane_single_cal_span_drift
|
||||
ozonePrecursor_Acetylene_single_cal_span_actual
|
||||
ozonePrecursor_Acetylene_single_cal_span_target
|
||||
ozonePrecursor_Acetylene_single_cal_span_drift
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_actual
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_target
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_drift
|
||||
ozonePrecursor_1-Butene_single_cal_span_actual
|
||||
ozonePrecursor_1-Butene_single_cal_span_target
|
||||
ozonePrecursor_1-Butene_single_cal_span_drift
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_actual
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_target
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_drift
|
||||
ozonePrecursor_i-Pentane_single_cal_span_actual
|
||||
ozonePrecursor_i-Pentane_single_cal_span_target
|
||||
ozonePrecursor_i-Pentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Pentane_single_cal_span_actual
|
||||
ozonePrecursor_n-Pentane_single_cal_span_target
|
||||
ozonePrecursor_n-Pentane_single_cal_span_drift
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_actual
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_target
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_drift
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_target
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_1-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_1-Pentene_single_cal_span_target
|
||||
ozonePrecursor_1-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_target
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_actual
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_target
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_actual
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_target
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_drift
|
||||
ozonePrecursor_Isoprene_single_cal_span_actual
|
||||
ozonePrecursor_Isoprene_single_cal_span_target
|
||||
ozonePrecursor_Isoprene_single_cal_span_drift
|
||||
ozonePrecursor_maintenance
|
||||
ozonePrecursor_repair
|
||||
ozone_precursor_flag
|
||||
|
@ -0,0 +1,31 @@
|
||||
so2_so2
|
||||
so2_so2_daily_Qccheck_zero_actual
|
||||
so2_so2_daily_Qccheck_zero_target
|
||||
so2_so2_daily_Qccheck_zero_drift
|
||||
so2_so2_daily_Qccheck_span_actual
|
||||
so2_so2_daily_Qccheck_span_target
|
||||
so2_so2_daily_Qccheck_span_drift
|
||||
so2_so2_single_cal_zero_actual
|
||||
so2_so2_single_cal_zero_target
|
||||
so2_so2_single_cal_zero_drift
|
||||
so2_so2_single_cal_span_actual
|
||||
so2_so2_single_cal_span_target
|
||||
so2_so2_single_cal_span_drift
|
||||
so2_so2_multi_cal_zero_actual
|
||||
so2_so2_multi_cal_zero_target
|
||||
so2_so2_multi_cal_zero_drift
|
||||
so2_so2_multi_cal_span1_actual
|
||||
so2_so2_multi_cal_span1_target
|
||||
so2_so2_multi_cal_span1_drift
|
||||
so2_so2_multi_cal_span2_actual
|
||||
so2_so2_multi_cal_span2_target
|
||||
so2_so2_multi_cal_span2_drift
|
||||
so2_so2_multi_cal_span3_actual
|
||||
so2_so2_multi_cal_span3_target
|
||||
so2_so2_multi_cal_span3_drift
|
||||
so2_so2_multi_cal_span4_actual
|
||||
so2_so2_multi_cal_span4_target
|
||||
so2_so2_multi_cal_span4_drift
|
||||
so2_maintenance
|
||||
so2_repair
|
||||
so2_flag
|
||||
|
@ -0,0 +1,5 @@
|
||||
teom_teom_a_1-hr_mass_conc
|
||||
teom_teom_b_1-hr_mass_conc
|
||||
teom_maintenance
|
||||
teom_repair
|
||||
teom_flag
|
||||
|
21
Udara/Demo/CAQM24/data/CAQM_024_20260307_1130_raw.csv
Normal file
21
Udara/Demo/CAQM24/data/CAQM_024_20260307_1130_raw.csv
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
22
Udara/Demo/CAQM_024_20210303_1200_raw.csv
Normal file
22
Udara/Demo/CAQM_024_20210303_1200_raw.csv
Normal file
File diff suppressed because one or more lines are too long
378
Udara/Demo/Data.json
Normal file
378
Udara/Demo/Data.json
Normal file
@ -0,0 +1,378 @@
|
||||
{
|
||||
"Devices": [
|
||||
{
|
||||
"DeviceId": 1,
|
||||
"Code": "42i",
|
||||
"Name": "nox",
|
||||
"Mode": "modbus",
|
||||
"Parameters": {
|
||||
"NO": "0.910169",
|
||||
"NO2": "3.80069",
|
||||
"NOX": "4.71086",
|
||||
"LO NO": "0",
|
||||
"INTERNAL TEMP": "32.8056",
|
||||
"CHAMBER TEMP": "50.1073",
|
||||
"COOLER TEMP": "-3.04592",
|
||||
"NO2 CONVERTER TEMP": "326.044",
|
||||
"CHAMBER PRESSURE": "358.428",
|
||||
"SAMPLE FLOW": "0.4805",
|
||||
"PMT VOLTS": "-845.821",
|
||||
"OZONATOR FLOW": "0.05"
|
||||
},
|
||||
"Flags": {
|
||||
"ZERO MODE": "0",
|
||||
"SPAN MODE": "0",
|
||||
"NO MODE": "1",
|
||||
"NOX MODE": "1",
|
||||
"INT TEMP ALARM": "0",
|
||||
"CHAMB TEMP ALARM": "0",
|
||||
"COOLER TEMP ALARM": "0",
|
||||
"NO2 CONVERTER TEMP ALARM": "0",
|
||||
"PERM OVEN GAS TEMP ALARM": "0",
|
||||
"PRESSURE ALARM": "1",
|
||||
"FLOW ALARM": "0",
|
||||
"OZONE FLOW ALARM": "0",
|
||||
"MOTHERBOARD STATUS ALARM": "0",
|
||||
"INTERFACE BD STATUS ALARM": "0",
|
||||
"SAMPLE MODE": "0",
|
||||
"PURGE MODE": "0",
|
||||
"ZERO CHECK CAL ALARM": "0",
|
||||
"SPAN CHECK CAL ALARM": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 2,
|
||||
"Code": "43i",
|
||||
"Name": "so2",
|
||||
"Mode": "modbus",
|
||||
"Parameters": {
|
||||
"SO2": "1.97335",
|
||||
"INTERNAL TEMP": "35.3874",
|
||||
"CHAMBER TEMP": "44.9375",
|
||||
"PERM OVEN GAS TEMP": "0",
|
||||
"PERM OVEN HEATER TEMP": "0",
|
||||
"CHAMBER PRESSURE": "727.48",
|
||||
"SAMPLE FLOW": "0.556314",
|
||||
"PMT VOLTS": "-622.711"
|
||||
},
|
||||
"Flags": {
|
||||
"AUTORANGE": "0",
|
||||
"LOCAL/REMOTE": "0",
|
||||
"ZERO MODE": "0",
|
||||
"SPAN MODE": "0",
|
||||
"SAMPLE MODE": "0",
|
||||
"INTERNAL TEMP ALARM": "0",
|
||||
"CHAMB TEMP ALARM": "0",
|
||||
"CONVERTER TEMP": "0",
|
||||
"PERM GAS TEMP": "0",
|
||||
"PRESSURE ALARM": "0",
|
||||
"SAMPLE FLOW ALARM": "0",
|
||||
"MOTHERBOARD STATUS ALARM": "0",
|
||||
"INTERFACE BD STATUS ALARM": "0",
|
||||
"ZERO CHK CAL ALARM": "0",
|
||||
"SPAN CHK CAL ALARM": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 3,
|
||||
"Code": "48i",
|
||||
"Name": "co",
|
||||
"Mode": "modbus",
|
||||
"Parameters": {
|
||||
"CO": "302.669",
|
||||
"INTERNAL TEMP": "35.3874",
|
||||
"BENCH TEMP": "48.0606",
|
||||
"PRESSURE": "740.072",
|
||||
"SAMPLE FLOW": "0.771857",
|
||||
"INTENSITY": "197009",
|
||||
"MOTOR SPEED": "100.021"
|
||||
},
|
||||
"Flags": {
|
||||
"ZERO MODE": "0",
|
||||
"SPAN MODE": "0",
|
||||
"SAMPLE MODE": "0",
|
||||
"INTERNAL TEMP ALARM": "0",
|
||||
"BENCH TEMP ALARM": "0",
|
||||
"PRESSURE ALARM": "0",
|
||||
"SAMPLE FLOW ALARM": "0",
|
||||
"INTENSITY ALARM": "0",
|
||||
"MOTOR SPEED ALARM": "0",
|
||||
"BIAS VOLTAGE ALARM": "0",
|
||||
"MOTHERBOARD STATUS ALARM": "0",
|
||||
"INTERFACE BD STATUS ALARM": "0",
|
||||
"PURGE MODE": "0",
|
||||
"ZERO CHK CAL ALARM": "0",
|
||||
"SPAN CHK CAL ALARM": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 4,
|
||||
"Code": "49i",
|
||||
"Name": "o3",
|
||||
"Mode": "modbus",
|
||||
"Parameters": {
|
||||
"O3": "28.2709",
|
||||
"INTENSITY A": "48550",
|
||||
"INTENSITY B": "45101",
|
||||
"NOISE A": "1.65",
|
||||
"NOISE B": "1.25",
|
||||
"FLOW A": "0.747824",
|
||||
"FLOW B": "0.738708",
|
||||
"PRESSURE": "739.41",
|
||||
"BENCH TEMP": "38.5616",
|
||||
"LAMP TEMP": "53.4734",
|
||||
"O3 LAMP TEMP": "0"
|
||||
},
|
||||
"Flags": {
|
||||
"ZERO MODE": "0",
|
||||
"SPAN MODE": "0",
|
||||
"SAMPLE MODE": "0",
|
||||
"BENCH TEMP ALARM": "0",
|
||||
"BENCH LAMP TEMP ALARM": "0",
|
||||
"PRESSURE ALARM": "0",
|
||||
"FLOW A ALARM": "0",
|
||||
"FLOW B ALARM": "0",
|
||||
"INTENSITY A ALARM": "0",
|
||||
"INTENSITY B ALARM": "0",
|
||||
"ZERO CHK CAL ALARM": "0",
|
||||
"SPAN CHK CAL ALARM": "0",
|
||||
"MOTHERBOARD STATUS ALARM": "0",
|
||||
"INTERFACE BD STATUS ALARM": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 5,
|
||||
"Code": "146i",
|
||||
"Name": "cal",
|
||||
"Mode": "modbus",
|
||||
"Parameters": {
|
||||
"GAS CONC": "0",
|
||||
"TARGET GFLOW": "0",
|
||||
"ACTUAL GFLOW": "0",
|
||||
"TARGET ZFLOW": "0",
|
||||
"ACTUAL ZFLOW": "0",
|
||||
"TARGET TFLOW": "0",
|
||||
"ACTUAL TFLOW": "0",
|
||||
"O3 CONC": "0",
|
||||
"PERM CONC": "0",
|
||||
"PRESSURE REGISTER": "0",
|
||||
"BENCH TEMP": "0",
|
||||
"LAMP TEMP": "0",
|
||||
"O3 LAMP TEMP REGISTER": "67.9489",
|
||||
"INTENSITY": "0",
|
||||
"NOISE": "0",
|
||||
"ANALOG 1": "0",
|
||||
"ANALOG 2": "0",
|
||||
"ANALOG 3": "0",
|
||||
"ANALOG 4": "0",
|
||||
"ANALOG 5": "0",
|
||||
"ANALOG 6": "0",
|
||||
"ANALOG 7": "0",
|
||||
"ANALOG 8": "0"
|
||||
},
|
||||
"Flags": {
|
||||
"GAS BIT 1": "0",
|
||||
"GAS BIT 2": "0",
|
||||
"GAS BIT 3": "0",
|
||||
"SPAN BIT 1": "0",
|
||||
"SPAN BIT 2": "0",
|
||||
"SPAN BIT 3": "0",
|
||||
"O3 PERM BIT 1": "0",
|
||||
"O3 PERM BIT 2": "0",
|
||||
"O3 PERM BIT 3": "0",
|
||||
"O3 PERM BIT 4": "0",
|
||||
"PHOTO BIT 1": "0",
|
||||
"PHOTO BIT 2": "0",
|
||||
"GAS A": "0",
|
||||
"GAS B": "0",
|
||||
"GAS C": "0",
|
||||
"GAS D": "0",
|
||||
"GAS E": "0",
|
||||
"GAS F": "0",
|
||||
"OZONATOR": "0",
|
||||
"PERM OVEN": "0",
|
||||
"USER 1": "0",
|
||||
"USER 2": "0",
|
||||
"USER 3": "0",
|
||||
"USER 4": "0",
|
||||
"USER 5": "0",
|
||||
"USER 6": "0",
|
||||
"USER 7": "0",
|
||||
"USER 8": "0",
|
||||
"GEN ALARM": "0",
|
||||
"AMBIENT TEMP": "0",
|
||||
"O3 LAMP TEMP COIL": "0",
|
||||
"PERM GAS TEMP": "0",
|
||||
"PRESSURE COIL": "0",
|
||||
"PHOT BENCH TEMP": "0",
|
||||
"PHOT LAMP TEMP": "0",
|
||||
"PHOT INTENSITY": "0",
|
||||
"O3 LEVEL": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 6,
|
||||
"Code": "1405df",
|
||||
"Name": "teom",
|
||||
"Mode": "ak",
|
||||
"Values": {},
|
||||
"Parameters": {
|
||||
"System Status": "842137664",
|
||||
"Case Temperature": "34.243000",
|
||||
"Cap Temperature": "33.812000",
|
||||
"Ambient Temperature": "34.027000",
|
||||
"Ambient Relative Humidity": "66.522003",
|
||||
"TEOM A Flow Rate": "3.003560",
|
||||
"TEOM A Flow Volumetric": "3.003560",
|
||||
"TEOM A Filter Load": "41.659344",
|
||||
"TEOM A Mass Conc": "-2.006379",
|
||||
"TEOM A 1-Hr Mass Conc": "19.173367",
|
||||
"TEOM A Frequency": "260.988328",
|
||||
"TEOM A Noise": "0.004862",
|
||||
"TEOM A Dryer Dew Point": "18.719999",
|
||||
"TEOM A Cooler Temperature": "146.112000",
|
||||
"TEOM A Base Mass Conc": "-15.170035",
|
||||
"TEOM A Ref Mass Conc": "-13.163656",
|
||||
"TEOM B Flow Rate": "1.669940",
|
||||
"TEOM B Flow Volumetric": "1.669940",
|
||||
"TEOM B Filter Load": "15.520150",
|
||||
"TEOM B Mass Conc": "-10.965565",
|
||||
"TEOM B 1-Hr Mass Conc": "10.450756",
|
||||
"TEOM B Frequency": "268.335635",
|
||||
"TEOM B Noise": "0.007932",
|
||||
"TEOM B Dryer Dew Point": "15.894000",
|
||||
"TEOM B Cooler Temp": "32.549000",
|
||||
"TEOM B Base Mass Conc": "-8.474164",
|
||||
"TEOM B Ref Mass Conc": "2.692038"
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 7,
|
||||
"Code": "aio",
|
||||
"Name": "aio2",
|
||||
"Mode": "serial",
|
||||
"Values": {},
|
||||
"Parameters": {
|
||||
"Wind Speed": "000.9",
|
||||
"Wind Direction": "162",
|
||||
"Ambient Temperature": "+033.3",
|
||||
"Relative Humidity": "065",
|
||||
"Barometric Pressure": "1008.6",
|
||||
"Rain": "000.00",
|
||||
"Solar Radiation": "0226",
|
||||
"Battery Voltage": "11.6",
|
||||
"Compass Reading": "203",
|
||||
"Config": "M0",
|
||||
"Checksum": "02546"
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 8,
|
||||
"Code": "ama",
|
||||
"Name": "ozoneprecursor",
|
||||
"Mode": "sqlserver",
|
||||
"Parameters": {
|
||||
"n-Hexane": "0.484464884950713",
|
||||
"Benzene": "0.343748092714198",
|
||||
"2,2,4-Trimethylpentane": "0.0676527332678424",
|
||||
"n-Heptane": "0",
|
||||
"Toluene": "0",
|
||||
"n-Octane": "0.0641648763469244",
|
||||
"Ethylbenzene": "0.210336043171019",
|
||||
"m,p-Xylene": "0.374504884378195",
|
||||
"Styrene": "",
|
||||
"o-Xylene": "0.188337438788156",
|
||||
"1,3,5-Trimethylbenzene": "0.127804072300202",
|
||||
"1,2,4-Trimethylbenzene": "0.124646281832719",
|
||||
"1,2,3-Trimethylbenzene": "0.0438370534637906",
|
||||
"Ethane": "0",
|
||||
"Ethene": "2.29089802244092",
|
||||
"Propane": "0.932668481496108",
|
||||
"Propene": "0.288107828566244",
|
||||
"i-Butane": "0.627011482127217",
|
||||
"n-Butane": "0.820025435000356",
|
||||
"Acetylene": "0",
|
||||
"trans-2-Butene": "0",
|
||||
"1-Butene": "0",
|
||||
"cis-2-Butene": "0.775687388214023",
|
||||
"i-Pentane": "0",
|
||||
"n-Pentane": "0.485188424420034",
|
||||
"1,3-Butadiene": "0.501550433804146",
|
||||
"trans-2-Pentene": "0",
|
||||
"1-Pentene": "0",
|
||||
"cis-2-Pentene": "",
|
||||
"2-Methylpentane": "0.375556231222657",
|
||||
"n-Hexane(VOC)": "2.34790313544383",
|
||||
"Isoprene": "1.82749462737622"
|
||||
},
|
||||
"Flags": {}
|
||||
},
|
||||
{
|
||||
"DeviceId": 9,
|
||||
"Code": "trace1600",
|
||||
"Name": "ozoneprecursor",
|
||||
"Mode": "csv",
|
||||
"Parameters": {
|
||||
"RT_n-Hexane": "6.53",
|
||||
"Conc_n-Hexane": "0.2885",
|
||||
"RT_Benzene": "8.01",
|
||||
"Conc_Benzene": "1.9816",
|
||||
"RT_2.2.4-Trimethylpentene": "9.09",
|
||||
"Conc_2.2.4-Trimethylpentene": "0.1475",
|
||||
"RT_n-Heptane": "9.09",
|
||||
"Conc_n-Heptane": "0.4461",
|
||||
"RT_Toluene": "11.26",
|
||||
"Conc_Toluene": "7.3416",
|
||||
"RT_n-Octane": "12.56",
|
||||
"Conc_n-Octane": "0.1128",
|
||||
"RT_Ethylbenzene": "14.28",
|
||||
"Conc_Ethylbenzene": "1.1071",
|
||||
"RT_p/m-Xylene": "14.52",
|
||||
"Conc_p/m-Xylene": "2.3680",
|
||||
"RT_o-Xylene": "15.21",
|
||||
"Conc_o-Xylene": "0.8644",
|
||||
"RT_1.3.5-Trimethylbenzene": "17.18",
|
||||
"Conc_1.3.5-Trimethylbenzene": "0.1939",
|
||||
"RT_1.2.4-Trimethylbenzene": "17.81",
|
||||
"Conc_1.2.4-Trimethylbenzene": "0.5433",
|
||||
"RT_1.2.3-Trimethylbenzene": "18.49",
|
||||
"Conc_1.2.3-Trimethylbenzene": "0.1672",
|
||||
"RT_Ethane": "5.41",
|
||||
"Conc_Ethane": "4.6423",
|
||||
"RT_Ethene": "5.77",
|
||||
"Conc_Ethene": "5.1753",
|
||||
"RT_Propane": "6.67",
|
||||
"Conc_Propane": "20.4921",
|
||||
"RT_Propene": "8.72",
|
||||
"Conc_Propene": "2.0739",
|
||||
"RT_i-Butane": "9.68",
|
||||
"Conc_i-Butane": "13.6268",
|
||||
"RT_n-Butane": "10.17",
|
||||
"Conc_n-Butane": "17.2023",
|
||||
"RT_Acetylene": "10.54",
|
||||
"Conc_Acetylene": "4.1668",
|
||||
"RT_Trans-2-Butane": "12.68",
|
||||
"Conc_Trans-2-Butane": "0.5570",
|
||||
"RT_1-Butene": "12.92",
|
||||
"Conc_1-Butene": "0.9296",
|
||||
"RT_cis-2-Butene": "13.71",
|
||||
"Conc_cis-2-Butene": "0.4130",
|
||||
"RT_i-Pentane": "14.26",
|
||||
"Conc_i-Pentane": "9.8822",
|
||||
"RT_n-Pentane": "14.74",
|
||||
"Conc_n-Pentane": "5.4647",
|
||||
"RT_1.3-Butadiene": "15.31",
|
||||
"Conc_1.3-Butadiene": "0.6577",
|
||||
"RT_Trans-2-Pentene": "16.38",
|
||||
"Conc_Trans-2-Pentene": "0.3918",
|
||||
"RT_1-Pentene": "16.84",
|
||||
"Conc_1-Pentene": "0.1332",
|
||||
"RT_2-Methylpentane": "18.00",
|
||||
"Conc_2-Methylpentane": "2.1133",
|
||||
"RT_IsoPrene": "18.82",
|
||||
"Conc_IsoPrene": "0.1381"
|
||||
},
|
||||
"Flags": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
10
Udara/Demo/M01-101.txt
Normal file
10
Udara/Demo/M01-101.txt
Normal file
@ -0,0 +1,10 @@
|
||||
172.16.101.42
|
||||
172.16.101.42
|
||||
172.16.101.43
|
||||
172.16.101.43
|
||||
172.16.101.48
|
||||
172.16.101.48
|
||||
172.16.101.49
|
||||
172.16.101.49
|
||||
172.16.101.146
|
||||
172.16.101.146
|
||||
2
Udara/Demo/MCAQM01/data/MCAQM_001_20260307_0830_avg.csv
Normal file
2
Udara/Demo/MCAQM01/data/MCAQM_001_20260307_0830_avg.csv
Normal file
File diff suppressed because one or more lines are too long
21
Udara/Demo/MCAQM01/data/MCAQM_001_20260307_0830_raw.csv
Normal file
21
Udara/Demo/MCAQM01/data/MCAQM_001_20260307_0830_raw.csv
Normal file
File diff suppressed because one or more lines are too long
2
Udara/Demo/MCAQM01/data/MCAQM_001_20260307_0845_avg.csv
Normal file
2
Udara/Demo/MCAQM01/data/MCAQM_001_20260307_0845_avg.csv
Normal file
File diff suppressed because one or more lines are too long
16
Udara/Demo/MCAQM01/data/MCAQM_001_20260307_0845_raw.csv
Normal file
16
Udara/Demo/MCAQM01/data/MCAQM_001_20260307_0845_raw.csv
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
322
Udara/Demo/OldData.json
Normal file
322
Udara/Demo/OldData.json
Normal file
@ -0,0 +1,322 @@
|
||||
{
|
||||
"Devices": [
|
||||
{
|
||||
"DeviceId": 1,
|
||||
"Code": "42i",
|
||||
"Name": "nox",
|
||||
"Mode": "modbus",
|
||||
"Values": {
|
||||
"coils": {
|
||||
"ZERO MODE": "0",
|
||||
"SPAN MODE": "0",
|
||||
"NO MODE": "1",
|
||||
"NOX MODE": "1",
|
||||
"INT TEMP ALARM": "0",
|
||||
"CHAMB TEMP ALARM": "0",
|
||||
"COOLER TEMP ALARM": "0",
|
||||
"NO2 CONVERTER TEMP ALARM": "0",
|
||||
"PERM OVEN GAS TEMP ALARM": "0",
|
||||
"PRESSURE ALARM": "0",
|
||||
"FLOW ALARM": "0",
|
||||
"OZONE FLOW ALARM": "0",
|
||||
"MOTHERBOARD STATUS ALARM": "0",
|
||||
"INTERFACE BD STATUS ALARM": "0",
|
||||
"SAMPLE MODE": "0",
|
||||
"PURGE MODE": "0",
|
||||
"ZERO CHECK CAL ALARM": "0",
|
||||
"SPAN CHECK CAL ALARM": "0"
|
||||
},
|
||||
"registers": {
|
||||
"NO": "52.5286",
|
||||
"NO2": "23.4154",
|
||||
"NOX": "75.944",
|
||||
"LO NO": "0",
|
||||
"INTERNAL TEMP": "35.1565",
|
||||
"CHAMBER TEMP": "50.1073",
|
||||
"COOLER TEMP": "-2.86198",
|
||||
"NO2 CONVERTER TEMP": "322.631",
|
||||
"CHAMBER PRESSURE": "259.936",
|
||||
"SAMPLE FLOW": "0.695768",
|
||||
"PMT VOLTS": "-846.191",
|
||||
"OZONATOR FLOW": "0.05"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 2,
|
||||
"Code": "43i",
|
||||
"Name": "so2",
|
||||
"Mode": "modbus",
|
||||
"Values": {
|
||||
"coils": {
|
||||
"AUTORANGE": "0",
|
||||
"LOCAL/REMOTE": "0",
|
||||
"ZERO MODE": "0",
|
||||
"SPAN MODE": "0",
|
||||
"SAMPLE MODE": "0",
|
||||
"INTERNAL TEMP ALARM": "0",
|
||||
"CHAMB TEMP ALARM": "0",
|
||||
"CONVERTER TEMP": "0",
|
||||
"PERM GAS TEMP": "0",
|
||||
"PRESSURE ALARM": "0",
|
||||
"SAMPLE FLOW ALARM": "0",
|
||||
"MOTHERBOARD STATUS ALARM": "0",
|
||||
"INTERFACE BD STATUS ALARM": "0",
|
||||
"ZERO CHK CAL ALARM": "0",
|
||||
"SPAN CHK CAL ALARM": "0"
|
||||
},
|
||||
"registers": {
|
||||
"SO2": "0.965215",
|
||||
"INTERNAL TEMP": "37.2088",
|
||||
"CHAMBER TEMP": "45.0619",
|
||||
"PERM OVEN GAS TEMP": "0",
|
||||
"PERM OVEN HEATER TEMP": "0",
|
||||
"CHAMBER PRESSURE": "731.395",
|
||||
"SAMPLE FLOW": "0.558697",
|
||||
"PMT VOLTS": "-622.711"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 3,
|
||||
"Code": "48i",
|
||||
"Name": "co",
|
||||
"Mode": "modbus",
|
||||
"Values": {
|
||||
"coils": {
|
||||
"ZERO MODE": "0",
|
||||
"SPAN MODE": "0",
|
||||
"SAMPLE MODE": "0",
|
||||
"INTERNAL TEMP ALARM": "0",
|
||||
"BENCH TEMP ALARM": "0",
|
||||
"PRESSURE ALARM": "0",
|
||||
"SAMPLE FLOW ALARM": "0",
|
||||
"INTENSITY ALARM": "0",
|
||||
"MOTOR SPEED ALARM": "0",
|
||||
"BIAS VOLTAGE ALARM": "0",
|
||||
"MOTHERBOARD STATUS ALARM": "0",
|
||||
"INTERFACE BD STATUS ALARM": "0",
|
||||
"PURGE MODE": "0",
|
||||
"ZERO CHK CAL ALARM": "0",
|
||||
"SPAN CHK CAL ALARM": "0"
|
||||
},
|
||||
"registers": {
|
||||
"CO": "1330.53",
|
||||
"INTERNAL TEMP": "37.6449",
|
||||
"BENCH TEMP": "47.9628",
|
||||
"PRESSURE": "743.735",
|
||||
"SAMPLE FLOW": "0.769839",
|
||||
"INTENSITY": "196445",
|
||||
"MOTOR SPEED": "100.03"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 4,
|
||||
"Code": "49i",
|
||||
"Name": "o3",
|
||||
"Mode": "modbus",
|
||||
"Values": {
|
||||
"coils": {
|
||||
"ZERO MODE": "0",
|
||||
"SPAN MODE": "0",
|
||||
"SAMPLE MODE": "0",
|
||||
"BENCH TEMP ALARM": "0",
|
||||
"BENCH LAMP TEMP ALARM": "0",
|
||||
"PRESSURE ALARM": "0",
|
||||
"FLOW A ALARM": "0",
|
||||
"FLOW B ALARM": "0",
|
||||
"INTENSITY A ALARM": "0",
|
||||
"INTENSITY B ALARM": "0",
|
||||
"ZERO CHK CAL ALARM": "0",
|
||||
"SPAN CHK CAL ALARM": "0",
|
||||
"MOTHERBOARD STATUS ALARM": "0",
|
||||
"INTERFACE BD STATUS ALARM": "0"
|
||||
},
|
||||
"registers": {
|
||||
"O3": "2.19913",
|
||||
"INTENSITY A": "51131",
|
||||
"INTENSITY B": "46722",
|
||||
"NOISE A": "0.4",
|
||||
"NOISE B": "0.5",
|
||||
"FLOW A": "0.742515",
|
||||
"FLOW B": "0.748315",
|
||||
"PRESSURE": "743.022",
|
||||
"BENCH TEMP": "37.9565",
|
||||
"LAMP TEMP": "53.3333",
|
||||
"O3 LAMP TEMP": "0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 5,
|
||||
"Code": "146i",
|
||||
"Name": "cal",
|
||||
"Mode": "modbus",
|
||||
"Values": {
|
||||
"coils": {
|
||||
"GAS BIT 1": "0",
|
||||
"GAS BIT 2": "0",
|
||||
"GAS BIT 3": "0",
|
||||
"SPAN BIT 1": "0",
|
||||
"SPAN BIT 2": "0",
|
||||
"SPAN BIT 3": "0",
|
||||
"O3 PERM BIT 1": "0",
|
||||
"O3 PERM BIT 2": "0",
|
||||
"O3 PERM BIT 3": "0",
|
||||
"O3 PERM BIT 4": "0",
|
||||
"PHOTO BIT 1": "0",
|
||||
"PHOTO BIT 2": "0",
|
||||
"GAS A": "0",
|
||||
"GAS B": "0",
|
||||
"GAS C": "0",
|
||||
"GAS D": "0",
|
||||
"GAS E": "0",
|
||||
"GAS F": "0",
|
||||
"OZONATOR": "0",
|
||||
"PERM OVEN": "0",
|
||||
"USER 1": "0",
|
||||
"USER 2": "0",
|
||||
"USER 3": "0",
|
||||
"USER 4": "0",
|
||||
"USER 5": "0",
|
||||
"USER 6": "0",
|
||||
"USER 7": "0",
|
||||
"USER 8": "0",
|
||||
"GEN ALARM": "0",
|
||||
"AMBIENT TEMP": "0",
|
||||
"O3 LAMP TEMP COIL": "0",
|
||||
"PERM GAS TEMP": "0",
|
||||
"PRESSURE COIL": "0",
|
||||
"PHOT BENCH TEMP": "0",
|
||||
"PHOT LAMP TEMP": "0",
|
||||
"PHOT INTENSITY": "0",
|
||||
"O3 LEVEL": "0"
|
||||
},
|
||||
"registers": {
|
||||
"GAS CONC": "0",
|
||||
"TARGET GFLOW": "0",
|
||||
"ACTUAL GFLOW": "0",
|
||||
"TARGET ZFLOW": "0",
|
||||
"ACTUAL ZFLOW": "0",
|
||||
"TARGET TFLOW": "0",
|
||||
"ACTUAL TFLOW": "0",
|
||||
"O3 CONC": "0",
|
||||
"PERM CONC": "0",
|
||||
"PRESSURE REGISTER": "0",
|
||||
"BENCH TEMP": "0",
|
||||
"LAMP TEMP": "0",
|
||||
"O3 LAMP TEMP REGISTER": "67.8798",
|
||||
"INTENSITY": "0",
|
||||
"NOISE": "0",
|
||||
"ANALOG 1": "0",
|
||||
"ANALOG 2": "0",
|
||||
"ANALOG 3": "0",
|
||||
"ANALOG 4": "0",
|
||||
"ANALOG 5": "0",
|
||||
"ANALOG 6": "0",
|
||||
"ANALOG 7": "0",
|
||||
"ANALOG 8": "0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 6,
|
||||
"Code": "1405df",
|
||||
"Name": "teom",
|
||||
"Mode": "ak",
|
||||
"Values": {
|
||||
"registers": {
|
||||
"System Status": "842137664",
|
||||
"Case Temperature": "34.138000",
|
||||
"Cap Temperature": "33.563000",
|
||||
"Ambient Temperature": "24.423000",
|
||||
"Ambient Relative Humidity": "98.226997",
|
||||
"TEOM A Flow Rate": "3.001083",
|
||||
"TEOM A Flow Volumetric": "3.001083",
|
||||
"TEOM A Filter Load": "58.404751",
|
||||
"TEOM A Mass Conc": "40.651314",
|
||||
"TEOM A 1-Hr Mass Conc": "42.717293",
|
||||
"TEOM A Frequency": "260.737398",
|
||||
"TEOM A Noise": "0.003009",
|
||||
"TEOM A Dryer Dew Point": "18.059000",
|
||||
"TEOM A Cooler Temperature": "156.656998",
|
||||
"TEOM A Base Mass Conc": "35.894749",
|
||||
"TEOM A Ref Mass Conc": "-4.756568",
|
||||
"TEOM B Flow Rate": "1.673967",
|
||||
"TEOM B Flow Volumetric": "1.673967",
|
||||
"TEOM B Filter Load": "14.703300",
|
||||
"TEOM B Mass Conc": "22.502310",
|
||||
"TEOM B 1-Hr Mass Conc": "20.611660",
|
||||
"TEOM B Frequency": "269.810451",
|
||||
"TEOM B Noise": "0.008147",
|
||||
"TEOM B Dryer Dew Point": "15.092000",
|
||||
"TEOM B Cooler Temp": "31.298000",
|
||||
"TEOM B Base Mass Conc": "17.669052",
|
||||
"TEOM B Ref Mass Conc": "-8.898390"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 7,
|
||||
"Code": "aio",
|
||||
"Name": "aio2",
|
||||
"Mode": "serial",
|
||||
"Values": {
|
||||
"registers": {
|
||||
"Wind Speed": "001.2",
|
||||
"Wind Direction": "078",
|
||||
"Ambient Temperature": "+024.4",
|
||||
"Relative Humidity": "094",
|
||||
"Barometric Pressure": "1012.2",
|
||||
"Rain": "000.00",
|
||||
"Solar Radiation": "0000",
|
||||
"Battery Voltage": "11.6",
|
||||
"Compass Reading": "204",
|
||||
"Config": "M0",
|
||||
"Checksum": "02531"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeviceId": 8,
|
||||
"Code": "ama",
|
||||
"Name": "ozoneprecursor",
|
||||
"Mode": "sqlserver",
|
||||
"Values": {
|
||||
"records": {
|
||||
"n-Hexane": "1.90965914981543",
|
||||
"Benzene": "3.15450421427172",
|
||||
"2,2,4-Trimethylpentane": "0.326820302007518",
|
||||
"n-Heptane": "0.462037380914552",
|
||||
"Toluene": "5.54570154009161",
|
||||
"n-Octane": "0.232242532762861",
|
||||
"Ethylbenzene": "1.36736475370466",
|
||||
"m,p-Xylene": "4.17614576951828",
|
||||
"o-Xylene": "1.54728054065554",
|
||||
"1,3,5-Trimethylbenzene": "0.954729491048662",
|
||||
"1,2,4-Trimethylbenzene": "0.0778578459822015",
|
||||
"1,2,3-Trimethylbenzene": "0.684891626947891",
|
||||
"Ethane": "751.839941139116",
|
||||
"Propane": "11.4367498915847",
|
||||
"Propene": "3.20627189267601",
|
||||
"n-Butane": "9.03891848210978",
|
||||
"Acetylene": "8.596369796437",
|
||||
"trans-2-Butene": "0.487337419423999",
|
||||
"cis-2-Butene": "6.12328662491597",
|
||||
"n-Pentane": "4.55600328780163",
|
||||
"1,3-Butadiene": "1.00341345168309",
|
||||
"trans-2-Pentene": "0.270654051057631",
|
||||
"1-Pentene": "0.441963118668669",
|
||||
"2-Methylpentane": "4.4342083089133",
|
||||
"n-Hexane(VOC)": "2.93893273212621",
|
||||
"Isoprene": "0.720918286820498",
|
||||
"Ethene": "0",
|
||||
"i-Butane": "0",
|
||||
"1-Butene": "0",
|
||||
"i-Pentane": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
10
Udara/Demo/S24-24.txt
Normal file
10
Udara/Demo/S24-24.txt
Normal file
@ -0,0 +1,10 @@
|
||||
172.16.24.42
|
||||
172.16.24.42
|
||||
172.16.24.43
|
||||
172.16.24.43
|
||||
172.16.24.48
|
||||
172.16.24.48
|
||||
172.16.24.49
|
||||
172.16.24.49
|
||||
172.16.24.146
|
||||
172.16.24.146
|
||||
30
Udara/Demo/Teom.csv
Normal file
30
Udara/Demo/Teom.csv
Normal file
@ -0,0 +1,30 @@
|
||||
teom_a_base_mass_conc,TEOM A Base Mass Conc,,,,,,,,,,,,System Status
|
||||
teom_a_cooler_temperature,TEOM A Cooler Temperature,,,,,,,,,,,,
|
||||
teom_a_ref_mass_conc,TEOM A Ref Mass Conc,,,,,,,,,,,,
|
||||
teom_ambient_relative_humidity,Ambient Relative Humidity,,,,,,,,,,,,
|
||||
teom_ambient_temperature,Ambient Temperature,,,,,,,,,,,,
|
||||
teom_b_base_mass_conc,TEOM B Base Mass Conc,,,,,,,,,,,,TEOM A Flow Rate
|
||||
teom_b_cooler_temp,TEOM B Cooler Temp,,,,,,,,,,,,TEOM A Flow Volumetric
|
||||
teom_b_ref_mass_conc,TEOM B Ref Mass Conc,,,,,,,,,,,,
|
||||
teom_cap_temperature,Cap Temperature,,,,,,,,,,,,TEOM A Mass Conc
|
||||
teom_case_temperature,Case Temperature,,,,,,,,,,,,
|
||||
teom_flag,,,,,,,,,,,,,TEOM A Frequency
|
||||
teom_maintenance,,,,,,,,,,,,,TEOM A Noise
|
||||
teom_repair,,,,,,,,,,,,,
|
||||
teom_system_status,,,,,,,,,,,,,
|
||||
teom_teom_a_1-hr_mass_conc,TEOM A 1-Hr Mass Conc,,,,,,,,,,,,
|
||||
teom_teom_a_1-hr_mass_conc,,,,,,,,,,,,,
|
||||
teom_teom_a_dryer_dew,TEOM A Dryer Dew Point,,,,,,,,,,,,TEOM B Flow Rate
|
||||
teom_teom_a_filter_load,TEOM A Filter Load,,,,,,,,,,,,TEOM B Flow Volumetric
|
||||
teom_teom_a_flow_rate,,,,,,,,,,,,,TEOM B Filter Load
|
||||
teom_teom_a_flow_volumetric,,,,,,,,,,,,,TEOM B Mass Conc
|
||||
teom_teom_a_frequency,,,,,,,,,,,,,TEOM B 1-Hr Mass Conc
|
||||
teom_teom_a_noise,,,,,,,,,,,,,TEOM B Frequency
|
||||
teom_teom_b_1-hr_mass_conc,,,,,,,,,,,,,TEOM B Noise
|
||||
teom_teom_b_1-hr_mass_conc,,,,,,,,,,,,,TEOM B Dryer Dew Point
|
||||
teom_teom_b_dryer_dew,,,,,,,,,,,,,
|
||||
teom_teom_b_filter_load,,,,,,,,,,,,,
|
||||
teom_teom_b_flow_rate,,,,,,,,,,,,,
|
||||
teom_teom_b_flow_volumetric,,,,,,,,,,,,,
|
||||
teom_teom_b_frequency,,,,,,,,,,,,,
|
||||
teom_teom_b_noise,,,,,,,,,,,,,
|
||||
|
541
Udara/Demo/a.txt
Normal file
541
Udara/Demo/a.txt
Normal file
@ -0,0 +1,541 @@
|
||||
Record Date
|
||||
Station
|
||||
teom_case_temperature
|
||||
teom_cap_temperature
|
||||
teom_ambient_temperature
|
||||
teom_ambient_relative_humidity
|
||||
teom_teom_a_flow_rate
|
||||
teom_teom_a_flow_volumetric
|
||||
teom_teom_a_filter_load
|
||||
teom_teom_a_1-hr_mass_conc
|
||||
teom_teom_a_1-hr_mass_conc
|
||||
teom_teom_a_frequency
|
||||
teom_teom_a_noise
|
||||
teom_teom_b_flow_rate
|
||||
teom_teom_b_flow_volumetric
|
||||
teom_teom_b_filter_load
|
||||
teom_teom_b_1-hr_mass_conc
|
||||
teom_teom_b_1-hr_mass_conc
|
||||
teom_teom_b_frequency
|
||||
teom_teom_b_noise
|
||||
teom_system_status
|
||||
nox_zero_mode
|
||||
nox_span_mode
|
||||
nox_no_mode
|
||||
nox_nox_mode
|
||||
nox_int_temp_alarm
|
||||
nox_chamb_temp_alarm
|
||||
nox_cooler_temp_alarm
|
||||
nox_no2_converter_temp_alarm
|
||||
nox_perm_oven_gas_temp_alarm
|
||||
nox_pressure_alarm
|
||||
nox_flow_alarm
|
||||
nox_ozone_flow_alarm
|
||||
nox_motherboard_status_alarm
|
||||
nox_interface_bd_status_alarm
|
||||
nox_sample_mode
|
||||
nox_purge_mode
|
||||
nox_zero_check_cal_alarm
|
||||
nox_span_check_cal_alarm
|
||||
nox_no
|
||||
nox_no2
|
||||
nox_nox
|
||||
nox_internal_temp
|
||||
nox_chamber_temp
|
||||
nox_cooler_temp
|
||||
nox_no2_converter_temp
|
||||
nox_chamber_pressure
|
||||
nox_sample_flow
|
||||
nox_pmt_volts
|
||||
nox_ozonator_flow
|
||||
so2_autorange
|
||||
so2_local/remote
|
||||
so2_zero_mode
|
||||
so2_span_mode
|
||||
so2_sample_mode
|
||||
so2_internal_temp_alarm
|
||||
so2_chamb_temp_alarm
|
||||
so2_converter_temp
|
||||
so2_perm_gas_temp
|
||||
so2_pressure_alarm
|
||||
so2_sample_flow_alarm
|
||||
so2_motherboard_status_alarm
|
||||
so2_interface_bd_status_alarm
|
||||
so2_zero_chk_cal_alarm
|
||||
so2_span_chk_cal_alarm
|
||||
so2_so2
|
||||
so2_internal_temp
|
||||
so2_chamber_temp
|
||||
so2_perm_oven_gas_temp
|
||||
so2_perm_oven_heater_temp
|
||||
so2_chamber_pressure
|
||||
so2_sample_flow
|
||||
so2_pmt_volts
|
||||
co_zero_mode
|
||||
co_span_mode
|
||||
co_sample_mode
|
||||
co_internal_temp_alarm
|
||||
co_bench_temp_alarm
|
||||
co_pressure_alarm
|
||||
co_sample_flow_alarm
|
||||
co_intensity_alarm
|
||||
co_motor_speed_alarm
|
||||
co_bias_voltage_alarm
|
||||
co_motherboard_status_alarm
|
||||
co_interface_bd_status_alarm
|
||||
co_purge_mode
|
||||
co_zero_chk_cal_alarm
|
||||
co_span_chk_cal_alarm
|
||||
co_co
|
||||
co_internal_temp
|
||||
co_bench_temp
|
||||
co_pressure
|
||||
co_sample_flow
|
||||
co_intensity
|
||||
co_motor_speed
|
||||
o3_zero_mode
|
||||
o3_span_mode
|
||||
o3_sample_mode
|
||||
o3_bench_temp_alarm
|
||||
o3_bench_lamp_temp_alarm
|
||||
o3_pressure_alarm
|
||||
o3_flow_a_alarm
|
||||
o3_flow_b_alarm
|
||||
o3_intensity_a_alarm
|
||||
o3_intensity_b_alarm
|
||||
o3_zero_chk_cal_alarm
|
||||
o3_span_chk_cal_alarm
|
||||
o3_motherboard_status_alarm
|
||||
o3_interface_bd_status_alarm
|
||||
o3_o3
|
||||
o3_intensity_a
|
||||
o3_intensity_b
|
||||
o3_noise_a
|
||||
o3_noise_b
|
||||
o3_flow_a
|
||||
o3_flow_b
|
||||
o3_pressure
|
||||
o3_bench_temp
|
||||
o3_lamp_temp
|
||||
o3_o3_lamp_temp
|
||||
cal_gas_bit_1
|
||||
cal_gas_bit_2
|
||||
cal_gas_bit_3
|
||||
cal_span_bit_1
|
||||
cal_span_bit_2
|
||||
cal_span_bit_3
|
||||
cal_o3_perm_bit_1
|
||||
cal_o3_perm_bit_2
|
||||
cal_o3_perm_bit_3
|
||||
cal_o3_perm_bit_4
|
||||
cal_photo_bit_2
|
||||
cal_photo_bit_1
|
||||
cal_gas_a
|
||||
cal_gas_b
|
||||
cal_gas_c
|
||||
cal_gas_d
|
||||
cal_gas_e
|
||||
cal_gas_f
|
||||
cal_ozonator
|
||||
cal_perm_oven
|
||||
cal_user_1
|
||||
cal_user_2
|
||||
cal_user_3
|
||||
cal_user_4
|
||||
cal_user_5
|
||||
cal_user_6
|
||||
cal_user_7
|
||||
cal_user_8
|
||||
cal_gen_alarm
|
||||
cal_ambient_temp
|
||||
cal_o3_lamp_temp_coil
|
||||
cal_perm_gas_temp
|
||||
cal_pressure_coil
|
||||
cal_phot_bench_temp
|
||||
cal_phot_lamp_temp
|
||||
cal_phot_intensity
|
||||
cal_o3_level
|
||||
cal_gas_conc
|
||||
cal_target_gflow
|
||||
cal_actual_gflow
|
||||
cal_target_zflow
|
||||
cal_actual_zflow
|
||||
cal_target_tflow
|
||||
cal_actual_tflow
|
||||
cal_o3_conc
|
||||
cal_perm_conc
|
||||
cal_pressure_register
|
||||
cal_bench_temp
|
||||
cal_lamp_temp
|
||||
cal_o3_lamp_temp_register
|
||||
cal_intensity
|
||||
cal_noise
|
||||
cal_analog_1
|
||||
cal_analog_2
|
||||
cal_analog_3
|
||||
cal_analog_4
|
||||
cal_analog_5
|
||||
cal_analog_6
|
||||
cal_analog_7
|
||||
cal_analog_8
|
||||
ozonePrecursor_n-Hexane
|
||||
ozonePrecursor_Benzene
|
||||
ozonePrecursor_2_2_4-Trimethylpentane
|
||||
ozonePrecursor_n-Heptane
|
||||
ozonePrecursor_Toluene
|
||||
ozonePrecursor_n-Octane
|
||||
ozonePrecursor_Ethylbenzene
|
||||
ozonePrecursor_m_p-Xylene
|
||||
??ozonePrecursor_Styrene
|
||||
ozonePrecursor_o-Xylene
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene
|
||||
ozonePrecursor_Ethane
|
||||
ozonePrecursor_Ethene
|
||||
ozonePrecursor_Propane
|
||||
ozonePrecursor_Propene
|
||||
ozonePrecursor_i-Butane
|
||||
ozonePrecursor_n-Butane
|
||||
ozonePrecursor_Acetylene
|
||||
ozonePrecursor_trans-2-Butene
|
||||
ozonePrecursor_1-Butene
|
||||
ozonePrecursor_cis-2-Butene
|
||||
ozonePrecursor_i-Pentane
|
||||
ozonePrecursor_n-Pentane
|
||||
ozonePrecursor_1_3-Butadiene
|
||||
ozonePrecursor_trans-2-Pentene
|
||||
ozonePrecursor_1-Pentene
|
||||
??ozonePrecursor_cis-2-Pentene
|
||||
ozonePrecursor_2-Methylpentane
|
||||
ozonePrecursor_n-Hexane(VOC)
|
||||
ozonePrecursor_Isoprene
|
||||
aio_wind_speed
|
||||
aio_wind_direction
|
||||
aio_ambient_temperature
|
||||
aio_relative_humidity
|
||||
aio_baromatric_pressure
|
||||
aio_rain
|
||||
aio_solar_radiation
|
||||
aio_CONFIG
|
||||
aio_battery_voltage
|
||||
nox_no_daily_Qccheck_zero_actual
|
||||
nox_no_daily_Qccheck_zero_target
|
||||
nox_no_daily_Qccheck_zero_drift
|
||||
nox_no_daily_Qccheck_span_actual
|
||||
nox_no_daily_Qccheck_span_target
|
||||
nox_no_daily_Qccheck_span_drift
|
||||
nox_no2_daily_Qccheck_zero_target
|
||||
nox_no2_daily_Qccheck_zero_actual
|
||||
nox_no2_daily_Qccheck_zero_drift
|
||||
nox_no2_daily_Qccheck_span_actual
|
||||
nox_no2_daily_Qccheck_span_target
|
||||
nox_no2_daily_Qccheck_span_drift
|
||||
nox_nox_daily_Qccheck_zero_actual
|
||||
nox_nox_daily_Qccheck_zero_target
|
||||
nox_nox_daily_Qccheck_zero_drift
|
||||
nox_nox_daily_Qccheck_span_actual
|
||||
nox_nox_daily_Qccheck_span_target
|
||||
nox_nox_daily_Qccheck_span_drift
|
||||
so2_so2_daily_Qccheck_zero_actual
|
||||
so2_so2_daily_Qccheck_zero_target
|
||||
so2_so2_daily_Qccheck_zero_drift
|
||||
so2_so2_daily_Qccheck_span_actual
|
||||
so2_so2_daily_Qccheck_span_target
|
||||
so2_so2_daily_Qccheck_span_drift
|
||||
co_co_daily_Qccheck_zero_actual
|
||||
co_co_daily_Qccheck_zero_target
|
||||
co_co_daily_Qccheck_zero_drift
|
||||
co_co_daily_Qccheck_span_actual
|
||||
co_co_daily_Qccheck_span_target
|
||||
co_co_daily_Qccheck_span_drift
|
||||
o3_o3_daily_Qccheck_zero_actual
|
||||
o3_o3_daily_Qccheck_zero_target
|
||||
o3_o3_daily_Qccheck_zero_drift
|
||||
o3_o3_daily_Qccheck_span_actual
|
||||
o3_o3_daily_Qccheck_span_target
|
||||
o3_o3_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Hexane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Benzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Heptane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Toluene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Octane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_m_p-Xylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Styrene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_o-Xylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Ethene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Propane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Propene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_i-Butane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Butane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Acetylene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_trans-2-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_cis-2-Butene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_i-Pentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Pentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_1_3-Butadiene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_trans-2-Pentene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_cis-2-Pentene_daily_Qccheck_span_drift
|
||||
ozonePrecursor_2-Methylpentane_daily_Qccheck_span_drift
|
||||
ozonePrecursor_n-Hexane(VOC)_daily_Qccheck_span_drift
|
||||
ozonePrecursor_Isoprene_daily_Qccheck_span_drift
|
||||
nox_no_single_cal_zero_actual
|
||||
nox_no_single_cal_zero_target
|
||||
nox_no_single_cal_zero_drift
|
||||
nox_no_single_cal_span_actual
|
||||
nox_no_single_cal_span_target
|
||||
nox_no_single_cal_span_drift
|
||||
nox_no2_single_cal_zero_actual
|
||||
nox_no2_single_cal_zero_target
|
||||
nox_no2_single_cal_zero_drift
|
||||
nox_no2_single_cal_span_actual
|
||||
nox_no2_single_cal_span_target
|
||||
nox_no2_single_cal_span_drift
|
||||
nox_nox_single_cal_zero_actual
|
||||
nox_nox_single_cal_zero_target
|
||||
nox_nox_single_cal_zero_drift
|
||||
nox_nox_single_cal_span_actual
|
||||
nox_nox_single_cal_span_target
|
||||
nox_nox_single_cal_span_drift
|
||||
so2_so2_single_cal_zero_actual
|
||||
so2_so2_single_cal_zero_target
|
||||
so2_so2_single_cal_zero_drift
|
||||
so2_so2_single_cal_span_actual
|
||||
so2_so2_single_cal_span_target
|
||||
so2_so2_single_cal_span_drift
|
||||
co_co_single_cal_zero_actual
|
||||
co_co_single_cal_zero_target
|
||||
co_co_single_cal_zero_drift
|
||||
co_co_single_cal_span_actual
|
||||
co_co_single_cal_span_target
|
||||
co_co_single_cal_span_drift
|
||||
o3_o3_single_cal_zero_actual
|
||||
o3_o3_single_cal_zero_target
|
||||
o3_o3_single_cal_zero_drift
|
||||
o3_o3_single_cal_span_actual
|
||||
o3_o3_single_cal_span_target
|
||||
o3_o3_single_cal_span_drift
|
||||
nox_no_multi_cal_zero_actual
|
||||
nox_no_multi_cal_zero_target
|
||||
nox_no_multi_cal_zero_drift
|
||||
nox_no_multi_cal_span1_actual
|
||||
nox_no_multi_cal_span1_target
|
||||
nox_no_multi_cal_span1_drift
|
||||
nox_no_multi_cal_span2_actual
|
||||
nox_no_multi_cal_span2_target
|
||||
nox_no_multi_cal_span2_drift
|
||||
nox_no_multi_cal_span3_actual
|
||||
nox_no_multi_cal_span3_target
|
||||
nox_no_multi_cal_span3_drift
|
||||
nox_no_multi_cal_span4_actual
|
||||
nox_no_multi_cal_span4_target
|
||||
nox_no_multi_cal_span4_drift
|
||||
nox_no2_multi_cal_zero_actual
|
||||
nox_no2_multi_cal_zero_target
|
||||
nox_no2_multi_cal_zero_drift
|
||||
nox_no2_multi_cal_span1_actual
|
||||
nox_no2_multi_cal_span1_target
|
||||
nox_no2_multi_cal_span1_drift
|
||||
nox_no2_multi_cal_span2_actual
|
||||
nox_no2_multi_cal_span2_target
|
||||
nox_no2_multi_cal_span2_drift
|
||||
nox_no2_multi_cal_span3_actual
|
||||
nox_no2_multi_cal_span3_target
|
||||
nox_no2_multi_cal_span3_drift
|
||||
nox_no2_multi_cal_span4_actual
|
||||
nox_no2_multi_cal_span4_target
|
||||
nox_no2_multi_cal_span4_drift
|
||||
nox_nox_multi_cal_zero_actual
|
||||
nox_nox_multi_cal_zero_target
|
||||
nox_nox_multi_cal_zero_drift
|
||||
nox_nox_multi_cal_span1_actual
|
||||
nox_nox_multi_cal_span1_target
|
||||
nox_nox_multi_cal_span1_drift
|
||||
nox_nox_multi_cal_span2_actual
|
||||
nox_nox_multi_cal_span2_target
|
||||
nox_nox_multi_cal_span2_drift
|
||||
nox_nox_multi_cal_span3_actual
|
||||
nox_nox_multi_cal_span3_target
|
||||
nox_nox_multi_cal_span3_drift
|
||||
nox_nox_multi_cal_span4_actual
|
||||
nox_nox_multi_cal_span4_target
|
||||
nox_nox_multi_cal_span4_drift
|
||||
so2_so2_multi_cal_zero_actual
|
||||
so2_so2_multi_cal_zero_target
|
||||
so2_so2_multi_cal_zero_drift
|
||||
so2_so2_multi_cal_span1_actual
|
||||
so2_so2_multi_cal_span1_target
|
||||
so2_so2_multi_cal_span1_drift
|
||||
so2_so2_multi_cal_span2_actual
|
||||
so2_so2_multi_cal_span2_target
|
||||
so2_so2_multi_cal_span2_drift
|
||||
so2_so2_multi_cal_span3_actual
|
||||
so2_so2_multi_cal_span3_target
|
||||
so2_so2_multi_cal_span3_drift
|
||||
so2_so2_multi_cal_span4_actual
|
||||
so2_so2_multi_cal_span4_target
|
||||
so2_so2_multi_cal_span4_drift
|
||||
co_co_multi_cal_zero_actual
|
||||
co_co_multi_cal_zero_target
|
||||
co_co_multi_cal_zero_drift
|
||||
co_co_multi_cal_span1_actual
|
||||
co_co_multi_cal_span1_target
|
||||
co_co_multi_cal_span1_drift
|
||||
co_co_multi_cal_span2_actual
|
||||
co_co_multi_cal_span2_target
|
||||
co_co_multi_cal_span2_drift
|
||||
co_co_multi_cal_span3_actual
|
||||
co_co_multi_cal_span3_target
|
||||
co_co_multi_cal_span3_drift
|
||||
co_co_multi_cal_span4_actual
|
||||
co_co_multi_cal_span4_target
|
||||
co_co_multi_cal_span4_drift
|
||||
o3_o3_multi_cal_zero_actual
|
||||
o3_o3_multi_cal_zero_target
|
||||
o3_o3_multi_cal_zero_drift
|
||||
o3_o3_multi_cal_span1_actual
|
||||
o3_o3_multi_cal_span1_target
|
||||
o3_o3_multi_cal_span1_drift
|
||||
o3_o3_multi_cal_span2_actual
|
||||
o3_o3_multi_cal_span2_target
|
||||
o3_o3_multi_cal_span2_drift
|
||||
o3_o3_multi_cal_span3_actual
|
||||
o3_o3_multi_cal_span3_target
|
||||
o3_o3_multi_cal_span3_drift
|
||||
o3_o3_multi_cal_span4_actual
|
||||
o3_o3_multi_cal_span4_target
|
||||
o3_o3_multi_cal_span4_drift
|
||||
ozonePrecursor_n-Hexane_single_cal_span_actual
|
||||
ozonePrecursor_n-Hexane_single_cal_span_target
|
||||
ozonePrecursor_n-Hexane_single_cal_span_drift
|
||||
ozonePrecursor_Benzene_single_cal_span_actual
|
||||
ozonePrecursor_Benzene_single_cal_span_target
|
||||
ozonePrecursor_Benzene_single_cal_span_drift
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_actual
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_target
|
||||
ozonePrecursor_2_2_4-Trimethylpentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Heptane_single_cal_span_actual
|
||||
ozonePrecursor_n-Heptane_single_cal_span_target
|
||||
ozonePrecursor_n-Heptane_single_cal_span_drift
|
||||
ozonePrecursor_Toluene_single_cal_span_actual
|
||||
ozonePrecursor_Toluene_single_cal_span_target
|
||||
ozonePrecursor_Toluene_single_cal_span_drift
|
||||
ozonePrecursor_n-Octane_single_cal_span_actual
|
||||
ozonePrecursor_n-Octane_single_cal_span_target
|
||||
ozonePrecursor_n-Octane_single_cal_span_drift
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_Ethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_actual
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_target
|
||||
ozonePrecursor_m_p-Xylene_single_cal_span_drift
|
||||
ozonePrecursor_Styrene_single_cal_span_actual
|
||||
ozonePrecursor_Styrene_single_cal_span_target
|
||||
ozonePrecursor_Styrene_single_cal_span_drift
|
||||
ozonePrecursor_o-Xylene_single_cal_span_actual
|
||||
ozonePrecursor_o-Xylene_single_cal_span_target
|
||||
ozonePrecursor_o-Xylene_single_cal_span_drift
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_3_5-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_2_4-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_actual
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_target
|
||||
ozonePrecursor_1_2_3-Trimethylbenzene_single_cal_span_drift
|
||||
ozonePrecursor_Ethane_single_cal_span_actual
|
||||
ozonePrecursor_Ethane_single_cal_span_target
|
||||
ozonePrecursor_Ethane_single_cal_span_drift
|
||||
ozonePrecursor_Ethene_single_cal_span_actual
|
||||
ozonePrecursor_Ethene_single_cal_span_target
|
||||
ozonePrecursor_Ethene_single_cal_span_drift
|
||||
ozonePrecursor_Propane_single_cal_span_actual
|
||||
ozonePrecursor_Propane_single_cal_span_target
|
||||
ozonePrecursor_Propane_single_cal_span_drift
|
||||
ozonePrecursor_Propene_single_cal_span_actual
|
||||
ozonePrecursor_Propene_single_cal_span_target
|
||||
ozonePrecursor_Propene_single_cal_span_drift
|
||||
ozonePrecursor_i-Butane_single_cal_span_actual
|
||||
ozonePrecursor_i-Butane_single_cal_span_target
|
||||
ozonePrecursor_i-Butane_single_cal_span_drift
|
||||
ozonePrecursor_n-Butane_single_cal_span_actual
|
||||
ozonePrecursor_n-Butane_single_cal_span_target
|
||||
ozonePrecursor_n-Butane_single_cal_span_drift
|
||||
ozonePrecursor_Acetylene_single_cal_span_actual
|
||||
ozonePrecursor_Acetylene_single_cal_span_target
|
||||
ozonePrecursor_Acetylene_single_cal_span_drift
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_actual
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_target
|
||||
ozonePrecursor_trans-2-Butene_single_cal_span_drift
|
||||
ozonePrecursor_1-Butene_single_cal_span_actual
|
||||
ozonePrecursor_1-Butene_single_cal_span_target
|
||||
ozonePrecursor_1-Butene_single_cal_span_drift
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_actual
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_target
|
||||
ozonePrecursor_cis-2-Butene_single_cal_span_drift
|
||||
ozonePrecursor_i-Pentane_single_cal_span_actual
|
||||
ozonePrecursor_i-Pentane_single_cal_span_target
|
||||
ozonePrecursor_i-Pentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Pentane_single_cal_span_actual
|
||||
ozonePrecursor_n-Pentane_single_cal_span_target
|
||||
ozonePrecursor_n-Pentane_single_cal_span_drift
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_actual
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_target
|
||||
ozonePrecursor_1_3-Butadiene_single_cal_span_drift
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_target
|
||||
ozonePrecursor_trans-2-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_1-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_1-Pentene_single_cal_span_target
|
||||
ozonePrecursor_1-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_actual
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_target
|
||||
ozonePrecursor_cis-2-Pentene_single_cal_span_drift
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_actual
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_target
|
||||
ozonePrecursor_2-Methylpentane_single_cal_span_drift
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_actual
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_target
|
||||
ozonePrecursor_n-Hexane(VOC)_single_cal_span_drift
|
||||
ozonePrecursor_Isoprene_single_cal_span_actual
|
||||
ozonePrecursor_Isoprene_single_cal_span_target
|
||||
ozonePrecursor_Isoprene_single_cal_span_drift
|
||||
so2_maintenance
|
||||
nox_maintenance
|
||||
co_maintenance
|
||||
o3_maintenance
|
||||
teom_maintenance
|
||||
ozonePrecursor_maintenance
|
||||
aio_maintenance
|
||||
so2_repair
|
||||
nox_repair
|
||||
co_repair
|
||||
o3_repair
|
||||
teom_repair
|
||||
aio_repair
|
||||
ozonePrecursor_repair
|
||||
nox_flag
|
||||
so2_flag
|
||||
o3_flag
|
||||
co_flag
|
||||
aio_flag
|
||||
ozone_precursor_flag
|
||||
teom_flag
|
||||
teom_teom_a_dryer_dew
|
||||
teom_teom_b_dryer_dew
|
||||
teom_a_cooler_temperature
|
||||
teom_a_base_mass_conc
|
||||
teom_a_ref_mass_conc
|
||||
teom_b_cooler_temp
|
||||
teom_b_base_mass_conc
|
||||
teom_b_ref_mass_conc
|
||||
diagnostic_teom_a
|
||||
diagnostic_teom_b
|
||||
BIN
Udara/Dependencies/01-dotnet-runtime-8.0.24-win-x64.exe
Normal file
BIN
Udara/Dependencies/01-dotnet-runtime-8.0.24-win-x64.exe
Normal file
Binary file not shown.
BIN
Udara/Dependencies/02-aspnetcore-runtime-8.0.24-win-x64.exe
Normal file
BIN
Udara/Dependencies/02-aspnetcore-runtime-8.0.24-win-x64.exe
Normal file
Binary file not shown.
BIN
Udara/Dependencies/03-windowsdesktop-runtime-8.0.24-win-x64.exe
Normal file
BIN
Udara/Dependencies/03-windowsdesktop-runtime-8.0.24-win-x64.exe
Normal file
Binary file not shown.
BIN
Udara/Documentation/1405DF.pdf
Normal file
BIN
Udara/Documentation/1405DF.pdf
Normal file
Binary file not shown.
BIN
Udara/Documentation/48i_101891-00_10Aug2017.pdf
Normal file
BIN
Udara/Documentation/48i_101891-00_10Aug2017.pdf
Normal file
Binary file not shown.
BIN
Udara/Documentation/AIO-2-9800-Manual-Rev-E.pdf
Normal file
BIN
Udara/Documentation/AIO-2-9800-Manual-Rev-E.pdf
Normal file
Binary file not shown.
97998
Udara/Documentation/EPM-TEOM1405-Manual.pdf
Normal file
97998
Udara/Documentation/EPM-TEOM1405-Manual.pdf
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Udara/Documentation/EPM-manual-Model 146i.pdf
Normal file
BIN
Udara/Documentation/EPM-manual-Model 146i.pdf
Normal file
Binary file not shown.
BIN
Udara/Documentation/EPM-manual-Model 42i.pdf
Normal file
BIN
Udara/Documentation/EPM-manual-Model 42i.pdf
Normal file
Binary file not shown.
BIN
Udara/Documentation/EPM-manual-Model 43i.pdf
Normal file
BIN
Udara/Documentation/EPM-manual-Model 43i.pdf
Normal file
Binary file not shown.
BIN
Udara/Documentation/EPM-manual-Model 49i.pdf
Normal file
BIN
Udara/Documentation/EPM-manual-Model 49i.pdf
Normal file
Binary file not shown.
88
Udara/Infrastructure/Chronolog/Chronolog.cs
Normal file
88
Udara/Infrastructure/Chronolog/Chronolog.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Udara.Infrastructure.Chronolog;
|
||||
|
||||
public class Chronolog : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly string _logFilePath;
|
||||
private readonly object _lock;
|
||||
|
||||
public Chronolog(string categoryName, string logFilePath, object logLock)
|
||||
{
|
||||
Assembly? entryAssembly = Assembly.GetEntryAssembly();
|
||||
var rootNamespace = entryAssembly?.GetName().Name ?? string.Empty;
|
||||
_categoryName = categoryName.StartsWith(rootNamespace + ".")
|
||||
? categoryName.Substring(categoryName.LastIndexOf('.') + 1)
|
||||
: categoryName;
|
||||
|
||||
_logFilePath = logFilePath;
|
||||
_lock = logLock;
|
||||
}
|
||||
|
||||
IDisposable ILogger.BeginScope<TState>(TState state) => null!;
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId,
|
||||
TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
var logTimeStamp = DateTime.Now.ToString("yyyyMMdd-HHmmssff");
|
||||
var logLevelString = logLevel.ToString();
|
||||
var logMessage = formatter(state, exception);
|
||||
|
||||
const string crlf = "\r\n";
|
||||
logMessage = logMessage.Replace(crlf, crlf + "\t");
|
||||
|
||||
var logLine = $"[{logTimeStamp}][{_categoryName}][{logLevelString}]\r\n\t{logMessage}";
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_logFilePath)!);
|
||||
File.AppendAllText(_logFilePath, logLine + Environment.NewLine);
|
||||
}
|
||||
|
||||
if (!Console.IsOutputRedirected)
|
||||
{
|
||||
WriteColored(logLevel, logLine);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteColored(LogLevel level, string message)
|
||||
{
|
||||
var originalFg = Console.ForegroundColor;
|
||||
var originalBg = Console.BackgroundColor;
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LogLevel.Trace:
|
||||
case LogLevel.Debug:
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
break;
|
||||
|
||||
case LogLevel.Information:
|
||||
Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||
break;
|
||||
|
||||
case LogLevel.Warning:
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
break;
|
||||
|
||||
case LogLevel.Error:
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
break;
|
||||
|
||||
case LogLevel.Critical:
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.BackgroundColor = ConsoleColor.Red;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Console.WriteLine(message);
|
||||
|
||||
Console.ForegroundColor = originalFg;
|
||||
Console.BackgroundColor = originalBg;
|
||||
}
|
||||
}
|
||||
57
Udara/Infrastructure/Chronolog/ChronologProvider.cs
Normal file
57
Udara/Infrastructure/Chronolog/ChronologProvider.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace Udara.Infrastructure.Chronolog;
|
||||
|
||||
public class ChronologProvider : ILoggerProvider
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly string _logDirectory;
|
||||
|
||||
public ChronologProvider()
|
||||
{
|
||||
_logDirectory = Path.Combine(AppContext.BaseDirectory, "Logs");
|
||||
Directory.CreateDirectory(_logDirectory);
|
||||
ArchiveOldLogs();
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
var logFileName = $"{DateTime.Now:yyyyMMdd}.log";
|
||||
var logPath = Path.Combine(_logDirectory, logFileName);
|
||||
return new Chronolog(categoryName, logPath, _lock);
|
||||
}
|
||||
|
||||
private void ArchiveOldLogs()
|
||||
{
|
||||
var cutoffDate = DateTime.Now.AddMonths(-3);
|
||||
var logFiles = Directory.GetFiles(_logDirectory, "*.log");
|
||||
|
||||
var groups = logFiles
|
||||
.Select(path => new FileInfo(path))
|
||||
.Where(fi => fi.LastWriteTime < cutoffDate)
|
||||
.GroupBy(fi => fi.Name.Substring(0, 6)
|
||||
);
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var year = group.Key.Substring(0, 4);
|
||||
var archiveDir = Path.Combine(_logDirectory, "Archive", year);
|
||||
Directory.CreateDirectory(archiveDir);
|
||||
|
||||
var archivePath = Path.Combine(archiveDir, $"{group.Key}.zip");
|
||||
|
||||
using var archive = new ZipArchive(
|
||||
new FileStream(archivePath, FileMode.OpenOrCreate),
|
||||
ZipArchiveMode.Update
|
||||
);
|
||||
|
||||
foreach (var file in group)
|
||||
{
|
||||
archive.CreateEntryFromFile(file.FullName, file.Name);
|
||||
File.Delete(file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
21
Udara/Infrastructure/FileSystem/FileReader.cs
Normal file
21
Udara/Infrastructure/FileSystem/FileReader.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Udara.Infrastructure.FileSystem;
|
||||
|
||||
public sealed class FileReader : IFileReader
|
||||
{
|
||||
public async Task<string> ReadAllTextAsync(string path, CancellationToken token)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return string.Empty;
|
||||
|
||||
using var stream = File.OpenRead(path);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
return await reader.ReadToEndAsync(token);
|
||||
}
|
||||
|
||||
public Task<bool> ExistsAsync(string path, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult(File.Exists(path));
|
||||
}
|
||||
}
|
||||
7
Udara/Infrastructure/FileSystem/IFileReader.cs
Normal file
7
Udara/Infrastructure/FileSystem/IFileReader.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Udara.Infrastructure.FileSystem;
|
||||
|
||||
public interface IFileReader
|
||||
{
|
||||
Task<string> ReadAllTextAsync(string path, CancellationToken token);
|
||||
Task<bool> ExistsAsync(string path, CancellationToken token);
|
||||
}
|
||||
76
Udara/Infrastructure/FileTransfer/FileTransfer.cs
Normal file
76
Udara/Infrastructure/FileTransfer/FileTransfer.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Udara.Infrastructure.FileTransfer;
|
||||
|
||||
public sealed class FileTransfer : IFileTransfer
|
||||
{
|
||||
private readonly string _root;
|
||||
|
||||
public FileTransfer()
|
||||
{
|
||||
_root = Path.Combine(AppContext.BaseDirectory, "Workplace", "FileProcessing");
|
||||
Directory.CreateDirectory(_root);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<string>> CollectFilesAsync(
|
||||
string fileProcessName,
|
||||
string targetPath,
|
||||
string filePattern,
|
||||
string extension,
|
||||
CancellationToken token)
|
||||
{
|
||||
var baseDir = Path.Combine(_root, fileProcessName);
|
||||
var collectionDir = Path.Combine(baseDir, "Collection");
|
||||
var processedDir = Path.Combine(baseDir, "Processed");
|
||||
|
||||
Directory.CreateDirectory(collectionDir);
|
||||
Directory.CreateDirectory(processedDir);
|
||||
|
||||
var regexPattern = $"^{Regex.Escape(filePattern)}.*$";
|
||||
var regex = new Regex(regexPattern, RegexOptions.IgnoreCase);
|
||||
|
||||
var files = Directory
|
||||
.EnumerateFiles(targetPath, $"*.{extension}", SearchOption.TopDirectoryOnly)
|
||||
.Where(path => regex.IsMatch(Path.GetFileName(path)))
|
||||
.ToList();
|
||||
|
||||
var results = new List<string>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var filename = Path.GetFileName(file);
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmssff");
|
||||
var newName = $"{timestamp}_{filename}";
|
||||
var destPath = Path.Combine(collectionDir, newName);
|
||||
|
||||
File.Copy(file, destPath, overwrite: false);
|
||||
|
||||
if (File.Exists(destPath))
|
||||
{
|
||||
File.Delete(file);
|
||||
results.Add(destPath);
|
||||
}
|
||||
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async Task ProcessedFilesAsync(string sourceFile, CancellationToken token)
|
||||
{
|
||||
var collectionDir = Path.GetDirectoryName(sourceFile)!;
|
||||
var rootDir = Directory.GetParent(collectionDir)!.FullName;
|
||||
var processedDir = Path.Combine(rootDir, "Processed");
|
||||
|
||||
if (!Directory.Exists(processedDir))
|
||||
Directory.CreateDirectory(processedDir);
|
||||
|
||||
var destFile = Path.Combine(processedDir, Path.GetFileName(sourceFile));
|
||||
|
||||
File.Move(sourceFile, destFile, overwrite: true);
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
12
Udara/Infrastructure/FileTransfer/IFileTransfer.cs
Normal file
12
Udara/Infrastructure/FileTransfer/IFileTransfer.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Udara.Infrastructure.FileTransfer;
|
||||
|
||||
public interface IFileTransfer
|
||||
{
|
||||
Task<IReadOnlyList<string>> CollectFilesAsync(
|
||||
string processName,
|
||||
string targetDir,
|
||||
string filePattern,
|
||||
string extension,
|
||||
CancellationToken token);
|
||||
Task ProcessedFilesAsync(string sourceFile, CancellationToken token);
|
||||
}
|
||||
18
Udara/Infrastructure/Ftp/FtpConfig.cs
Normal file
18
Udara/Infrastructure/Ftp/FtpConfig.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Udara.Infrastructure.Ftp;
|
||||
|
||||
public sealed class FtpConfig
|
||||
{
|
||||
public string Host { get; init; } = string.Empty;
|
||||
public int Port { get; init; } = 21;
|
||||
|
||||
public string Username { get; init; } = string.Empty;
|
||||
public string Password { get; init; } = string.Empty;
|
||||
|
||||
// ftp, ftps, sftp
|
||||
public string Protocol { get; init; } = "ftp";
|
||||
|
||||
// true = passive, false = active
|
||||
public bool Passive { get; init; } = true;
|
||||
|
||||
public string RemoteDirectory { get; init; } = "/";
|
||||
}
|
||||
61
Udara/Infrastructure/Ftp/FtpUploader.cs
Normal file
61
Udara/Infrastructure/Ftp/FtpUploader.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using WinSCP;
|
||||
|
||||
namespace Udara.Infrastructure.Ftp;
|
||||
|
||||
public sealed class FtpUploader : IFtpUploader
|
||||
{
|
||||
private readonly ILogger<FtpUploader> _logger;
|
||||
|
||||
public FtpUploader(ILogger<FtpUploader> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task UploadAsync(
|
||||
string localFilePath,
|
||||
string remoteFileName,
|
||||
FtpConfig config,
|
||||
CancellationToken token)
|
||||
{
|
||||
var sessionOptions = new WinSCP.SessionOptions
|
||||
{
|
||||
HostName = config.Host,
|
||||
PortNumber = config.Port,
|
||||
UserName = config.Username,
|
||||
Password = config.Password,
|
||||
Protocol = config.Protocol.ToLower() switch
|
||||
{
|
||||
"ftp" => Protocol.Ftp,
|
||||
"sftp" => Protocol.Sftp,
|
||||
"ftps" => Protocol.Ftp,
|
||||
_ => Protocol.Ftp
|
||||
},
|
||||
FtpMode = config.Passive ? FtpMode.Passive : FtpMode.Active,
|
||||
TlsHostCertificateFingerprint = null
|
||||
};
|
||||
|
||||
using var session = new Session();
|
||||
|
||||
session.Open(sessionOptions);
|
||||
|
||||
var baseDir = string.IsNullOrWhiteSpace(config.RemoteDirectory) ? "/" : config.RemoteDirectory.TrimEnd('/');
|
||||
|
||||
var remotePath = $"{baseDir}/{remoteFileName}";
|
||||
|
||||
var transferOptions = new TransferOptions
|
||||
{
|
||||
TransferMode = TransferMode.Binary
|
||||
};
|
||||
|
||||
var result = session.PutFiles(localFilePath, remotePath, false, transferOptions);
|
||||
|
||||
result.Check();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Uploaded {Local} to {Remote} on {Host}",
|
||||
localFilePath, remotePath, config.Host
|
||||
);
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
11
Udara/Infrastructure/Ftp/IFtpUploader.cs
Normal file
11
Udara/Infrastructure/Ftp/IFtpUploader.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Udara.Infrastructure.Ftp;
|
||||
|
||||
public interface IFtpUploader
|
||||
{
|
||||
Task UploadAsync(
|
||||
string localFilePath,
|
||||
string remoteFileName,
|
||||
FtpConfig config,
|
||||
CancellationToken token
|
||||
);
|
||||
}
|
||||
45
Udara/Infrastructure/Logging/AppFileLogger.cs
Normal file
45
Udara/Infrastructure/Logging/AppFileLogger.cs
Normal file
@ -0,0 +1,45 @@
|
||||
namespace Udara.Infrastructure.Logging;
|
||||
|
||||
public class AppFileLogger : ILogger
|
||||
{
|
||||
private readonly string _path;
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public AppFileLogger(string path)
|
||||
{
|
||||
_path = path;
|
||||
}
|
||||
|
||||
IDisposable ILogger.BeginScope<TState>(TState state)
|
||||
{
|
||||
return NullScope.Instance;
|
||||
}
|
||||
|
||||
bool ILogger.IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void ILogger.Log<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
var message = formatter(state, exception);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
File.AppendAllText(
|
||||
_path,
|
||||
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {message}{Environment.NewLine}");
|
||||
}
|
||||
}
|
||||
|
||||
private class NullScope : IDisposable
|
||||
{
|
||||
public static readonly NullScope Instance = new();
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
16
Udara/Infrastructure/Logging/AppFileLoggerProvider.cs
Normal file
16
Udara/Infrastructure/Logging/AppFileLoggerProvider.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Udara.Infrastructure.Logging;
|
||||
|
||||
public class AppFileLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
public AppFileLoggerProvider(string path)
|
||||
{
|
||||
_path = path;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
=> new AppFileLogger(_path);
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
10
Udara/Infrastructure/ProcessExecutor/IProcessExecutor.cs
Normal file
10
Udara/Infrastructure/ProcessExecutor/IProcessExecutor.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Udara.Infrastructure.ProcessExecutor;
|
||||
|
||||
public interface IProcessExecutor
|
||||
{
|
||||
Task<ProcessExecutorResult> ExecuteAsync(
|
||||
string executablePath,
|
||||
IEnumerable<string> arguments,
|
||||
CancellationToken token
|
||||
);
|
||||
}
|
||||
54
Udara/Infrastructure/ProcessExecutor/ProcessExecutor.cs
Normal file
54
Udara/Infrastructure/ProcessExecutor/ProcessExecutor.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace Udara.Infrastructure.ProcessExecutor;
|
||||
|
||||
public class ProcessExecutor : IProcessExecutor
|
||||
{
|
||||
private readonly ILogger<ProcessExecutor> _logger;
|
||||
|
||||
public ProcessExecutor(ILogger<ProcessExecutor> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ProcessExecutorResult> ExecuteAsync(
|
||||
string executablePath,
|
||||
IEnumerable<string> arguments,
|
||||
CancellationToken token)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = executablePath,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
foreach (var arg in arguments)
|
||||
psi.ArgumentList.Add(arg);
|
||||
|
||||
using var process = new Process { StartInfo = psi };
|
||||
|
||||
process.Start();
|
||||
|
||||
var stdoutTask = process.StandardOutput.ReadToEndAsync(token);
|
||||
var stderrTask = process.StandardError.ReadToEndAsync(token);
|
||||
|
||||
await Task.WhenAll(stdoutTask, stderrTask);
|
||||
|
||||
var result = new ProcessExecutorResult
|
||||
{
|
||||
ExitCode = process.ExitCode,
|
||||
StandardOutput = stdoutTask.Result ?? string.Empty,
|
||||
StandardError = stderrTask.Result ?? string.Empty
|
||||
};
|
||||
|
||||
await process.WaitForExitAsync(token);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Udara.Infrastructure.ProcessExecutor;
|
||||
|
||||
public sealed class ProcessExecutorResult
|
||||
{
|
||||
public int ExitCode { get; set; }
|
||||
public string StandardOutput { get; set; } = string.Empty;
|
||||
public string StandardError { get; set; } = string.Empty;
|
||||
}
|
||||
12
Udara/Infrastructure/Serial/ISerialPortClient.cs
Normal file
12
Udara/Infrastructure/Serial/ISerialPortClient.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Udara.Infrastructure.Serial;
|
||||
|
||||
public interface ISerialPortClient : IDisposable
|
||||
{
|
||||
Task<string> SendAndReceiveAsync(string command, CancellationToken token);
|
||||
Task<string> WaitForDataAsync(CancellationToken token);
|
||||
Task SendCommandAsync(string command, CancellationToken token);
|
||||
|
||||
Task SendBytesAsync(byte[] data, CancellationToken token);
|
||||
int ReadByte();
|
||||
int BytesToRead { get; }
|
||||
}
|
||||
10
Udara/Infrastructure/Serial/ISerialPortFactory.cs
Normal file
10
Udara/Infrastructure/Serial/ISerialPortFactory.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Udara.Database.App.Model;
|
||||
|
||||
namespace Udara.Infrastructure.Serial;
|
||||
|
||||
public interface ISerialPortFactory
|
||||
{
|
||||
ISerialPortClient Create(string portName, int baudRate);
|
||||
ISerialPortClient Create(DeviceSerialModel serialModel);
|
||||
ISerialPortClient Create(DeviceSerialModel serialModel, bool binaryMode);
|
||||
}
|
||||
158
Udara/Infrastructure/Serial/SerialPortClient.cs
Normal file
158
Udara/Infrastructure/Serial/SerialPortClient.cs
Normal file
@ -0,0 +1,158 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using Udara.Database.App.Model;
|
||||
|
||||
namespace Udara.Infrastructure.Serial;
|
||||
|
||||
public sealed class SerialPortClient : ISerialPortClient
|
||||
{
|
||||
private readonly SerialPort _port;
|
||||
private readonly ConcurrentQueue<string> _queue = new();
|
||||
private readonly bool _binaryMode;
|
||||
|
||||
public SerialPortClient(string portName, int baudRate)
|
||||
{
|
||||
_port = new SerialPort(portName, baudRate);
|
||||
_port.DataReceived += OnDataReceived;
|
||||
_port.Open();
|
||||
}
|
||||
|
||||
public SerialPortClient(DeviceSerialModel serialModel)
|
||||
: this(serialModel,false)
|
||||
{
|
||||
}
|
||||
|
||||
public SerialPortClient(DeviceSerialModel serialModel, bool binaryMode)
|
||||
{
|
||||
_binaryMode = binaryMode;
|
||||
|
||||
_port = new SerialPort()
|
||||
{
|
||||
PortName = serialModel.PortName,
|
||||
BaudRate = serialModel.BaudRate,
|
||||
DataBits = serialModel.DataBits,
|
||||
Parity = ParseParity(serialModel.Parity),
|
||||
StopBits = ParseStopBits(serialModel.StopBits),
|
||||
Handshake = ParseHandshake(serialModel.FlowControl),
|
||||
Encoding = ParseEncoding(serialModel.Encoding),
|
||||
NewLine = ParseNewLine(serialModel.NewLine),
|
||||
ReadTimeout = serialModel.ReadTimeout,
|
||||
WriteTimeout = serialModel.WriteTimeout
|
||||
};
|
||||
if (!_binaryMode)
|
||||
_port.DataReceived += OnDataReceived;
|
||||
_port.Open();
|
||||
}
|
||||
|
||||
private void OnDataReceived(object? sender, SerialDataReceivedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var line = _port.ReadLine();
|
||||
_queue.Enqueue(line);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore or log
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> SendAndReceiveAsync(string command, CancellationToken token)
|
||||
{
|
||||
_port.WriteLine(command);
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
if (_queue.TryDequeue(out var line))
|
||||
return line;
|
||||
|
||||
await Task.Delay(10, token);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public async Task<string> WaitForDataAsync(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
if (_queue.TryDequeue(out var line))
|
||||
return line;
|
||||
|
||||
await Task.Delay(10, token);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public Task SendCommandAsync(string command, CancellationToken token)
|
||||
{
|
||||
_port.WriteLine(command);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public int ReadByte() => _port.ReadByte();
|
||||
public int BytesToRead => _port.BytesToRead;
|
||||
public Task SendBytesAsync(byte[] data, CancellationToken token)
|
||||
{
|
||||
_port.Write(data, 0, data.Length);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_port.DataReceived -= OnDataReceived;
|
||||
if (_port.IsOpen)
|
||||
_port.Close();
|
||||
_port.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static Parity ParseParity(string p) =>
|
||||
p.ToLower() switch
|
||||
{
|
||||
"none" => Parity.None,
|
||||
"odd" => Parity.Odd,
|
||||
"even" => Parity.Even,
|
||||
_ => Parity.None
|
||||
};
|
||||
|
||||
private static StopBits ParseStopBits(int s) =>
|
||||
s switch
|
||||
{
|
||||
1 => StopBits.One,
|
||||
2 => StopBits.Two,
|
||||
_ => StopBits.One
|
||||
};
|
||||
|
||||
private static Handshake ParseHandshake(string f) =>
|
||||
f.ToLower() switch
|
||||
{
|
||||
"none" => Handshake.None,
|
||||
"xonxoff" => Handshake.XOnXOff,
|
||||
"rts" => Handshake.RequestToSend,
|
||||
"rtsxonxoff" => Handshake.RequestToSendXOnXOff,
|
||||
_ => Handshake.None
|
||||
};
|
||||
|
||||
private static string ParseNewLine(string n) =>
|
||||
n.ToLower() switch
|
||||
{
|
||||
"crlf" => "\r\n",
|
||||
"lf" => "\n",
|
||||
"cr" => "\r",
|
||||
_ => "\n"
|
||||
};
|
||||
|
||||
private static Encoding ParseEncoding(string e) =>
|
||||
e.ToLower() switch
|
||||
{
|
||||
"ascii" => Encoding.ASCII,
|
||||
"utf-8" => Encoding.UTF8,
|
||||
_ => Encoding.ASCII
|
||||
};
|
||||
}
|
||||
13
Udara/Infrastructure/Serial/SerialPortFactory.cs
Normal file
13
Udara/Infrastructure/Serial/SerialPortFactory.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Udara.Database.App.Model;
|
||||
|
||||
namespace Udara.Infrastructure.Serial;
|
||||
|
||||
public sealed class SerialPortFactory : ISerialPortFactory
|
||||
{
|
||||
public ISerialPortClient Create(string portName, int baudRate)
|
||||
=> new SerialPortClient(portName, baudRate);
|
||||
public ISerialPortClient Create(DeviceSerialModel serialModel)
|
||||
=> new SerialPortClient(serialModel);
|
||||
public ISerialPortClient Create(DeviceSerialModel model, bool binaryMode = false)
|
||||
=> new SerialPortClient(model, binaryMode);
|
||||
}
|
||||
6
Udara/Infrastructure/Telegram/ITelegramApi.cs
Normal file
6
Udara/Infrastructure/Telegram/ITelegramApi.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Udara.Infrastructure.Telegram;
|
||||
|
||||
public interface ITelegramApi
|
||||
{
|
||||
Task SendMessageAsync(string text, CancellationToken token = default);
|
||||
}
|
||||
23
Udara/Infrastructure/Telegram/TelegramApi.cs
Normal file
23
Udara/Infrastructure/Telegram/TelegramApi.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Udara.Common.Feature.Telegram;
|
||||
|
||||
namespace Udara.Infrastructure.Telegram;
|
||||
|
||||
public class TelegramApi : ITelegramApi
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ITelegramSettingsProvider _settingsProvider;
|
||||
|
||||
public TelegramApi(HttpClient httpClient, ITelegramSettingsProvider settingsProvider)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_settingsProvider = settingsProvider;
|
||||
}
|
||||
|
||||
public async Task SendMessageAsync(string text, CancellationToken token = default)
|
||||
{
|
||||
var settings = await _settingsProvider.GetSettingsAsync(token);
|
||||
var url = $"{settings.Protocol}://{settings.Host}/{settings.Mode}{settings.Token}/{settings.Method}";
|
||||
var payload = new { chat_id = settings.TargetChatId, text };
|
||||
await _httpClient.PostAsJsonAsync(url, payload);
|
||||
}
|
||||
}
|
||||
222
Udara/Program.cs
Normal file
222
Udara/Program.cs
Normal file
@ -0,0 +1,222 @@
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Udara.Common.Feature.Csv;
|
||||
using Udara.Common.Feature.Telegram;
|
||||
using Udara.Common.Feature.Time;
|
||||
using Udara.Database.App;
|
||||
using Udara.Database.Data;
|
||||
using Udara.Infrastructure.Chronolog;
|
||||
using Udara.Infrastructure.FileSystem;
|
||||
using Udara.Infrastructure.FileTransfer;
|
||||
using Udara.Infrastructure.Ftp;
|
||||
using Udara.Infrastructure.Logging;
|
||||
using Udara.Infrastructure.ProcessExecutor;
|
||||
using Udara.Infrastructure.Serial;
|
||||
using Udara.Infrastructure.Telegram;
|
||||
using Udara.SvcApp;
|
||||
using Udara.SvcApp.Poll.Processor;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
using Udara.SvcApp.Poll.Processor.Mode;
|
||||
|
||||
namespace Udara;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static readonly string AppDbFileName = $"app.db";
|
||||
public static readonly string DataDbFileName = $"data.db";
|
||||
public static readonly string CertFileName = $"cert.pem";
|
||||
public static readonly string MvcDirName = "WebApp";
|
||||
|
||||
public static readonly string _databaseDir = Path.Combine(AppContext.BaseDirectory, "Database");
|
||||
public static readonly string _logDir = Path.Combine(AppContext.BaseDirectory, "Logs");
|
||||
public static readonly string _toolDir = Path.Combine(AppContext.BaseDirectory, "Tools");
|
||||
|
||||
public static readonly string _appDbFilePath = Path.Combine(_databaseDir, AppDbFileName);
|
||||
public static readonly string _dataDbFilePath = Path.Combine(_databaseDir, DataDbFileName);
|
||||
|
||||
public class ProgramConfigs
|
||||
{
|
||||
public string ProjectName { get; set; } = null!;
|
||||
public string KestrelHost { get; set; } = null!;
|
||||
public string KestrelPort { get; set; } = null!;
|
||||
}
|
||||
|
||||
//public class LogConfigs
|
||||
//{
|
||||
// public string AppLogPath { get; set; } = null!;
|
||||
// public string SvcLogPath { get; set; } = null!;
|
||||
// public string WebLogPath { get; set; } = null!;
|
||||
//}
|
||||
|
||||
//private static readonly string _appLogPath = Path.Combine(_logFolder, $"{ProjectName}-App.log");
|
||||
//private static readonly string _svcLogPath = Path.Combine(_logFolder, $"{ProjectName}-Svc.log");
|
||||
//private static readonly string _webLogPath = Path.Combine(_logFolder, $"{ProjectName}-Web.log");
|
||||
|
||||
private static ProgramConfigs GetProgramConfigs()
|
||||
{
|
||||
try
|
||||
{
|
||||
ProgramConfigs configs = new();
|
||||
var appDbPath = Path.Combine(_databaseDir, AppDbFileName);
|
||||
var dbcOptions = new DbContextOptionsBuilder<AppDbContext>().UseSqlite($"Data Source={_appDbFilePath}").Options;
|
||||
var db = new AppDbContext(dbcOptions);
|
||||
var configsList = db.AppConfigs.ToList();
|
||||
configsList.ForEach(row =>
|
||||
{
|
||||
var key = row.ConfigKey;
|
||||
var val = row.ConfigValue;
|
||||
switch (key)
|
||||
{
|
||||
case "ProjectName":
|
||||
configs.ProjectName = val; break;
|
||||
case "KestrelHost":
|
||||
configs.KestrelHost = val; break;
|
||||
case "KestrelPort":
|
||||
configs.KestrelPort = val; break;
|
||||
default: break;
|
||||
}
|
||||
});
|
||||
return configs;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// Initial Checks
|
||||
if (!Directory.Exists(_logDir)) Directory.CreateDirectory(_logDir);
|
||||
if (!File.Exists(_appDbFilePath)) throw new FileNotFoundException();
|
||||
|
||||
// Initial Configs
|
||||
ProgramConfigs configs = GetProgramConfigs();
|
||||
if (configs == null) throw new NullReferenceException();
|
||||
|
||||
Console.WriteLine("Hello World");
|
||||
Console.WriteLine("ProjectName: " + configs.ProjectName);
|
||||
|
||||
// Init WebApp Builder
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
//// Init Reloadable AppSettings
|
||||
//builder.Configuration
|
||||
// .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
// .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
||||
|
||||
// Init Chronolog Logger
|
||||
builder.Logging.ClearProviders();
|
||||
//builder.Logging.AddProvider(new ChronologProvider());
|
||||
builder.Services.AddSingleton<ILoggerProvider, ChronologProvider>();
|
||||
|
||||
// Init Service Register
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Enable Windows Service hosting
|
||||
builder.Services.AddWindowsService(options =>
|
||||
{
|
||||
options.ServiceName = configs.ProjectName;
|
||||
});
|
||||
}
|
||||
|
||||
// Init EF Core
|
||||
builder.Services.AddDbContext<AppDbContext>(options =>
|
||||
{
|
||||
options.UseSqlite($"Data Source={_appDbFilePath}");
|
||||
});
|
||||
builder.Services.AddDbContext<DataDbContext>(options =>
|
||||
{
|
||||
options.UseSqlite($"Data Source={_dataDbFilePath}");
|
||||
});
|
||||
//using (var scope = builder.Services.BuildServiceProvider().CreateScope())
|
||||
//{
|
||||
// var raw = scope.ServiceProvider.GetRequiredService<DataDbContext>();
|
||||
// raw.Database.EnsureCreated();
|
||||
//}
|
||||
|
||||
// Init Cert
|
||||
var cert = new X509Certificate2(Path.Combine(_toolDir, CertFileName));
|
||||
|
||||
// Init HttpClient
|
||||
using var httpClient = new HttpClientHandler();
|
||||
httpClient.ClientCertificates.Add(cert);
|
||||
|
||||
// Init Main Worker
|
||||
builder.Services.AddHostedService<SvcWorker>();
|
||||
|
||||
// Init Feature
|
||||
builder.Services.AddSingleton<IMinuteAlignment, MinuteAlignment>();
|
||||
builder.Services.AddSingleton<ICsvParser, CsvParser>();
|
||||
builder.Services.AddSingleton<ITelegramSettingsProvider, TelegramSettingsProvider>();
|
||||
|
||||
// Init Infrastructure
|
||||
builder.Services.AddSingleton<IProcessExecutor, ProcessExecutor>();
|
||||
builder.Services.AddSingleton<IFileReader, FileReader>();
|
||||
builder.Services.AddSingleton<IFileTransfer, FileTransfer>();
|
||||
builder.Services.AddSingleton<ISerialPortFactory, SerialPortFactory>();
|
||||
builder.Services.AddSingleton<IFtpUploader, FtpUploader>();
|
||||
builder.Services.AddHttpClient<ITelegramApi,TelegramApi>().ConfigurePrimaryHttpMessageHandler(() => httpClient);
|
||||
|
||||
builder.Services.AddScoped<IDeviceProcessorFactory, DeviceProcessorFactory>();
|
||||
builder.Services.AddScoped<IProcessorResultCollector, ProcessorResultCollector>();
|
||||
|
||||
builder.Services.AddTransient<AioProcessor>();
|
||||
builder.Services.AddTransient<IDeviceProcessor, AioProcessor>();
|
||||
builder.Services.AddTransient<AkProcessor>();
|
||||
builder.Services.AddTransient<IDeviceProcessor, AkProcessor>();
|
||||
builder.Services.AddTransient<AmaProcessor>();
|
||||
builder.Services.AddTransient<IDeviceProcessor, AmaProcessor>();
|
||||
builder.Services.AddTransient<ModbusProcessor>();
|
||||
builder.Services.AddTransient<IDeviceProcessor, ModbusProcessor>();
|
||||
builder.Services.AddTransient<CsvProcessor>();
|
||||
builder.Services.AddTransient<IDeviceProcessor, CsvProcessor>();
|
||||
|
||||
// Init .NET MVC
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
// Modify Razor 'Area' and 'View' location
|
||||
builder.Services.Configure<RazorViewEngineOptions>(options =>
|
||||
{
|
||||
var uniqueViewFormat = "/" + MvcDirName + "/Views/{1}/{0}";
|
||||
var sharedViewFormat = "/" + MvcDirName + "/Views/Shared/{0}";
|
||||
options.ViewLocationFormats.Clear();
|
||||
options.ViewLocationFormats.Add(uniqueViewFormat + RazorViewEngine.ViewExtension);
|
||||
options.ViewLocationFormats.Add(sharedViewFormat + RazorViewEngine.ViewExtension);
|
||||
options.AreaViewLocationFormats.Clear();
|
||||
options.AreaViewLocationFormats.Add(uniqueViewFormat + RazorViewEngine.ViewExtension);
|
||||
options.AreaViewLocationFormats.Add(sharedViewFormat + RazorViewEngine.ViewExtension);
|
||||
});
|
||||
|
||||
builder.WebHost.UseStaticWebAssets();
|
||||
|
||||
// Remove Logging
|
||||
//builder.Logging.ClearProviders();
|
||||
|
||||
// Init WebApp
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
}
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
app.Run($"http://{configs.KestrelHost}:{configs.KestrelPort}");
|
||||
app.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
Udara/Properties/launchSettings.json
Normal file
30
Udara/Properties/launchSettings.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:7789",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": ""
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:55630",
|
||||
"sslPort": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Udara/SvcApp/Poll/Processor/DataLoggingBatch.cs
Normal file
6
Udara/SvcApp/Poll/Processor/DataLoggingBatch.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Udara.SvcApp.Poll.Processor;
|
||||
|
||||
public class DataLoggingBatch
|
||||
{
|
||||
public List<DeviceResultSnapshot> Devices { get; set; } = [];
|
||||
}
|
||||
31
Udara/SvcApp/Poll/Processor/DeviceProcessorFactory.cs
Normal file
31
Udara/SvcApp/Poll/Processor/DeviceProcessorFactory.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Udara.Database.App.Model;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
using Udara.SvcApp.Poll.Processor.Mode;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor;
|
||||
|
||||
public class DeviceProcessorFactory : IDeviceProcessorFactory
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public DeviceProcessorFactory(IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public IDeviceProcessor? Create(DeviceListModel device)
|
||||
{
|
||||
var mode = device.Mode?.ToLowerInvariant();
|
||||
|
||||
return mode switch
|
||||
{
|
||||
"modbus" => _services.GetRequiredService<ModbusProcessor>(),
|
||||
"serial" => _services.GetRequiredService<AioProcessor>(),
|
||||
"sqlserver" => _services.GetRequiredService<AmaProcessor>(),
|
||||
"ak" => _services.GetRequiredService<AkProcessor>(),
|
||||
"csv" => _services.GetService<CsvProcessor>(),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
13
Udara/SvcApp/Poll/Processor/DeviceResultSnapshot.cs
Normal file
13
Udara/SvcApp/Poll/Processor/DeviceResultSnapshot.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Udara.SvcApp.Poll.Processor;
|
||||
|
||||
public class DeviceResultSnapshot
|
||||
{
|
||||
public int DeviceId { get; set; }
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Mode { get; set; } = string.Empty;
|
||||
public Dictionary<string, string> Parameters { get; set; }
|
||||
= new(StringComparer.OrdinalIgnoreCase);
|
||||
public Dictionary<string, string> Flags { get; set; }
|
||||
= new(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
using Udara.Database.App.Model;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
public interface IDeviceProcessor
|
||||
{
|
||||
Task ProcessAsync(DeviceListModel device, CancellationToken token);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
using Udara.Database.App.Model;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
public interface IDeviceProcessorFactory
|
||||
{
|
||||
IDeviceProcessor? Create(DeviceListModel device);
|
||||
}
|
||||
5
Udara/SvcApp/Poll/Processor/Interface/IDeviceResult.cs
Normal file
5
Udara/SvcApp/Poll/Processor/Interface/IDeviceResult.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
public interface IDeviceResult
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using Udara.Database.App.Model;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
public interface IProcessorResultCollector
|
||||
{
|
||||
void BeginBatch(IReadOnlyCollection<DeviceListModel> devices, DateTime Timestamp);
|
||||
Task CollectAsync(DeviceListModel device, IDeviceResult result, CancellationToken token);
|
||||
}
|
||||
132
Udara/SvcApp/Poll/Processor/Mode/AioProcessor.cs
Normal file
132
Udara/SvcApp/Poll/Processor/Mode/AioProcessor.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Udara.Database.App;
|
||||
using Udara.Database.App.Model;
|
||||
using Udara.Infrastructure.Serial;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor.Mode;
|
||||
|
||||
public class AioResult : IDeviceResult
|
||||
{
|
||||
public DeviceListModel Device { get; set; } = default!;
|
||||
public Dictionary<string, string> Parameters { get; set; } = new();
|
||||
}
|
||||
|
||||
public class AioProcessor : IDeviceProcessor
|
||||
{
|
||||
private readonly ILogger<AioProcessor> _logger;
|
||||
private readonly IProcessorResultCollector _collector;
|
||||
private readonly AppDbContext _db;
|
||||
private readonly ISerialPortFactory _serialPortFactory;
|
||||
|
||||
public AioProcessor(
|
||||
ILogger<AioProcessor> logger,
|
||||
IProcessorResultCollector collector,
|
||||
AppDbContext db,
|
||||
ISerialPortFactory serialPortFactory
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_collector = collector;
|
||||
_db = db;
|
||||
_serialPortFactory = serialPortFactory;
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(DeviceListModel device, CancellationToken token)
|
||||
{
|
||||
var serial = await _db.DeviceSerial
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.DeviceId == device.Id, token);
|
||||
|
||||
if (serial == null)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceSerial entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = await _db.DeviceParameters
|
||||
.AsNoTracking()
|
||||
.Where(x => x.DeviceId == device.Id)
|
||||
.OrderBy(x => x.ParameterId)
|
||||
.ToListAsync(token);
|
||||
|
||||
if (parameters.Count == 0)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceParameters entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Always initialize dictionary with all register names = ""
|
||||
var aioData = parameters.ToDictionary(r => r.ParameterName, _ => "");
|
||||
|
||||
string? serialLine = null;
|
||||
|
||||
try
|
||||
{
|
||||
using var client = _serialPortFactory.Create(serial);
|
||||
serialLine = await client.WaitForDataAsync(token);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(serialLine))
|
||||
{
|
||||
// Parsing Data
|
||||
var parsed = await ParseAioLineAsync(device.Id, serialLine, parameters, token);
|
||||
|
||||
// Overwrite only the values that were parsed
|
||||
foreach (var kv in parsed)
|
||||
aioData[kv.Key] = kv.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("DeviceId {DeviceId}: Empty AIO line received", device.Id);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: Timeout reading from AIO device", device.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "DeviceId {DeviceId}: Error reading AIO device", device.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var result = new AioResult
|
||||
{
|
||||
Device = device,
|
||||
Parameters = aioData
|
||||
};
|
||||
|
||||
await _collector.CollectAsync(device, result, token);
|
||||
}
|
||||
}
|
||||
|
||||
private Task<Dictionary<string, string>> ParseAioLineAsync(
|
||||
int deviceId,
|
||||
string line,
|
||||
List<DeviceParametersModel> parameters,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
var parts = line.Trim().Split(',');
|
||||
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
for (int i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
if (i >= parts.Length)
|
||||
{
|
||||
dict[parameters[i].ParameterName] = "";
|
||||
continue;
|
||||
}
|
||||
|
||||
string value = parts[i];
|
||||
|
||||
if (parameters[i].ParameterName == "Checksum")
|
||||
value = value.TrimStart('*');
|
||||
|
||||
dict[parameters[i].ParameterName] = value;
|
||||
}
|
||||
|
||||
return Task.FromResult(dict);
|
||||
}
|
||||
}
|
||||
244
Udara/SvcApp/Poll/Processor/Mode/AkProcessor.cs
Normal file
244
Udara/SvcApp/Poll/Processor/Mode/AkProcessor.cs
Normal file
@ -0,0 +1,244 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text;
|
||||
using Udara.Database.App;
|
||||
using Udara.Database.App.Model;
|
||||
using Udara.Infrastructure.Serial;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor.Mode;
|
||||
|
||||
public class AkResult : IDeviceResult
|
||||
{
|
||||
public DeviceListModel Device { get; set; } = default!;
|
||||
public Dictionary<string, string> Parameters { get; set; }
|
||||
= new();
|
||||
}
|
||||
|
||||
public class AkProcessor : IDeviceProcessor
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
private readonly ILogger<AkProcessor> _logger;
|
||||
private readonly IProcessorResultCollector _collector;
|
||||
private readonly ISerialPortFactory _serialPortFactory;
|
||||
|
||||
public AkProcessor(
|
||||
AppDbContext db,
|
||||
ILogger<AkProcessor> logger,
|
||||
IProcessorResultCollector collector,
|
||||
ISerialPortFactory serialPortFactory
|
||||
)
|
||||
{
|
||||
_db = db;
|
||||
_logger = logger;
|
||||
_collector = collector;
|
||||
_serialPortFactory = serialPortFactory;
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(DeviceListModel device, CancellationToken token)
|
||||
{
|
||||
var serial = await _db.DeviceSerial
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.DeviceId == device.Id, token);
|
||||
|
||||
if (serial == null)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceSerial entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = await _db.DeviceParameters
|
||||
.AsNoTracking()
|
||||
.Where(x => x.DeviceId == device.Id)
|
||||
.OrderBy(x => x.ParameterId)
|
||||
.ToListAsync(token);
|
||||
|
||||
if (parameters.Count == 0)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceRegisters entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Always initialize dictionary with all register names = ""
|
||||
var akData = parameters.ToDictionary(r => r.ParameterName, _ => "");
|
||||
|
||||
try
|
||||
{
|
||||
using var client = _serialPortFactory.Create(serial, binaryMode: true);
|
||||
|
||||
foreach (var reg in parameters)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var prc = reg.ParameterId;
|
||||
var request = BuildRequest(prc.ToString());
|
||||
|
||||
await client.SendBytesAsync(request, token);
|
||||
|
||||
// Allow TEOM/AK device time to process each command
|
||||
await Task.Delay(50, token);
|
||||
|
||||
var response = ReadResponse(device, client, prc);
|
||||
|
||||
if (response.IsError)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"DeviceId {DeviceId}: PRC {PRC} returned error {ErrorType}",
|
||||
device.Id, prc, response.ErrorType
|
||||
);
|
||||
|
||||
akData[reg.ParameterName] = "";
|
||||
continue;
|
||||
}
|
||||
akData[reg.ParameterName] = response.Value;
|
||||
}
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: Timeout reading from AK device", device.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "DeviceId {DeviceId}: Error reading AK device", device.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var result = new AkResult
|
||||
{
|
||||
Device = device,
|
||||
Parameters = akData
|
||||
};
|
||||
|
||||
await _collector.CollectAsync(device, result, token);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] BuildRequest(string prc)
|
||||
{
|
||||
var body = $"4AREG K0 {prc}";
|
||||
var bytes = new List<byte>();
|
||||
bytes.Add(0x02); // STX
|
||||
bytes.AddRange(Encoding.ASCII.GetBytes(body));
|
||||
bytes.Add(0x03); // ETX
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
private AkResponse ReadResponse(
|
||||
DeviceListModel device,
|
||||
ISerialPortClient client,
|
||||
int prc
|
||||
)
|
||||
{
|
||||
var buffer = new List<byte>();
|
||||
bool inFrame = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int b = client.ReadByte();
|
||||
|
||||
if (!inFrame)
|
||||
{
|
||||
// Ignore everything until STX
|
||||
if (b == 0x02)
|
||||
{
|
||||
inFrame = true;
|
||||
buffer.Add((byte)b);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Inside STX..ETX
|
||||
buffer.Add((byte)b);
|
||||
|
||||
if (b == 0x03)
|
||||
{
|
||||
// ETX reached → frame complete
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// After ETX, discard CR/LF if present
|
||||
while (client.BytesToRead > 0)
|
||||
{
|
||||
int next = client.ReadByte();
|
||||
if (next != 0x0D && next != 0x0A)
|
||||
{
|
||||
// Not CR/LF → TEOM never sends meaningful data after ETX.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"DeviceId {DeviceId}: PRC {PRC} raw frame: {Frame}",
|
||||
device.Id,
|
||||
prc,
|
||||
ToCEscaped(buffer.ToArray())
|
||||
);
|
||||
|
||||
return ParseResponse(buffer.ToArray());
|
||||
}
|
||||
|
||||
private AkResponse ParseResponse(byte[] frame)
|
||||
{
|
||||
if (frame.Length < 5)
|
||||
return AkResponse.Error("InvalidFrame");
|
||||
|
||||
if (frame[0] != 0x02 || frame[^1] != 0x03)
|
||||
return AkResponse.Error("BadSTXETX");
|
||||
|
||||
var text = Encoding.ASCII.GetString(frame, 1, frame.Length - 2);
|
||||
var parts = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length < 3)
|
||||
return AkResponse.Error("Malformed");
|
||||
|
||||
var header = parts[0];
|
||||
var warning = parts[1];
|
||||
var prc = parts[2];
|
||||
|
||||
if (header.StartsWith("4???"))
|
||||
return AkResponse.Error("UnrecognizedCommand");
|
||||
|
||||
if (prc == "SE")
|
||||
return AkResponse.Error("SyntaxError");
|
||||
|
||||
if (parts.Length < 4)
|
||||
return AkResponse.Error("MissingValue");
|
||||
|
||||
var value = parts[3];
|
||||
|
||||
return AkResponse.Success(value);
|
||||
}
|
||||
|
||||
private class AkResponse
|
||||
{
|
||||
public bool IsError { get; private set; }
|
||||
public string ErrorType { get; private set; } = "";
|
||||
public string Value { get; private set; } = "";
|
||||
|
||||
public static AkResponse Success(string value) =>
|
||||
new AkResponse { IsError = false, Value = value };
|
||||
|
||||
public static AkResponse Error(string type) =>
|
||||
new AkResponse { IsError = true, ErrorType = type };
|
||||
}
|
||||
|
||||
private static string ToCEscaped(byte[] data)
|
||||
{
|
||||
var sb = new StringBuilder(data.Length * 4);
|
||||
|
||||
foreach (var b in data)
|
||||
{
|
||||
if (b >= 0x20 && b <= 0x7E)
|
||||
{
|
||||
sb.Append((char)b);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append($"\\x{b:X2}");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
192
Udara/SvcApp/Poll/Processor/Mode/AmaProcessor.cs
Normal file
192
Udara/SvcApp/Poll/Processor/Mode/AmaProcessor.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using Udara.Database.App;
|
||||
using Udara.Database.App.Model;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor.Mode;
|
||||
|
||||
public class AmaResult : IDeviceResult
|
||||
{
|
||||
public DeviceListModel Device { get; set; } = default!;
|
||||
public Dictionary<string, string> Parameters { get; set; } = new();
|
||||
}
|
||||
|
||||
public class AmaProcessor : IDeviceProcessor
|
||||
{
|
||||
private readonly ILogger<AmaProcessor> _logger;
|
||||
private readonly AppDbContext _db;
|
||||
private readonly IProcessorResultCollector _collector;
|
||||
|
||||
public AmaProcessor(
|
||||
ILogger<AmaProcessor> logger,
|
||||
IProcessorResultCollector collector,
|
||||
AppDbContext db
|
||||
)
|
||||
{
|
||||
_db = db;
|
||||
_logger = logger;
|
||||
_collector = collector;
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(DeviceListModel device, CancellationToken token)
|
||||
{
|
||||
// Load SQL Server config for this device
|
||||
var cfg = await _db.DeviceSqlServer
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.DeviceId == device.Id, token);
|
||||
if (cfg == null)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceSqlServer entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = await _db.DeviceParameters
|
||||
.AsNoTracking()
|
||||
.Where(x => x.DeviceId == device.Id)
|
||||
.OrderBy(x => x.ParameterId)
|
||||
.ToListAsync(token);
|
||||
if (parameters.Count == 0)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceParameters entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var amaData = parameters.ToDictionary(r => r.ParameterName, _ => "");
|
||||
|
||||
try
|
||||
{
|
||||
// Always connect to master first
|
||||
var masterConnStr = BuildConnectionString(cfg.Host, cfg.DatabaseName, cfg.Username, cfg.Password, cfg.ConnectionOptions);
|
||||
|
||||
string targetDatabase;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cfg.PreferedDatabaseName))
|
||||
{
|
||||
// Manual override
|
||||
targetDatabase = cfg.PreferedDatabaseName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Auto-discovery
|
||||
targetDatabase = await DiscoverAmaDatabaseAsync(device,masterConnStr);
|
||||
}
|
||||
|
||||
if (targetDatabase == null)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No Database available.", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now build connection string to the actual AMA database
|
||||
var amaConnStr = BuildConnectionString(cfg.Host, targetDatabase, cfg.Username, cfg.Password, cfg.ConnectionOptions);
|
||||
|
||||
// Execute the AMA query
|
||||
var rows = await ExecuteAmaQueryAsync(amaConnStr, cfg.Query);
|
||||
|
||||
if (rows.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("DeviceId {DeviceId}: Empty AMA result received", device.Id);
|
||||
}
|
||||
|
||||
foreach( var row in rows)
|
||||
amaData[row.Key] = row.Value;
|
||||
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: Timeout reading from AMA device", device.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "DeviceId {DeviceId}: Error reading AMA device", device.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var result = new AmaResult
|
||||
{
|
||||
Device = device,
|
||||
Parameters = amaData
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
_logger.LogInformation("=== JSON Export ===\n{json}", json);
|
||||
|
||||
await _collector.CollectAsync(device, result, token);
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildConnectionString(string host, string database, string user, string pass, string options)
|
||||
{
|
||||
return $"Data Source={host}\\SQLEXPRESS;Initial Catalog={database};User ID={user};Password={pass};{options}";
|
||||
}
|
||||
|
||||
private async Task<string> DiscoverAmaDatabaseAsync(DeviceListModel device, string masterConnStr)
|
||||
{
|
||||
using var conn = new SqlConnection(masterConnStr);
|
||||
await conn.OpenAsync();
|
||||
|
||||
var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = @"
|
||||
SELECT name
|
||||
FROM sys.databases
|
||||
WHERE name LIKE 'MeasSysDb0504%'
|
||||
ORDER BY name;
|
||||
";
|
||||
|
||||
var matches = new List<string>();
|
||||
|
||||
using (var reader = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (await reader.ReadAsync())
|
||||
matches.Add(reader.GetString(0));
|
||||
}
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No AMA database found matching 'MeasSysDb0504%'", device.Id);
|
||||
return null!;
|
||||
}
|
||||
|
||||
if (matches.Count > 1)
|
||||
{
|
||||
_logger.LogWarning("DeviceId {DeviceId}: Multiple AMA databases found", device.Id);
|
||||
foreach (var m in matches)
|
||||
{
|
||||
_logger.LogWarning("DeviceId {DeviceId}: Database=$ - {m}", device.Id, m);
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the first one
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> ExecuteAmaQueryAsync(string connStr, string query)
|
||||
{
|
||||
using var conn = new SqlConnection(connStr);
|
||||
await conn.OpenAsync();
|
||||
|
||||
var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = query;
|
||||
cmd.CommandType = CommandType.Text;
|
||||
|
||||
var result = new Dictionary<string, string>();
|
||||
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
var name = reader["ChemCompName"].ToString() ?? "";
|
||||
var conc = reader["Concentration"].ToString() ?? "";
|
||||
result[name] = conc;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
136
Udara/SvcApp/Poll/Processor/Mode/CsvProcessor.cs
Normal file
136
Udara/SvcApp/Poll/Processor/Mode/CsvProcessor.cs
Normal file
@ -0,0 +1,136 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
using Udara.Common.Feature.Csv;
|
||||
using Udara.Database.App;
|
||||
using Udara.Database.App.Model;
|
||||
using Udara.Infrastructure.FileSystem;
|
||||
using Udara.Infrastructure.FileTransfer;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor.Mode;
|
||||
|
||||
public class CsvResult : IDeviceResult
|
||||
{
|
||||
public DeviceListModel Device { get; set; } = default!;
|
||||
public Dictionary<string, string> Parameters { get; set; } = new();
|
||||
}
|
||||
|
||||
public class CsvProcessor : IDeviceProcessor
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
private readonly ILogger<CsvProcessor> _logger;
|
||||
private readonly IProcessorResultCollector _collector;
|
||||
private readonly IFileTransfer _fileTransfer;
|
||||
private readonly IFileReader _fileReader;
|
||||
private readonly ICsvParser _csvParser;
|
||||
|
||||
public CsvProcessor(
|
||||
AppDbContext db,
|
||||
ILogger<CsvProcessor> logger,
|
||||
IProcessorResultCollector collector,
|
||||
IFileTransfer fileTransfer,
|
||||
IFileReader fileReader,
|
||||
ICsvParser csvParser
|
||||
)
|
||||
{
|
||||
_db = db;
|
||||
_logger = logger;
|
||||
_collector = collector;
|
||||
_fileTransfer = fileTransfer;
|
||||
_fileReader = fileReader;
|
||||
_csvParser = csvParser;
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(DeviceListModel device, CancellationToken token)
|
||||
{
|
||||
var config = await _db.DeviceDatafile
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.DeviceId == device.Id, token);
|
||||
|
||||
if (config == null)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceDatafile entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = await _db.DeviceParameters
|
||||
.AsNoTracking()
|
||||
.Where(x => x.DeviceId == device.Id)
|
||||
.OrderBy(x => x.ParameterId)
|
||||
.ToListAsync(token);
|
||||
|
||||
if (parameters.Count == 0)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceParameters entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var csvData = parameters.ToDictionary(r => r.ParameterName, _ => "");
|
||||
|
||||
try
|
||||
{
|
||||
var collected = await _fileTransfer.CollectFilesAsync(
|
||||
config.ProcessName,
|
||||
config.TargetDir,
|
||||
config.FilePattern,
|
||||
config.FileExtension,
|
||||
token);
|
||||
|
||||
foreach (var file in collected)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var content = await _fileReader.ReadAllTextAsync(file, token);
|
||||
|
||||
if (string.IsNullOrEmpty(content))
|
||||
continue;
|
||||
|
||||
var parsed = _csvParser.Parse(content);
|
||||
|
||||
if (!(parsed.Header.Length == 0 || parsed.Rows.Count == 0))
|
||||
{
|
||||
var fileData = parsed.Header.Zip(parsed.Rows[0], (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v);
|
||||
|
||||
var header = parsed.Header;
|
||||
var firstRow = parsed.Rows[0];
|
||||
|
||||
foreach (var c in fileData)
|
||||
if (csvData.ContainsKey(c.Key))
|
||||
csvData[c.Key] = c.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("DeviceId {DeviceId}: CSV file {File} has no usable data", device.Id, file);
|
||||
continue;
|
||||
}
|
||||
|
||||
await _fileTransfer.ProcessedFilesAsync(file, token);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: Timeout reading from AIO device", device.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "DeviceId {DeviceId}: Error processing CSV files", device.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var result = new CsvResult
|
||||
{
|
||||
Device = device,
|
||||
Parameters = csvData
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
_logger.LogInformation("=== DeviceId {DeviceId} JSON Export ===\n{json}", device.Id, json);
|
||||
|
||||
await _collector.CollectAsync(device, result, token);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
261
Udara/SvcApp/Poll/Processor/Mode/ModbusProcessor.cs
Normal file
261
Udara/SvcApp/Poll/Processor/Mode/ModbusProcessor.cs
Normal file
@ -0,0 +1,261 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
using Udara.Database.App;
|
||||
using Udara.Database.App.Model;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor.Mode;
|
||||
|
||||
public class ModbusResult : IDeviceResult
|
||||
{
|
||||
public DeviceListModel Device { get; set; } = default!;
|
||||
public Dictionary<string, string> Parameters { get; set; } = new();
|
||||
public Dictionary<string, string> Flags { get; set; } = new();
|
||||
|
||||
}
|
||||
|
||||
public class ModbusCommand
|
||||
{
|
||||
public string ExecutablePath { get; set; } = string.Empty;
|
||||
public List<string> Arguments { get; set; } = new();
|
||||
public string Mode { get; set; } = string.Empty;
|
||||
public string Target { get; set; } = string.Empty;
|
||||
public int? SlaveAddress { get; set; }
|
||||
public int? Reference { get; set; }
|
||||
public int? Count { get; set; }
|
||||
public string? DataType { get; set; }
|
||||
}
|
||||
|
||||
public class ModbusCommandBuilder
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ModbusCommandBuilder(AppDbContext db, ILogger logger)
|
||||
{
|
||||
_db = db;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<List<ModbusCommand>> BuildCommands(int deviceId)
|
||||
{
|
||||
var rows = await _db.DeviceModbus
|
||||
.AsNoTracking()
|
||||
.Where(x => x.DeviceId == deviceId)
|
||||
.ToListAsync();
|
||||
|
||||
var commands = new List<ModbusCommand>();
|
||||
|
||||
foreach (var modbus in rows)
|
||||
{
|
||||
var mode = modbus.Mode?.Trim().ToLowerInvariant();
|
||||
if (mode != "tcp" && mode != "rtu")
|
||||
continue;
|
||||
|
||||
if (!modbus.SlaveAddress.HasValue ||
|
||||
!modbus.Reference.HasValue ||
|
||||
!modbus.Count.HasValue ||
|
||||
string.IsNullOrWhiteSpace(modbus.DataType))
|
||||
continue;
|
||||
|
||||
if (mode == "tcp" && (string.IsNullOrWhiteSpace(modbus.Host) || !modbus.TcpPort.HasValue))
|
||||
continue;
|
||||
|
||||
if (mode == "rtu" &&
|
||||
(string.IsNullOrWhiteSpace(modbus.PortName) ||
|
||||
!modbus.BaudRate.HasValue ||
|
||||
!modbus.DataBits.HasValue ||
|
||||
!modbus.StopBits.HasValue ||
|
||||
string.IsNullOrWhiteSpace(modbus.Parity)))
|
||||
continue;
|
||||
|
||||
var cmd = new ModbusCommand
|
||||
{
|
||||
ExecutablePath = Path.Combine(AppContext.BaseDirectory, "Tools", "mbpoll.exe"),
|
||||
Mode = mode,
|
||||
Target = mode == "tcp"
|
||||
? $"{modbus.Host}:{modbus.TcpPort}"
|
||||
: modbus.PortName!,
|
||||
SlaveAddress = modbus.SlaveAddress,
|
||||
Reference = modbus.Reference,
|
||||
Count = modbus.Count,
|
||||
DataType = modbus.DataType
|
||||
};
|
||||
|
||||
var args = new List<string>
|
||||
{
|
||||
"-q",
|
||||
"-1",
|
||||
"-m", mode,
|
||||
"-a", modbus.SlaveAddress.Value.ToString(),
|
||||
"-r", modbus.Reference.Value.ToString(),
|
||||
"-c", modbus.Count.Value.ToString(),
|
||||
"-t", modbus.DataType
|
||||
};
|
||||
|
||||
if (mode == "tcp")
|
||||
{
|
||||
args.Add("-p");
|
||||
args.Add(modbus.TcpPort!.Value.ToString());
|
||||
args.Add(modbus.Host!);
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Add(modbus.PortName!);
|
||||
args.Add("-b"); args.Add(modbus.BaudRate!.Value.ToString());
|
||||
args.Add("-d"); args.Add(modbus.DataBits!.Value.ToString());
|
||||
args.Add("-s"); args.Add(modbus.StopBits!.Value.ToString());
|
||||
args.Add("-p"); args.Add(modbus.Parity!);
|
||||
}
|
||||
|
||||
cmd.Arguments = args;
|
||||
commands.Add(cmd);
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
public class ModbusProcessor : IDeviceProcessor
|
||||
{
|
||||
private static readonly Regex _valueRegex = new(@"\[(\d+)\]:\s*(.+)", RegexOptions.Compiled);
|
||||
|
||||
private readonly AppDbContext _db;
|
||||
private readonly ILogger<ModbusProcessor> _logger;
|
||||
private readonly IProcessorResultCollector _collector;
|
||||
|
||||
public ModbusProcessor(
|
||||
AppDbContext db,
|
||||
ILogger<ModbusProcessor> logger,
|
||||
IProcessorResultCollector collector)
|
||||
{
|
||||
_db = db;
|
||||
_logger = logger;
|
||||
_collector = collector;
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(DeviceListModel device, CancellationToken token)
|
||||
{
|
||||
var parameters = await _db.DeviceParameters
|
||||
.AsNoTracking()
|
||||
.Where(x => x.DeviceId == device.Id)
|
||||
.OrderBy(x => x.ParameterId)
|
||||
.ToListAsync(token);
|
||||
|
||||
if (parameters.Count == 0)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceRegisters entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var flags = await _db.DeviceFlags
|
||||
.AsNoTracking()
|
||||
.Where(x => x.DeviceId == device.Id)
|
||||
.OrderBy(x => x.FlagId)
|
||||
.ToListAsync(token);
|
||||
|
||||
if (flags.Count == 0)
|
||||
{
|
||||
_logger.LogError("DeviceId {DeviceId}: No DeviceFlags entry found", device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var deviceParameters = parameters.ToDictionary(p => p.ParameterId, p => p.ParameterName);
|
||||
var deviceFlags = flags.ToDictionary(f => f.FlagId, f => f.FlagName);
|
||||
var modbusParameterData = parameters.ToDictionary(p => p.ParameterName, _ => "");
|
||||
var modbusFlagData = flags.ToDictionary(f => f.FlagName, _ => "");
|
||||
|
||||
var builder = new ModbusCommandBuilder(_db, _logger);
|
||||
var commands = await builder.BuildCommands(device.Id);
|
||||
|
||||
foreach (var cmd in commands)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
var isCoil = IsCoilType(cmd.DataType);
|
||||
var isReg = IsRegisterType(cmd.DataType);
|
||||
|
||||
var lines = await RunMbPollAsync(cmd, token);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var m = _valueRegex.Match(line);
|
||||
if (!m.Success)
|
||||
continue;
|
||||
|
||||
var address = int.Parse(m.Groups[1].Value);
|
||||
var value = m.Groups[2].Value.Trim();
|
||||
|
||||
if (isCoil && deviceFlags.TryGetValue(address, out var fname))
|
||||
modbusFlagData[fname] = value;
|
||||
|
||||
if (isReg && deviceParameters.TryGetValue(address, out var pname))
|
||||
modbusParameterData[pname] = value;
|
||||
}
|
||||
await Task.Delay(100, token);
|
||||
}
|
||||
|
||||
var result = new ModbusResult
|
||||
{
|
||||
Device = device,
|
||||
Parameters = modbusParameterData,
|
||||
Flags = modbusFlagData
|
||||
};
|
||||
|
||||
await _collector.CollectAsync(device, result, token);
|
||||
}
|
||||
|
||||
private static bool IsCoilType(string? t)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(t)) return false;
|
||||
t = t.ToLowerInvariant();
|
||||
return t == "0" || t == "1";
|
||||
}
|
||||
|
||||
private static bool IsRegisterType(string? t)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(t)) return false;
|
||||
t = t.ToLowerInvariant();
|
||||
return t.StartsWith("3") || t.StartsWith("4");
|
||||
}
|
||||
|
||||
private async Task<List<string>> RunMbPollAsync(ModbusCommand cmd, CancellationToken token)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = cmd.ExecutablePath,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
foreach (var arg in cmd.Arguments)
|
||||
psi.ArgumentList.Add(arg);
|
||||
|
||||
using var p = new Process { StartInfo = psi };
|
||||
|
||||
p.Start();
|
||||
var output = await p.StandardOutput.ReadToEndAsync(token);
|
||||
var error = await p.StandardError.ReadToEndAsync(token);
|
||||
|
||||
p.WaitForExit();
|
||||
|
||||
foreach (var line in output.Split('\n'))
|
||||
lines.Add(line.Trim());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error running mbpoll");
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
172
Udara/SvcApp/Poll/Processor/ProcessorResultCollector.cs
Normal file
172
Udara/SvcApp/Poll/Processor/ProcessorResultCollector.cs
Normal file
@ -0,0 +1,172 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Udara.Database.App.Model;
|
||||
using Udara.Database.Data;
|
||||
using Udara.Database.Data.Model;
|
||||
using Udara.SvcApp.Poll.Processor.Interface;
|
||||
using Udara.SvcApp.Poll.Processor.Mode;
|
||||
|
||||
namespace Udara.SvcApp.Poll.Processor;
|
||||
|
||||
public class ProcessorResultCollector : IProcessorResultCollector
|
||||
{
|
||||
private readonly ILogger<ProcessorResultCollector> _logger;
|
||||
private readonly DataDbContext _DataDbContext;
|
||||
|
||||
private readonly HashSet<int> _expectedDeviceIds = new();
|
||||
private readonly ConcurrentDictionary<int, DeviceResultSnapshot> _currentBatch = new();
|
||||
|
||||
private readonly object _syncRoot = new();
|
||||
private bool _batchActive;
|
||||
private DateTime _batchTimestamp;
|
||||
|
||||
public ProcessorResultCollector(
|
||||
ILogger<ProcessorResultCollector> logger,
|
||||
DataDbContext DataDbContext
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_DataDbContext = DataDbContext;
|
||||
}
|
||||
|
||||
public void BeginBatch(IReadOnlyCollection<DeviceListModel> devices, DateTime Timestamp)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_expectedDeviceIds.Clear();
|
||||
_currentBatch.Clear();
|
||||
|
||||
foreach (var device in devices)
|
||||
_expectedDeviceIds.Add(device.Id);
|
||||
|
||||
_batchTimestamp = Timestamp;
|
||||
_batchActive = _expectedDeviceIds.Count > 0;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Started new raw data batch for {DeviceCount} devices at {Timestamp}",
|
||||
_expectedDeviceIds.Count,
|
||||
_batchTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CollectAsync(DeviceListModel device, IDeviceResult result, CancellationToken token)
|
||||
{
|
||||
if (!_batchActive)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"CollectAsync called for DeviceId {DeviceId} but no active batch is started",
|
||||
device.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var snapshot = MapToSnapshot(device, result);
|
||||
_currentBatch[device.Id] = snapshot;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Collected result for DeviceId {DeviceId} ({Code})",
|
||||
device.Id,
|
||||
device.Code);
|
||||
|
||||
if (IsBatchComplete())
|
||||
{
|
||||
await PersistCurrentBatchAsync(token);
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceResultSnapshot MapToSnapshot(DeviceListModel device, IDeviceResult result)
|
||||
{
|
||||
var snapshot = new DeviceResultSnapshot
|
||||
{
|
||||
DeviceId = device.Id,
|
||||
Code = device.Code,
|
||||
Name = device.Name,
|
||||
Mode = device.Mode,
|
||||
};
|
||||
|
||||
switch (result)
|
||||
{
|
||||
//SINI PENTING
|
||||
case ModbusResult modbus:
|
||||
snapshot.Flags = modbus.Flags;
|
||||
snapshot.Parameters = modbus.Parameters;
|
||||
break;
|
||||
|
||||
case AioResult aio:
|
||||
snapshot.Parameters = aio.Parameters;
|
||||
break;
|
||||
|
||||
case AmaResult ama:
|
||||
snapshot.Parameters = ama.Parameters;
|
||||
break;
|
||||
|
||||
case AkResult ak:
|
||||
snapshot.Parameters = ak.Parameters;
|
||||
break;
|
||||
|
||||
case CsvResult csv:
|
||||
snapshot.Parameters = csv.Parameters;
|
||||
break;
|
||||
|
||||
default:
|
||||
//snapshot.Values["unknown"] = new Dictionary<string, string>
|
||||
//{
|
||||
// ["type"] = result.GetType().Name
|
||||
//};
|
||||
break;
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private bool IsBatchComplete()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (!_batchActive)
|
||||
return false;
|
||||
|
||||
var complete =
|
||||
_currentBatch.Count == _expectedDeviceIds.Count &&
|
||||
_expectedDeviceIds.All(id => _currentBatch.ContainsKey(id));
|
||||
|
||||
if (complete)
|
||||
_batchActive = false;
|
||||
|
||||
return complete;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PersistCurrentBatchAsync(CancellationToken token)
|
||||
{
|
||||
var batch = new DataLoggingBatch
|
||||
{
|
||||
Devices = _currentBatch.Values
|
||||
.OrderBy(x => x.DeviceId)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(batch,
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
}
|
||||
);
|
||||
|
||||
var entry = new RawDataModel
|
||||
{
|
||||
Timestamp = _batchTimestamp,
|
||||
Log = json
|
||||
};
|
||||
|
||||
_DataDbContext.Database.EnsureCreated();
|
||||
_DataDbContext.RawData.Add(entry);
|
||||
await _DataDbContext.SaveChangesAsync(token);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Persisted raw data batch with {DeviceCount} devices at {Timestamp}",
|
||||
batch.Devices.Count,
|
||||
_batchTimestamp);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user