초기 커밋.
This commit is contained in:
28
XRServer.sln
Normal file
28
XRServer.sln
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35707.178 d17.12
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XRServer", "XRServer\XRServer.csproj", "{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Debug|x86.Build.0 = Debug|x86
|
||||
{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Release|x86.ActiveCfg = Release|x86
|
||||
{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
9
XRServer/App.xaml
Normal file
9
XRServer/App.xaml
Normal file
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="XRServer.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:XRServer"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
14
XRServer/App.xaml.cs
Normal file
14
XRServer/App.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Windows;
|
||||
|
||||
namespace XRServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
10
XRServer/AssemblyInfo.cs
Normal file
10
XRServer/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
151
XRServer/BaseInfoHandler.cs
Normal file
151
XRServer/BaseInfoHandler.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using uhttpsharp;
|
||||
using uhttpsharp.Headers;
|
||||
using utils;
|
||||
|
||||
namespace XRServer
|
||||
{
|
||||
/// <summary>
|
||||
/// /baseinfo 요청을 처리하는 핸들러
|
||||
/// </summary>
|
||||
public class BaseInfoHandler : IHttpRequestHandler
|
||||
{
|
||||
|
||||
private SQLiteService sqliteService;
|
||||
public BaseInfoHandler(SQLiteService sqliteService)
|
||||
{
|
||||
this.sqliteService = sqliteService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// /baseinfo 요청을 처리하는 메서드
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Handle(IHttpContext context, Func<Task> next)
|
||||
{
|
||||
// /baseinfo/분:초(mm:ss format) 문자열을 받아 해당 시간에 대한 SelectBySecondBaseInfo 데이터 반환
|
||||
string url = context.Request.Uri.OriginalString;
|
||||
Logger.Debug($"BaseInfoHandler: {url} 요청 처리 중...");
|
||||
|
||||
//url에 mm:ss 형식이 포함되어 있는지 확인
|
||||
Match match = Regex.Match(url, @"([0-5][0-9]):([0-5][0-9])$");
|
||||
if (match.Success)
|
||||
{
|
||||
// mm:ss 형식이 맞으면 URL에서 mm:ss 부분 추출
|
||||
string timePart = url.Substring(match.Index, 5); // URL에서 mm:ss 부분 추출
|
||||
|
||||
// 데이터 조회
|
||||
List<SQLiteDataEntity> data = await sqliteService.SelectBySecondBaseInfo(timePart);
|
||||
// 데이터가 null이 아니고, 데이터가 있는지 확인
|
||||
if (data != null && data.Count > 0)
|
||||
{
|
||||
string jsonData = data[0].data;
|
||||
Logger.Debug($"BaseInfoHandler: {data.Count}개의 데이터 조회됨. 요청 시간: {timePart}");
|
||||
|
||||
if (url.StartsWith("/baseinfo/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var json = data.Count > 0 ? jsonData : "{}";
|
||||
|
||||
//Logger.Debug($"BaseInfoHandler: 조회된 데이터: {json}");
|
||||
// gzip 압축
|
||||
byte[] compressedBytes = GzipCompress(json);
|
||||
|
||||
var headers = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("Content-Type", "application/json; charset=utf-8"),
|
||||
new KeyValuePair<string, string>("Content-Encoding", "gzip"),
|
||||
new KeyValuePair<string, string>("Content-Length", compressedBytes.Length.ToString())
|
||||
};
|
||||
|
||||
context.Response = new HttpResponse(
|
||||
HttpResponseCode.Ok,
|
||||
"application/json; charset=utf-8",
|
||||
new MemoryStream(compressedBytes),
|
||||
context.Request.Headers.KeepAliveConnection(),
|
||||
headers
|
||||
);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
JObject? jsonObject = JsonConvert.DeserializeObject<JObject>(jsonData);
|
||||
if (jsonObject != null)
|
||||
{
|
||||
foreach (var property in jsonObject.Properties())
|
||||
{
|
||||
if (url.StartsWith($"/{property.Name}", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Debug($"BaseInfoHandler: {property.Name}:");
|
||||
// 속성 값을 문자열로 직렬화
|
||||
string propertyValue = property.Value.ToString(Formatting.None);
|
||||
// gzip 압축
|
||||
byte[] compressedBytes = GzipCompress(propertyValue);
|
||||
|
||||
var headers = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("Content-Type", "application/json; charset=utf-8"),
|
||||
new KeyValuePair<string, string>("Content-Encoding", "gzip"),
|
||||
new KeyValuePair<string, string>("Content-Length", compressedBytes.Length.ToString())
|
||||
};
|
||||
context.Response = new HttpResponse(
|
||||
HttpResponseCode.Ok,
|
||||
"application/json; charset=utf-8",
|
||||
new MemoryStream(compressedBytes),
|
||||
context.Request.Headers.KeepAliveConnection(),
|
||||
headers
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// JSON 데이터가 비어있는 경우
|
||||
context.Response = new HttpResponse(HttpResponseCode.NoContent, "No Content", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // 데이터가 없는 경우
|
||||
context.Response = new HttpResponse(HttpResponseCode.NotFound, "No data found for the specified time.", false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response = new HttpResponse(HttpResponseCode.BadRequest, "Invalid time format. Use mm:ss.", false);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GzipCompress(string content)
|
||||
{
|
||||
byte[] inputBytes = Encoding.UTF8.GetBytes(content);
|
||||
using (var outputStream = new MemoryStream())
|
||||
{
|
||||
using (var gzipStream = new GZipStream(outputStream, CompressionLevel.Optimal, true))
|
||||
{
|
||||
gzipStream.Write(inputBytes, 0, inputBytes.Length);
|
||||
}
|
||||
return outputStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private MemoryStream StringToStream(string content)
|
||||
{
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
StreamWriter streamWriter = new StreamWriter(memoryStream);
|
||||
streamWriter.Write(content);
|
||||
streamWriter.Flush();
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
117
XRServer/DateTimeUtil.cs
Normal file
117
XRServer/DateTimeUtil.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XRServer
|
||||
{
|
||||
public class DateTimeUtil
|
||||
{
|
||||
//헝가리 서버에서 NQTT 시간이 UTC 시간으로 전송되서 Greenwich Standard Time 로 변경
|
||||
private static TimeZoneInfo hungaryInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time");
|
||||
private static TimeZoneInfo koreadInfo = TimeZoneInfo.FindSystemTimeZoneById("Korea Standard Time");//korea
|
||||
|
||||
/// <summary>
|
||||
/// 헝가리 시간
|
||||
/// </summary>
|
||||
public static DateTime HungaryNow { get => TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.Local, hungaryInfo); }
|
||||
|
||||
public static string HungaryNowString { get => FormatTime(HungaryNow); }
|
||||
|
||||
public static DateTime UtcNow { get => DateTime.UtcNow; }
|
||||
public static string UtcNowString { get => FormatTime(UtcNow); }
|
||||
|
||||
|
||||
private static TimeSpan utcHungaryGap = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// hungary - utc 시간 차이
|
||||
/// </summary>
|
||||
public static TimeSpan UtcHungaryGap { get => (utcHungaryGap == TimeSpan.Zero) ? ToHungaryDateTime(DateTime.UtcNow) - DateTime.UtcNow : utcHungaryGap; }
|
||||
|
||||
/// <summary>
|
||||
/// UTC string을 UTC DateTime으로 변환. yyyy-MM-ddTHH:mm:ss.fffZ 포맷이라 제대로 변환 됨
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime UtcParse(string s) { return DateTime.Parse(s).ToUniversalTime(); }
|
||||
|
||||
/// <summary>
|
||||
/// string을 DateTime으로 변환
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime Parse(string s, string format) { return DateTime.ParseExact(s, format, CultureInfo.InvariantCulture); }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UTC DateTime을 헝가리 DateTime으로 변환
|
||||
/// </summary>
|
||||
/// <param name="utcDateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ToHungaryDateTime(DateTime utcDateTime)
|
||||
{
|
||||
return TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, hungaryInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UTC string을 헝가리 DateTime으로 변환
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime UtcStringToHungaryDateTime(string s)
|
||||
{
|
||||
return ToHungaryDateTime(UtcParse(s));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UTC string을 헝가리 Time String으로 변환
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
public static string UtcStringToHungaryTimeString(string s)
|
||||
{
|
||||
return FormatTime(UtcStringToHungaryDateTime(s));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UTC DateTime을 한국 DateTime으로 변환
|
||||
/// </summary>
|
||||
/// <param name="utcDateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ToKoreaDateTime(DateTime utcDateTime)
|
||||
{
|
||||
return TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, koreadInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UTC string을 한국 DateTime으로 변환
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime UtcStringToKoreaDateTime(string s)
|
||||
{
|
||||
return ToKoreaDateTime(UtcParse(s));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UTC string을 한국 Time String으로 변환
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
public static string UtcStringToKoreaTimeString(string s)
|
||||
{
|
||||
return FormatTime(UtcStringToKoreaDateTime(s));
|
||||
}
|
||||
|
||||
public static string FormatTime(DateTime dateTime)
|
||||
{
|
||||
return dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
|
||||
}
|
||||
}
|
||||
}
|
||||
243
XRServer/INIFile.cs
Normal file
243
XRServer/INIFile.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace XRServer
|
||||
{
|
||||
/// <summary>
|
||||
/// INIFile
|
||||
/// Window INI 파일을 다루기 위한 클레스
|
||||
///
|
||||
/// // 사용법 예
|
||||
/// String path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, System.Diagnostics.Process.GetCurrentProcess().ProcessName + @".ini");
|
||||
/// FileInfo f = new FileInfo(path);
|
||||
/// if (!f.Exists)
|
||||
/// {
|
||||
/// path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Application.Current.MainWindow.GetType().Assembly.GetName().Name + @".ini");
|
||||
/// }
|
||||
/// INIFile ini = new INIFile(path);
|
||||
/// Config.UPDATE_FILE = ini.GetString("CONFIG", "UPDATE_FILE");
|
||||
/// Config.IS_TEST_TOP = ini.GetBoolean("DEBUG", "IS_TEST_TOP", false);
|
||||
///
|
||||
/// // ini 내부
|
||||
/// [CONFIG]
|
||||
/// UPDATE_URL = http://app.stickint.com/samsung/check_update.do
|
||||
/// LOG_URL = http://app.stickint.com/samsung/upload_log.do
|
||||
///
|
||||
/// [DEBUG]
|
||||
/// IS_TEST_TOP = false
|
||||
///
|
||||
/// </summary>
|
||||
public class INIFile
|
||||
{
|
||||
|
||||
//*********************************************************************************************************************
|
||||
//
|
||||
// properties
|
||||
//
|
||||
//*********************************************************************************************************************
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ini 파일명을 저장
|
||||
/// </summary>
|
||||
private string INIFileName;
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ini 파일을 지정하거나 가져올때 쓰는 속성
|
||||
/// </summary>
|
||||
public string FileName
|
||||
{
|
||||
get { return INIFileName; }
|
||||
set { INIFileName = value; }
|
||||
}
|
||||
|
||||
//*********************************************************************************************************************
|
||||
//
|
||||
// construct
|
||||
//
|
||||
//*********************************************************************************************************************
|
||||
/// <summary>
|
||||
/// 생성자 : 사용할 ini 파일을 지정
|
||||
/// </summary>
|
||||
/// <param name="FileName">사용할 파일명</param>
|
||||
public INIFile(string FileName)
|
||||
{
|
||||
INIFileName = FileName;
|
||||
}
|
||||
|
||||
//*********************************************************************************************************************
|
||||
//
|
||||
// methods
|
||||
//
|
||||
//*********************************************************************************************************************
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ini 파일에서 정보를 가져오기 위한 API 기초 함수
|
||||
/// </summary>
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern int GetPrivateProfileString(
|
||||
string section,
|
||||
string key,
|
||||
string def,
|
||||
StringBuilder retVal,
|
||||
int size,
|
||||
string filePath);
|
||||
/// <summary>
|
||||
/// ini 파일에서 정보를 쓰기위한 위한 API 기초 함수
|
||||
/// </summary>
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern long WritePrivateProfileString(
|
||||
string section,
|
||||
string key,
|
||||
string val,
|
||||
string filePath);
|
||||
/// <summary>
|
||||
/// ini 파일에 정보를 기록하기 위한 함수
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명</param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <param name="Value">기록할 값</param>
|
||||
private void _IniWriteValue(string Section, string Key, string Value)
|
||||
{
|
||||
WritePrivateProfileString(Section, Key, Value, INIFileName);
|
||||
}
|
||||
/// <summary>
|
||||
/// ini 파일에 정보를 가져오기 위한 함수
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명</param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <returns>가져온 값</returns>
|
||||
private string _IniReadValue(string Section, string Key)
|
||||
{
|
||||
StringBuilder temp = new StringBuilder(2000);
|
||||
int i = GetPrivateProfileString(Section, Key, "", temp, 2000, INIFileName);
|
||||
return temp.ToString().Trim();
|
||||
}
|
||||
|
||||
|
||||
//*********************************************************************************************************************
|
||||
//
|
||||
// public method
|
||||
//
|
||||
//*********************************************************************************************************************
|
||||
|
||||
/// <summary>
|
||||
/// 문자열 타입으로 값을 기록한다
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명</param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <param name="Value">기록 할 문자열</param>
|
||||
public void SetString(string Section, string Key, string Value)
|
||||
{
|
||||
_IniWriteValue(Section, Key, Value.Trim());
|
||||
}
|
||||
/// <summary>
|
||||
/// 정수 타입으로 값을 기록한다
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명 </param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <param name="Value">기록 할 정수값</param>
|
||||
///
|
||||
public void SetInteger(string Section, string Key, int Value)
|
||||
{
|
||||
_IniWriteValue(Section, Key, Value.ToString().Trim());
|
||||
}
|
||||
/// <summary>
|
||||
/// 소수 타입으로 값을 기록한다
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명 </param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <param name="Value">기록 할 소수값</param>
|
||||
///
|
||||
public void SetNumber(string Section, string Key, float Value)
|
||||
{
|
||||
_IniWriteValue(Section, Key, Value.ToString().Trim());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 논리 타입으로 값을 기록 한다.
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명</param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <param name="Value">기록 할 논리 값</param>
|
||||
public void SetBoolean(string Section, string Key, bool Value)
|
||||
{
|
||||
_IniWriteValue(Section, Key, Value ? "1" : "0");
|
||||
}
|
||||
/// <summary>
|
||||
/// 논리 타입으로 값을 가져온다
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명</param>
|
||||
/// <param name="Key">키 값</param>
|
||||
/// <param name="def">기본값</param>
|
||||
/// <returns>가져온 논리값</returns>
|
||||
public bool GetBoolean(string Section, string Key, bool def)
|
||||
{
|
||||
bool temp = def;
|
||||
string stTemp = _IniReadValue(Section, Key);
|
||||
if (stTemp == "") return def;
|
||||
if (stTemp.Trim() == "true" || stTemp.Trim() == "True" || stTemp.Trim() == "1") return true;
|
||||
else return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// 문자열로 값을 가져 온다
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명</param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <returns>가져온 문자열</returns>
|
||||
public string GetString(string Section, string Key)
|
||||
{
|
||||
return _IniReadValue(Section, Key).Trim();
|
||||
}
|
||||
/// <summary>
|
||||
/// 정수 타입으로 값을 가져 온다
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명</param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <param name="def">기본값</param>
|
||||
/// <returns>가져온 정수값</returns>
|
||||
public int GetInteger(string Section, string Key, int def)
|
||||
{
|
||||
int temp = def;
|
||||
string stTemp = _IniReadValue(Section, Key);
|
||||
if (stTemp == "") return def;
|
||||
try
|
||||
{
|
||||
temp = int.Parse(stTemp.Trim());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소수 타입으로 값을 가져 온다
|
||||
/// </summary>
|
||||
/// <param name="Section">섹션명</param>
|
||||
/// <param name="Key">키 명</param>
|
||||
/// <param name="def">기본값</param>
|
||||
/// <returns>가져온 소수값</returns>
|
||||
public float GetNumber(string Section, string Key, float def)
|
||||
{
|
||||
float temp = def;
|
||||
string stTemp = _IniReadValue(Section, Key);
|
||||
if (stTemp == "") return def;
|
||||
try
|
||||
{
|
||||
temp = float.Parse(stTemp.Trim());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
42
XRServer/Log4net.config
Normal file
42
XRServer/Log4net.config
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<log4net>
|
||||
<root>
|
||||
<level value="ALL"/>
|
||||
<appender-ref ref="DebugAppender" />
|
||||
<appender-ref ref="RollingFile" />
|
||||
<appender-ref ref="FatalRollingFile" />
|
||||
</root>
|
||||
<appender name="DebugAppender" type="log4net.Appender.DebugAppender">
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="%date [%thread]] %-5level %message%newline" />
|
||||
</layout>
|
||||
</appender>
|
||||
<!-- 지정한 Folder에 Log 파일을 기록 -->
|
||||
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
|
||||
<file value="log\" />
|
||||
<datepattern value="yyyy\\MM\\yyyy-MM-dd'.log'"/>
|
||||
<appendToFile value="true" />
|
||||
<rollingStyle value="Date" />
|
||||
<staticLogFileName value="false" />
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="[%date][Thread : %thread][%level] %message%newline" />
|
||||
</layout>
|
||||
</appender>
|
||||
<appender name="FatalRollingFile" type="log4net.Appender.RollingFileAppender">
|
||||
<file value="log\fatal.log" />
|
||||
<appendToFile value="true" />
|
||||
<rollingStyle value="Size" />
|
||||
<maxSizeRollBackups value="500" />
|
||||
<maximumFileSize value="10MB" />
|
||||
<staticLogFileName value="true" />
|
||||
<filter type="log4net.Filter.LevelRangeFilter">
|
||||
<param name="LevelMin" value="FATAL" />
|
||||
<param name="LevelMax" value="FATAL" />
|
||||
</filter>
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="[%date][Thread : %thread][%level] %message%newline" />
|
||||
</layout>
|
||||
</appender>
|
||||
</log4net>
|
||||
</configuration>
|
||||
153
XRServer/Logger.cs
Normal file
153
XRServer/Logger.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using log4net;
|
||||
using log4net.Config;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
public class Logger
|
||||
{
|
||||
|
||||
//private static readonly log4net.ILog logger = log4net.LogManager.GetLogger("Logger");
|
||||
private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
static Logger()
|
||||
{
|
||||
try
|
||||
{
|
||||
//log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo("log4netSqlite.config"));
|
||||
string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log4net.config");
|
||||
FileInfo configFile = new FileInfo(configFilePath);
|
||||
if (configFile.Exists)
|
||||
{
|
||||
XmlConfigurator.ConfigureAndWatch(configFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FileNotFoundException("log4net configuration file not found", configFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error configuring log4net: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void Info(string msg)
|
||||
{
|
||||
|
||||
StackFrame frame = (new StackTrace(true)).GetFrame(1);
|
||||
var method = frame.GetMethod();
|
||||
var type = method.DeclaringType;
|
||||
var name = method.Name;
|
||||
|
||||
int lineNumber = frame.GetFileLineNumber();
|
||||
|
||||
if (logger.IsInfoEnabled) Log(logType.Info, type.Name + "." + name + " [line " + lineNumber + "] " + msg);
|
||||
}
|
||||
|
||||
public static void Debug(string msg)
|
||||
{
|
||||
StackFrame frame = (new StackTrace(true)).GetFrame(1);
|
||||
var method = frame.GetMethod();
|
||||
var type = method.DeclaringType;
|
||||
var name = method.Name;
|
||||
int lineNumber = frame.GetFileLineNumber();
|
||||
|
||||
if (logger.IsDebugEnabled) Log(logType.Debug, $"{(type.FullName == null ? type.Name : type.FullName)}.{name} [line {lineNumber}] {msg}");
|
||||
}
|
||||
|
||||
|
||||
public static void Warning(string msg, Exception ex)
|
||||
{
|
||||
StackFrame frame = (new StackTrace(true)).GetFrame(1);
|
||||
var method = frame.GetMethod();
|
||||
var type = method.DeclaringType;
|
||||
var name = method.Name;
|
||||
int lineNumber = frame.GetFileLineNumber();
|
||||
|
||||
if (logger.IsWarnEnabled) Log(logType.Warning, type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex: ex);
|
||||
}
|
||||
|
||||
public static void Error(string msg, Exception ex)
|
||||
{
|
||||
StackFrame frame = (new StackTrace(true)).GetFrame(1);
|
||||
var method = frame.GetMethod();
|
||||
var type = method.DeclaringType;
|
||||
var name = method.Name;
|
||||
int lineNumber = frame.GetFileLineNumber();
|
||||
|
||||
if (logger.IsErrorEnabled) Log(logType.Error, type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex: ex);
|
||||
}
|
||||
|
||||
public static void Fatal(string msg, Exception ex)
|
||||
{
|
||||
StackFrame frame = (new StackTrace(true)).GetFrame(1);
|
||||
var method = frame.GetMethod();
|
||||
var type = method.DeclaringType;
|
||||
var name = method.Name;
|
||||
int lineNumber = frame.GetFileLineNumber();
|
||||
|
||||
if (logger.IsFatalEnabled) Log(logType.Fatal, type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex: ex);
|
||||
}
|
||||
|
||||
// log4net 설정 파일 로드 App.xml.cs OnStartup
|
||||
//XmlConfigurator.Configure(new System.IO.FileInfo("log4net.config"));
|
||||
//
|
||||
//각 클래스 마다
|
||||
//static ILog Logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
||||
//
|
||||
//private static ILog? _log;
|
||||
//private static ILog log
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// if (_log == null)
|
||||
// {
|
||||
// _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
||||
// }
|
||||
// return _log;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
private enum logType : ushort
|
||||
{
|
||||
Debug = 0x10,
|
||||
Info = 0x01,
|
||||
Warning = 0x02,
|
||||
Error = 0x04,
|
||||
Fatal = 0x08
|
||||
}
|
||||
private static Dictionary<logType, Action<string, Exception>> logAction =
|
||||
new Dictionary<logType, Action<string, Exception>>()
|
||||
{
|
||||
{ logType.Debug, (msg,info) => logger.Debug(msg)},
|
||||
{ logType.Info, (msg,info) => logger.Info(msg)},
|
||||
{ logType.Warning, (info,ex) => logger.Warn(info,ex)},
|
||||
{ logType.Error, (info,ex) => logger.Error(info,ex)},
|
||||
{ logType.Fatal, (info,ex) => logger.Fatal(info,ex)},
|
||||
};
|
||||
|
||||
private static void Log(logType type, string msg = default(string), string info = default(string), Exception ex = default(Exception))
|
||||
{
|
||||
if ((logType.Error | logType.Warning | logType.Fatal).HasFlag(type))
|
||||
//logAction[type](info, ex);
|
||||
ThreadPool.QueueUserWorkItem(tmp => logAction[type](info, ex));
|
||||
else
|
||||
//logAction[type](msg, null);
|
||||
ThreadPool.QueueUserWorkItem(tmp => logAction[type](msg, null));
|
||||
}
|
||||
|
||||
//static public void Debug(string msg) => Log(logType.Debug, msg);
|
||||
//static public void Info(string msg) => Log(logType.Info, msg);
|
||||
//static public void Warning(string info, Exception ex) => Log(logType.Warning, info, ex: ex);
|
||||
//static public void Error(string info, Exception ex) => Log(logType.Error, info, ex: ex);
|
||||
//static public void Fatal(string info, Exception ex) => Log(logType.Fatal, info, ex: ex);
|
||||
|
||||
}
|
||||
}
|
||||
214
XRServer/MQTTService.cs
Normal file
214
XRServer/MQTTService.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using MQTTnet;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Buffers;
|
||||
using utils;
|
||||
|
||||
namespace XRServer
|
||||
{
|
||||
/// <summary>
|
||||
/// MQTT 서버 초기화
|
||||
/// mqttPort 포트에서 실행되며, newXR 토픽에 매 1초당 SQliteSevice의 SelectBySecond 메서드로 데이터를 가져와서 메세지 발송
|
||||
/// 클라이언트가 시작 시간(00:00~59:59)을 지정할 수 있도록 함
|
||||
/// 시작 시간이 지정되지 않은 경우 기본값은 00:00
|
||||
/// 시작 시간부터 1초씩 증가하며, 59:59 이후에는 59:59 데이터를 계속 발송
|
||||
/// </summary>
|
||||
public class MQTTService
|
||||
{
|
||||
private MqttServerFactory mqttServerFactory = new MqttServerFactory();
|
||||
private MqttServer mqttServer = null;
|
||||
private System.Timers.Timer timer;
|
||||
private int mqttPort = 1883;
|
||||
private string topic = "newXR";
|
||||
private string startTimeTopic = "startTime";
|
||||
|
||||
private SQLiteService sqliteService;
|
||||
public MQTTService(SQLiteService sqliteService)
|
||||
{
|
||||
this.sqliteService = sqliteService;
|
||||
}
|
||||
|
||||
public void SetConfig(int mqttPort = 1883, string topic = "newXR", string startTimeTopic = "startTime")
|
||||
{
|
||||
this.mqttPort = mqttPort;
|
||||
this.topic = topic;
|
||||
this.startTimeTopic = startTimeTopic;
|
||||
}
|
||||
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// MQTT 서버 옵션 설정
|
||||
var optionsBuilder = new MqttServerOptionsBuilder()
|
||||
.WithDefaultEndpoint()
|
||||
.WithDefaultEndpointPort(mqttPort);
|
||||
|
||||
// MQTT 서버 생성
|
||||
mqttServer = mqttServerFactory.CreateMqttServer(optionsBuilder.Build());
|
||||
|
||||
// 클라이언트의 현재 재생 시간을 저장할 딕셔너리 (클라이언트ID -> 현재 재생 시간)
|
||||
var clientPlaybackTimes = new Dictionary<string, TimeSpan>();
|
||||
|
||||
// 클라이언트의 마지막 업데이트 시간을 저장하는 딕셔너리 (클라이언트ID -> DateTime.Now)
|
||||
var clientLastUpdates = new Dictionary<string, DateTime>();
|
||||
|
||||
// 클라이언트 연결 이벤트 처리
|
||||
mqttServer.ClientConnectedAsync += e =>
|
||||
{
|
||||
Logger.Debug($"클라이언트 연결됨: {e.ClientId}");
|
||||
// 클라이언트 연결 시 기본 시작 시간 설정 (00:00)
|
||||
clientPlaybackTimes[e.ClientId] = TimeSpan.Zero; // 00:00
|
||||
clientLastUpdates[e.ClientId] = DateTime.Now;
|
||||
Logger.Debug($"클라이언트 {e.ClientId}의 기본 시작 시간 설정: 00:00");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
// 클라이언트 메시지 수신 이벤트 처리
|
||||
mqttServer.InterceptingPublishAsync += async e =>
|
||||
{
|
||||
// 클라이언트가 "startTime" 토픽으로 시작 시간을 지정할 경우
|
||||
if (e.ApplicationMessage.Topic == startTimeTopic)
|
||||
{
|
||||
var startTimePayload = System.Text.Encoding.UTF8.GetString(e.ApplicationMessage.Payload.ToArray());
|
||||
|
||||
// 시간 형식 검증 (00:00~59:59)
|
||||
if (TimeSpan.TryParseExact(startTimePayload, @"mm\:ss", null, out TimeSpan startTimeSpan))
|
||||
{
|
||||
clientPlaybackTimes[e.ClientId] = startTimeSpan;
|
||||
clientLastUpdates[e.ClientId] = DateTime.Now;
|
||||
Logger.Debug($"클라이언트 {e.ClientId}의 시작 시간 설정: {startTimePayload}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug($"클라이언트 {e.ClientId}가 잘못된 시간 형식 전송: {startTimePayload}");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 클라이언트 연결 해제 이벤트 처리
|
||||
mqttServer.ClientDisconnectedAsync += e =>
|
||||
{
|
||||
clientPlaybackTimes.Remove(e.ClientId);
|
||||
clientLastUpdates.Remove(e.ClientId);
|
||||
Logger.Debug($"클라이언트 연결 해제: {e.ClientId}");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 1초마다 데이터 발행을 위한 타이머 설정
|
||||
timer = new System.Timers.Timer(1000);
|
||||
timer.Elapsed += async (sender, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 각 클라이언트에 대해 현재 재생 시간에 맞는 데이터 전송
|
||||
foreach (var clientEntry in new Dictionary<string, TimeSpan>(clientPlaybackTimes))
|
||||
{
|
||||
string clientId = clientEntry.Key;
|
||||
|
||||
// 마지막 업데이트 시간과 현재 시간의 차이 계산 (초 단위)
|
||||
DateTime now = DateTime.Now;
|
||||
TimeSpan elapsedSinceUpdate = now - clientLastUpdates[clientId];
|
||||
|
||||
// 1초 이상 지났으면 클라이언트 재생 시간 업데이트
|
||||
if (elapsedSinceUpdate.TotalSeconds >= 1)
|
||||
{
|
||||
// 재생 시간 업데이트 (1초 증가)
|
||||
clientPlaybackTimes[clientId] += TimeSpan.FromSeconds(1);
|
||||
clientLastUpdates[clientId] = now;
|
||||
|
||||
// 59:59를 넘어가면 59:59로 고정
|
||||
if (clientPlaybackTimes[clientId] > TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)))
|
||||
{
|
||||
clientPlaybackTimes[clientId] = TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59));
|
||||
}
|
||||
|
||||
// 현재 재생 시간으로 mm:ss 형식 문자열 생성
|
||||
string currentPlaybackTime = clientPlaybackTimes[clientId].ToString(@"mm\:ss");
|
||||
|
||||
// SQLiteService를 통해 데이터 가져오기 (현재 재생 시간의 데이터, 1초 분량)
|
||||
List<SQLiteDataEntity> data = await sqliteService.SelectBySecond(currentPlaybackTime, 1);
|
||||
|
||||
if (data != null && data.Count > 0)
|
||||
{
|
||||
//json 변환
|
||||
JObject? jsonObject = JsonConvert.DeserializeObject<JObject>(data[0].data);
|
||||
if (jsonObject != null)
|
||||
{
|
||||
string topicString = "";
|
||||
//Dictionary의 모든 키와 값을 키를 topic 값을 payload로 발송
|
||||
foreach (var property in jsonObject.Properties())
|
||||
{
|
||||
topicString += property.Name + ", ";
|
||||
// 속성 값을 문자열로 직렬화
|
||||
string propertyValue = property.Value.ToString(Formatting.None);
|
||||
// 클라이언트별 MQTT 메시지 생성
|
||||
var message = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(property.Name)
|
||||
.WithPayload(propertyValue)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.WithRetainFlag(false)
|
||||
.Build();
|
||||
|
||||
// 메시지 발송
|
||||
await mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(message));
|
||||
}
|
||||
Logger.Debug($"클라이언트 {clientId}에 MQTT 메시지 발송: 1개 항목, 토픽: {topicString} 재생시간: {currentPlaybackTime}");
|
||||
}
|
||||
|
||||
var messageAll = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topic)
|
||||
.WithPayload(data[0].data)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.WithRetainFlag(false)
|
||||
.Build();
|
||||
Logger.Debug($"클라이언트 {clientId}에 MQTT 메시지 발송: 1개 항목, 토픽: {topic} 재생시간: {currentPlaybackTime}");
|
||||
|
||||
// 메시지 발송
|
||||
await mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(messageAll));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug($"클라이언트 {clientId}의 재생시간 {currentPlaybackTime}에 해당하는 데이터가 없습니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug($"MQTT 메시지 발송 중 오류 발생: {ex.Message}");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
if (mqttServer != null)
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
timer.Start();
|
||||
Logger.Debug($"MQTT 서버가 시작되었습니다.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("MQTT 서버가 초기화되지 않았습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Stop()
|
||||
{
|
||||
timer?.Stop();
|
||||
if (mqttServer != null)
|
||||
{
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
Logger.Debug("MQTT 서버가 중지되었습니다.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
55
XRServer/MainWindow.xaml
Normal file
55
XRServer/MainWindow.xaml
Normal file
@@ -0,0 +1,55 @@
|
||||
<Window x:Class="XRServer.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:XRServer"
|
||||
mc:Ignorable="d"
|
||||
Title="XR Server" Height="350" Width="840" Closing="Window_Closing" Loaded="Window_Loaded">
|
||||
<Grid>
|
||||
<Button x:Name="startBtn" Content="Start" HorizontalAlignment="Left" Margin="20,222,0,0" VerticalAlignment="Top" Width="247" Height="35" Click="startBtn_Click"/>
|
||||
<Label Content="HTTP Port" HorizontalAlignment="Left" Margin="20,74,0,0" VerticalAlignment="Top"/>
|
||||
<TextBox x:Name="httpPortTxt" HorizontalAlignment="Left" Margin="147,80,0,0" TextWrapping="Wrap" Text="8888" VerticalAlignment="Top" Width="120" Height="20" TextChanged="httpPortTxt_TextChanged"/>
|
||||
<Label Content="MQTT Port" HorizontalAlignment="Left" Margin="20,105,0,0" VerticalAlignment="Top"/>
|
||||
<TextBox x:Name="mqttPortTxt" HorizontalAlignment="Left" Margin="147,108,0,0" TextWrapping="Wrap" Text="1883" VerticalAlignment="Top" Width="120" Height="20" TextChanged="mqttPortTxt_TextChanged"/>
|
||||
<Label Content="MQTT Topic" HorizontalAlignment="Left" Margin="20,135,0,0" VerticalAlignment="Top"/>
|
||||
<TextBox x:Name="mqttTopicTxt" HorizontalAlignment="Left" Margin="147,138,0,0" TextWrapping="Wrap" Text="newXR" VerticalAlignment="Top" Width="120" Height="20" TextChanged="mqttPortTxt_TextChanged"/>
|
||||
<Label Content="MQTT Start Topic" HorizontalAlignment="Left" Margin="20,165,0,0" VerticalAlignment="Top"/>
|
||||
<TextBox x:Name="mqttTopicTxt2" HorizontalAlignment="Left" Margin="147,168,0,0" TextWrapping="Wrap" Text="startTime" VerticalAlignment="Top" Width="120" Height="20" TextChanged="mqttPortTxt_TextChanged"/>
|
||||
<Label Content="DB File" HorizontalAlignment="Left" Margin="20,20,0,0" VerticalAlignment="Top"/>
|
||||
<TextBox x:Name="dbPathTxt" HorizontalAlignment="Left" Margin="20,43,0,0" VerticalAlignment="Top" TextWrapping="Wrap" Text="" Width="180" Height="20" IsReadOnly="True"/>
|
||||
<Button x:Name="dbFileLoadBtn" Content="설정" HorizontalAlignment="Left" Margin="212,43,0,0" VerticalAlignment="Top" Width="55" Height="20" Click="dbFileLoadBtn_Click"/>
|
||||
<ScrollViewer HorizontalAlignment="Left" Margin="300,43,0,0" VerticalAlignment="Top" Width="250" Height="210">
|
||||
<RichTextBox x:Name="httpLinkTxtBlock" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
IsReadOnly="True" IsDocumentEnabled="True">
|
||||
<FlowDocument>
|
||||
<Paragraph x:Name="httpPara">
|
||||
<Run Text="BaseInfo API"/>
|
||||
<LineBreak />
|
||||
<Run Text="데이터 요청 mm:ss(분:초)"/>
|
||||
<LineBreak />
|
||||
<Hyperlink x:Name="httpLink" NavigateUri="http://localhost:8888/baseinfo/00:00" IsEnabled="False" RequestNavigate="httpLink_RequestNavigate">
|
||||
http://localhost:8888/baseinfo/00:00
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
</FlowDocument>
|
||||
</RichTextBox>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer HorizontalAlignment="Left" Margin="560,43,0,0" VerticalAlignment="Top" Width="250" Height="210">
|
||||
<RichTextBox x:Name="mqttLinkTxtBlock" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
IsReadOnly="True" IsDocumentEnabled="True">
|
||||
<FlowDocument>
|
||||
<Paragraph x:Name="mqttPara">
|
||||
<Run Text="MQTT API"/>
|
||||
<LineBreak />
|
||||
<Run Text="시작시간 설정: startTime 토픽으로 mm:ss(분:초) 전달"/>
|
||||
<LineBreak />
|
||||
<Hyperlink x:Name="mqttLink" NavigateUri="mqtt://localhost:1883" IsEnabled="False" RequestNavigate="mqttLink_RequestNavigate">
|
||||
mqtt://localhost:1883
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
</FlowDocument>
|
||||
</RichTextBox>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
322
XRServer/MainWindow.xaml.cs
Normal file
322
XRServer/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
using MQTTnet;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Shapes;
|
||||
using uhttpsharp;
|
||||
using uhttpsharp.Handlers;
|
||||
using uhttpsharp.Listeners;
|
||||
using uhttpsharp.RequestProviders;
|
||||
using utils;
|
||||
|
||||
namespace XRServer
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
|
||||
private bool isRunning = false;
|
||||
private SQLiteService sqliteService = new SQLiteService();
|
||||
// HTTP 서버 설정
|
||||
private HttpServer httpServer = new HttpServer(new HttpRequestProvider());
|
||||
private int httpPort = 8888; // HTTP 서버 포트
|
||||
|
||||
private MQTTService mqttService;
|
||||
|
||||
private INIFile ini;
|
||||
|
||||
private int mqttPort = 1883; // MQTT 서버 포트
|
||||
|
||||
private string sqliteDbPath = string.Empty; // SQLite 데이터베이스 경로
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
LoadIniFile();
|
||||
InitHttpServer();
|
||||
mqttService = new MQTTService(sqliteService);
|
||||
mqttService.Init();
|
||||
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
/*httpLinkTxtBlock.ContentEnd.InsertLineBreak();
|
||||
httpLinkTxtBlock.ContentEnd.InsertLineBreak();
|
||||
httpLinkTxtBlock.ContentEnd.InsertTextInRun($"1");
|
||||
httpLinkTxtBlock.ContentEnd.InsertLineBreak();
|
||||
httpLinkTxtBlock.ContentEnd.InsertTextInRun($"2");
|
||||
httpLinkTxtBlock.ContentEnd.InsertLineBreak();
|
||||
httpLinkTxtBlock.ContentEnd.InsertTextInRun($"3");*/
|
||||
}
|
||||
|
||||
private void LoadIniFile()
|
||||
{
|
||||
// INI 파일 경로 설정
|
||||
String iniFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, System.Diagnostics.Process.GetCurrentProcess().ProcessName + @".ini");
|
||||
FileInfo f = new FileInfo(iniFilePath);
|
||||
if (!f.Exists)
|
||||
{
|
||||
iniFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Application.Current.MainWindow.GetType().Assembly.GetName().Name + @".ini");
|
||||
}
|
||||
ini = new INIFile(iniFilePath);
|
||||
// INI 파일에서 설정 값 읽기
|
||||
sqliteDbPath = ini.GetString("CONFIG", "DB_PATH").Trim();
|
||||
dbPathTxt.Text = sqliteDbPath;
|
||||
|
||||
if(dbPathTxt.Text == string.Empty || !File.Exists(dbPathTxt.Text))
|
||||
{
|
||||
startBtn.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void startBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
StartStop();
|
||||
}
|
||||
|
||||
private async void StartStop()
|
||||
{
|
||||
if(sqliteDbPath == string.Empty || !File.Exists(sqliteDbPath))
|
||||
{
|
||||
Logger.Debug("SQLite DB 경로가 설정되지 않았습니다. DB File 경로를 확인하세요.");
|
||||
MessageBox.Show("SQLite DB 경로가 설정되지 않았습니다. DB File 경로를 확인하세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sqliteService.Connected)
|
||||
{
|
||||
sqliteService.Connect(sqliteDbPath);
|
||||
AppendHttpPathLinkAsync();
|
||||
AppendMQTTTopicLinkAsync();
|
||||
}
|
||||
|
||||
if (!isRunning)
|
||||
{
|
||||
httpPort = int.Parse(httpPortTxt.Text);
|
||||
mqttPort = int.Parse(mqttPortTxt.Text);
|
||||
Logger.Debug($"Start Server httpPort:{httpPort} mqttPort:{mqttPort}");
|
||||
httpServer.Start();
|
||||
|
||||
// MQTT 서버 시작
|
||||
mqttService.SetConfig(mqttPort, mqttTopicTxt.Text, mqttTopicTxt2.Text);
|
||||
await mqttService.Start();
|
||||
|
||||
startBtn.Content = "Stop";
|
||||
httpPortTxt.IsEnabled = false;
|
||||
mqttPortTxt.IsEnabled = false;
|
||||
httpLink.IsEnabled = true;
|
||||
mqttLink.IsEnabled = true;
|
||||
mqttTopicTxt.IsEnabled = false;
|
||||
mqttTopicTxt2.IsEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Stop Server");
|
||||
httpServer.Dispose();
|
||||
// MQTT 서버 정지
|
||||
|
||||
await mqttService.Stop();
|
||||
Logger.Debug($"MQTT 서버가 포트 {mqttPort}에서 정지되었습니다.");
|
||||
startBtn.Content = "Start";
|
||||
httpPortTxt.IsEnabled = true;
|
||||
mqttPortTxt.IsEnabled = true;
|
||||
httpLink.IsEnabled = false;
|
||||
mqttLink.IsEnabled = false;
|
||||
mqttTopicTxt.IsEnabled = true;
|
||||
mqttTopicTxt2.IsEnabled = true;
|
||||
}
|
||||
|
||||
isRunning = !isRunning;
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
if (isRunning)
|
||||
{
|
||||
Logger.Debug("Stopping server on window close.");
|
||||
httpServer.Dispose();
|
||||
mqttService.Stop().Wait();
|
||||
}
|
||||
sqliteService.CloseDB();
|
||||
}
|
||||
|
||||
|
||||
private void InitHttpServer()
|
||||
{
|
||||
httpServer.Use(new TcpListenerAdapter(new TcpListener(IPAddress.Loopback, httpPort)));
|
||||
httpServer.Use(new BaseInfoHandler(sqliteService));
|
||||
}
|
||||
|
||||
private void dbFileLoadBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// SQLite 데이터베이스 파일 선택 대화상자 열기
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog();
|
||||
dialog.FileName = "SQLite"; // Default file name
|
||||
dialog.DefaultExt = ".txt"; // Default file extension
|
||||
dialog.Filter = "SQLite file (.sqlite)|*.sqlite|(.db)|*.db"; // Filter files by extension
|
||||
|
||||
// Show open file dialog box
|
||||
bool? result = dialog.ShowDialog();
|
||||
|
||||
// Process open file dialog box results
|
||||
if (result == true)
|
||||
{
|
||||
// Open document
|
||||
sqliteDbPath = dialog.FileName.Trim();
|
||||
dbPathTxt.Text = sqliteDbPath;
|
||||
ini.SetString("CONFIG", "DB_PATH", sqliteDbPath);
|
||||
startBtn.IsEnabled = dbPathTxt.Text != string.Empty && File.Exists(dbPathTxt.Text);
|
||||
}
|
||||
}
|
||||
|
||||
private void httpLink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
|
||||
{
|
||||
// 기본 브라우저로 HTTP 링크 열기
|
||||
var psi = new ProcessStartInfo(e.Uri.AbsoluteUri)
|
||||
{
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void httpPortTxt_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
if (httpPortTxt.Text == string.Empty || httpLink == null) return;
|
||||
string url = $"http://localhost:{httpPortTxt.Text}/baseinfo/00:00";
|
||||
try
|
||||
{
|
||||
httpLink.NavigateUri = new Uri(url);
|
||||
httpLink.Inlines.Clear();
|
||||
httpLink.Inlines.Add(url);
|
||||
}
|
||||
catch (UriFormatException ex)
|
||||
{
|
||||
Logger.Debug($"Invalid URL format: {ex.Message}");
|
||||
httpLink.NavigateUri = null;
|
||||
httpLink.Inlines.Clear();
|
||||
httpLink.Inlines.Add("Invalid URL format");
|
||||
}
|
||||
}
|
||||
|
||||
private void mqttLink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
|
||||
{
|
||||
// 기본 브라우저로 HTTP 링크 열기
|
||||
var psi = new ProcessStartInfo(e.Uri.AbsoluteUri)
|
||||
{
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void mqttPortTxt_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
if (mqttPortTxt.Text == string.Empty || mqttLink == null) return;
|
||||
string url = $"mqtt://localhost:{mqttPortTxt.Text}";
|
||||
try
|
||||
{
|
||||
mqttLink.NavigateUri = new Uri(url);
|
||||
mqttLink.Inlines.Clear();
|
||||
mqttLink.Inlines.Add(url);
|
||||
}
|
||||
catch (UriFormatException ex)
|
||||
{
|
||||
Logger.Debug($"Invalid URL format: {ex.Message}");
|
||||
mqttLink.NavigateUri = null;
|
||||
mqttLink.Inlines.Clear();
|
||||
mqttLink.Inlines.Add("Invalid URL format");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// sqlite에서 baseinfo 데이터를 조회하여 json 첫번째 항목을 확인해서 httpLinkTxtBlock에 추가 표시
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task AppendHttpPathLinkAsync()
|
||||
{
|
||||
// 데이터 조회
|
||||
List<SQLiteDataEntity> data = await sqliteService.SelectBySecondBaseInfo("00:00");
|
||||
// 데이터가 null이 아니고, 데이터가 있는지 확인
|
||||
if (data != null && data.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Newtonsoft.Json을 사용하여 1-depth 키만 추출
|
||||
using (StringReader stringReader = new StringReader(data[0].data))
|
||||
using (JsonTextReader reader = new JsonTextReader(stringReader))
|
||||
{
|
||||
JObject jsonObject = JObject.Load(reader);
|
||||
foreach (var property in jsonObject.Properties())
|
||||
{
|
||||
httpPara.Inlines.Add(new LineBreak());
|
||||
string linkString = $"http://localhost:{httpPort}/{property.Name}/00:00";
|
||||
Hyperlink hyperLink = new Hyperlink()
|
||||
{
|
||||
NavigateUri = new Uri(linkString)
|
||||
};
|
||||
hyperLink.Inlines.Add(linkString);
|
||||
hyperLink.RequestNavigate += httpLink_RequestNavigate;
|
||||
httpPara.Inlines.Add(hyperLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug($"JSON 파싱 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AppendMQTTTopicLinkAsync()
|
||||
{
|
||||
// 데이터 조회
|
||||
List<SQLiteDataEntity> data = await sqliteService.SelectBySecond("00:00", 1);
|
||||
// 데이터가 null이 아니고, 데이터가 있는지 확인
|
||||
if (data != null && data.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Newtonsoft.Json을 사용하여 1-depth 키만 추출
|
||||
using (StringReader stringReader = new StringReader(data[0].data))
|
||||
using (JsonTextReader reader = new JsonTextReader(stringReader))
|
||||
{
|
||||
JObject jsonObject = JObject.Load(reader);
|
||||
mqttPara.Inlines.Add(new LineBreak());
|
||||
mqttPara.Inlines.Add(new Run($"제공하는 Topic명 ------"));
|
||||
foreach (var property in jsonObject.Properties())
|
||||
{
|
||||
mqttPara.Inlines.Add(new LineBreak());
|
||||
mqttPara.Inlines.Add(new Run(property.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug($"JSON 파싱 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
191
XRServer/SQLiteService.cs
Normal file
191
XRServer/SQLiteService.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
using Microsoft.VisualBasic;
|
||||
using SQLite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using utils;
|
||||
|
||||
namespace XRServer
|
||||
{
|
||||
public class SQLiteService
|
||||
{
|
||||
//#region Singleton
|
||||
//private static readonly SQLiteService instance = new SQLiteService();
|
||||
//public static SQLiteService Instance => instance;
|
||||
//static SQLiteService() { }
|
||||
//#endregion
|
||||
|
||||
|
||||
private SQLiteConnection? dbConnection;
|
||||
|
||||
public bool Connected { get => dbConnection != null; }
|
||||
|
||||
public void Connect(string dbFilePath)
|
||||
{
|
||||
var _dbPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbFilePath);
|
||||
var str = new SQLiteConnectionString(_dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.FullMutex, false);
|
||||
dbConnection = new SQLiteConnection(str);
|
||||
}
|
||||
|
||||
public void CloseDB()
|
||||
{
|
||||
dbConnection?.Close();
|
||||
dbConnection = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 추가하기
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="timeStamp">yyyy-MM-ddTHH:mm:ss.fffZ format string</param>
|
||||
/// <param name="temp"></param>
|
||||
/// <returns>데이터베이스에서 추가된 행 수</returns>
|
||||
public int Insert(string data, string timeStamp, string temp = null)
|
||||
{
|
||||
var query = $"INSERT INTO realTime (data, timestamp, temp) VALUES ('{data}', '{timeStamp}', " + (temp == null ? "null" : "'" + temp + "'") + ");";
|
||||
int changedRowLen = dbConnection!.Execute(query);
|
||||
return changedRowLen;
|
||||
}
|
||||
|
||||
private string yyyyMMddHH = "";
|
||||
|
||||
readonly string[] queryParts =
|
||||
{
|
||||
"SELECT * FROM realTime WHERE ",
|
||||
"timestamp >= '",
|
||||
"' AND timestamp < '",
|
||||
"timestamp <= '",
|
||||
"' AND timestamp > '",
|
||||
" ORDER BY timestamp ",
|
||||
" LIMIT ",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// selectTime보다 +- second 사이의 데이터 요청. selectTime, second 포함
|
||||
/// second > 0 : selectTime <= data < selectTime + second
|
||||
/// second < 0 : selectTime + second < data <= selectTime
|
||||
/// </summary>
|
||||
/// <param name="selectTime">mm:ss format string</param>
|
||||
/// <param name="second"></param>
|
||||
/// <param name="orderAsc">true: 오래된 시간이 먼저, false: 최근 시간이 먼저</param>
|
||||
/// <param name="limit"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<SQLiteDataEntity>> SelectBySecond(string selectTime, int second, bool orderAsc = true, int limit = 0)
|
||||
{
|
||||
if (yyyyMMddHH.Length == 0)
|
||||
{
|
||||
List<SQLiteDataEntity> one = await Task.Run(() =>
|
||||
{
|
||||
return dbConnection!.Query<SQLiteDataEntity>("SELECT * FROM realTime LIMIT 1");
|
||||
});
|
||||
if (one.Count > 0)
|
||||
{
|
||||
yyyyMMddHH = one[0].timestamp.Substring(0, 14);
|
||||
}
|
||||
}
|
||||
selectTime = yyyyMMddHH + selectTime + ".000Z";
|
||||
|
||||
StringBuilder queryBuilder = new();
|
||||
DateTime date = DateTimeUtil.UtcParse(selectTime).AddSeconds(second);
|
||||
string targetTime = DateTimeUtil.FormatTime(date);
|
||||
//Debug.Log($"SelectBySecondBaseInfo {selectTime} {second} {targetTime} {date}");
|
||||
queryBuilder.Append(queryParts[0]);
|
||||
//second가 selectTime 보다 더 미래면
|
||||
if (second > 0)
|
||||
{
|
||||
queryBuilder.Append($"{queryParts[1]}{selectTime}{queryParts[2]}{targetTime}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
//second가 selectTime 보다 더 과거면
|
||||
queryBuilder.Append($"{queryParts[3]}{selectTime}{queryParts[4]}{targetTime}'");
|
||||
}
|
||||
queryBuilder.Append($"{queryParts[5]}{(orderAsc ? "asc" : "desc")}");
|
||||
if (limit > 0)
|
||||
queryBuilder.Append($"{queryParts[6]}{limit}");
|
||||
|
||||
queryBuilder.Append(";");
|
||||
//Debug.Log($"SelectBySecond {query}");
|
||||
List<SQLiteDataEntity> result = await Task.Run(() =>
|
||||
{
|
||||
var query = queryBuilder.ToString();
|
||||
queryBuilder.Clear();
|
||||
return dbConnection!.Query<SQLiteDataEntity>(query);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private string yyyyMMddHHBaseInfo = "";
|
||||
|
||||
/// <summary>
|
||||
/// selectTime보다 +- second 사이의 데이터 요청. selectTime, second 포함
|
||||
/// second > 0 : selectTime <= data < selectTime + second
|
||||
/// second < 0 : selectTime + second < data <= selectTime
|
||||
/// </summary>
|
||||
/// <param name="selectTime">mm:ss format string</param>
|
||||
/// <param name="second"></param>
|
||||
/// <param name="orderAsc">true: 오래된 시간이 먼저, false: 최근 시간이 먼저</param>
|
||||
/// <param name="limit"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<SQLiteDataEntity>> SelectBySecondBaseInfo(string selectTime, int second = 59, bool orderAsc = false, int limit = 1)
|
||||
{
|
||||
if(yyyyMMddHHBaseInfo.Length == 0)
|
||||
{
|
||||
List<SQLiteDataEntity> one = await Task.Run(() =>
|
||||
{
|
||||
return dbConnection.Query<SQLiteDataEntity>("SELECT * FROM baseInfo LIMIT 1;");
|
||||
});
|
||||
if(one.Count > 0)
|
||||
{
|
||||
yyyyMMddHHBaseInfo = one[0].timestamp.Substring(0, 14);
|
||||
}
|
||||
}
|
||||
Logger.Debug($"yyyyMMddHHBaseInfo1: {yyyyMMddHHBaseInfo}");
|
||||
selectTime = yyyyMMddHHBaseInfo + selectTime + ".000Z";
|
||||
Logger.Debug($"yyyyMMddHHBaseInfo2: {yyyyMMddHHBaseInfo}");
|
||||
StringBuilder queryBuilder = new();
|
||||
DateTime date = DateTimeUtil.UtcParse(selectTime).AddSeconds(second);
|
||||
string targetTime = DateTimeUtil.FormatTime(date);
|
||||
queryBuilder.Append($"SELECT * FROM baseInfo WHERE ");
|
||||
//second가 selectTime 보다 더 미래면
|
||||
if (second > 0)
|
||||
{
|
||||
queryBuilder.Append($"timestamp >= '{selectTime}' AND timestamp < '{targetTime}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
//second가 selectTime 보다 더 과거면
|
||||
queryBuilder.Append($"timestamp <= '{selectTime}' AND timestamp > '{targetTime}'");
|
||||
}
|
||||
queryBuilder.Append($" ORDER BY timestamp {(orderAsc ? "asc" : "desc")}");
|
||||
if (limit > 0)
|
||||
queryBuilder.Append($" LIMIT {limit}");
|
||||
queryBuilder.Append(";");
|
||||
|
||||
List<SQLiteDataEntity> result = await Task.Run(() =>
|
||||
{
|
||||
var query = queryBuilder.ToString();
|
||||
Logger.Debug($"SelectBySecondBaseInfo {query}");
|
||||
queryBuilder.Clear();
|
||||
return dbConnection.Query<SQLiteDataEntity>(query);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class SQLiteDataEntity
|
||||
{
|
||||
public string data { get; set; }
|
||||
[PrimaryKey]
|
||||
public string timestamp { get; set; }
|
||||
public DateTime timestampHungary { get => DateTimeUtil.UtcStringToHungaryDateTime(timestamp); }
|
||||
public string temp { get; set; }
|
||||
}
|
||||
}
|
||||
32
XRServer/XRServer.csproj
Normal file
32
XRServer/XRServer.csproj
Normal file
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="icon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="log4net" Version="3.1.0" />
|
||||
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
|
||||
<PackageReference Include="MQTTnet.Server" Version="5.0.1.1416" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageReference Include="uhttpsharp.Standard" Version="8.0.30703.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Log4net.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
2
XRServer/XRServer.ini
Normal file
2
XRServer/XRServer.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[CONFIG]
|
||||
DB_PATH=
|
||||
BIN
XRServer/icon.ico
Normal file
BIN
XRServer/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Reference in New Issue
Block a user