초기 커밋.
This commit is contained in:
89
.gitignore
vendored
Normal file
89
.gitignore
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# Built Visual Studio files
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.pdb
|
||||||
|
*.cache
|
||||||
|
*.mdb
|
||||||
|
*.obj
|
||||||
|
*.log
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.userprefs
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.psess
|
||||||
|
*.pidb
|
||||||
|
*.tmp
|
||||||
|
*.svclog
|
||||||
|
*.sqlite
|
||||||
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
|
*.opendb
|
||||||
|
|
||||||
|
# Build directories
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Tt]emp/
|
||||||
|
[Pp]ublish/
|
||||||
|
[Rr]elease/
|
||||||
|
[Dd]ebug/
|
||||||
|
|
||||||
|
# Rider / ReSharper
|
||||||
|
*.DotSettings.user
|
||||||
|
.idea/
|
||||||
|
.Rider/
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]esult
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Test results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NuGet
|
||||||
|
*.nupkg
|
||||||
|
packages/
|
||||||
|
*.snupkg
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
!**/[Pp]ackages/repositories.config
|
||||||
|
*.nuspec
|
||||||
|
|
||||||
|
# Azure
|
||||||
|
*.azurePubxml
|
||||||
|
*.portbridge.json
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# Configuration files
|
||||||
|
appsettings.*.json
|
||||||
|
secrets.json
|
||||||
|
*.KeyStore
|
||||||
|
*.pfx
|
||||||
|
|
||||||
|
# Entity Framework
|
||||||
|
Migrations/obj/
|
||||||
|
*/migrations/obj/
|
||||||
|
|
||||||
|
# Coverage & analysis
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
TestResults/
|
||||||
|
.coverlet/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
*.bak
|
||||||
|
*.tlog
|
||||||
|
*.ipch
|
||||||
|
*.iuser
|
||||||
|
*.tlog
|
||||||
|
*.ipr
|
||||||
|
.vs/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
15
Configs/Entrance.json
Normal file
15
Configs/Entrance.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"LaneName": "Entrance",
|
||||||
|
"LaneId": "#P-001",
|
||||||
|
"LaneDescription": "Main Entrance",
|
||||||
|
"CameraIp": "192.168.100.99",
|
||||||
|
"CameraPort": 80,
|
||||||
|
"GateIp": "127.0.0.1",
|
||||||
|
"GatePort": 5000,
|
||||||
|
"LedIp": "127.0.0.1",
|
||||||
|
"LedPort": 5001,
|
||||||
|
"LedTopMessage": "어서오세요",
|
||||||
|
"LedBottomMessage": "주차관리시스템",
|
||||||
|
"CmsIp": "127.0.0.1",
|
||||||
|
"CmsPort": 6000
|
||||||
|
}
|
||||||
6
Inno.Common.Stubs/Class1.cs
Normal file
6
Inno.Common.Stubs/Class1.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Inno.Common.Stubs;
|
||||||
|
|
||||||
|
public class Class1
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
9
Inno.Common.Stubs/Inno.Common.Stubs.csproj
Normal file
9
Inno.Common.Stubs/Inno.Common.Stubs.csproj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Inno.Communication.TCP
|
||||||
|
{
|
||||||
|
public class TcpClientConnector : IDisposable
|
||||||
|
{
|
||||||
|
private TcpClient _client;
|
||||||
|
private NetworkStream _stream;
|
||||||
|
|
||||||
|
public bool IsConnected => _client != null && _client.Connected;
|
||||||
|
|
||||||
|
public async Task ConnectAsync(string host, int port, int timeout, CancellationToken token)
|
||||||
|
{
|
||||||
|
_client = new TcpClient();
|
||||||
|
var connectTask = _client.ConnectAsync(host, port);
|
||||||
|
if (await Task.WhenAny(connectTask, Task.Delay(timeout, token)) == connectTask)
|
||||||
|
{
|
||||||
|
await connectTask;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_stream = _client.GetStream();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_client.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_client.Dispose();
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteAsync(byte[] data, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (_stream == null) throw new InvalidOperationException("Not connected");
|
||||||
|
await _stream.WriteAsync(data, 0, data.Length, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteAsync(string message, Encoding encoding, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (_stream == null) throw new InvalidOperationException("Not connected");
|
||||||
|
byte[] data = encoding.GetBytes(message);
|
||||||
|
await WriteAsync(data, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> ReadAsync(byte[] buffer, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (_stream == null) throw new InvalidOperationException("Not connected");
|
||||||
|
return await _stream.ReadAsync(buffer, 0, buffer.Length, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_stream?.Dispose();
|
||||||
|
_client?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Inno.Communication.TCP
|
||||||
|
{
|
||||||
|
public class TcpConnectionItem
|
||||||
|
{
|
||||||
|
public string Host { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
public TcpConnectionItem(string host, int port) { Host = host; Port = port; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Inno.Rtsp.RawFramesDecoding.DecodedFrames.Video
|
||||||
|
{
|
||||||
|
public interface IDecodedVideoFrame
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Inno.Common.Stubs/Inno/Rtsp/RtspPlayer.cs
Normal file
23
Inno.Common.Stubs/Inno/Rtsp/RtspPlayer.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using RtspModule;
|
||||||
|
using Inno.Rtsp.RawFramesDecoding.DecodedFrames.Video;
|
||||||
|
|
||||||
|
namespace Inno.Rtsp
|
||||||
|
{
|
||||||
|
public class RtspPlayer : IDisposable
|
||||||
|
{
|
||||||
|
public event EventHandler<IDecodedVideoFrame> RtspFrameReceived;
|
||||||
|
public event EventHandler<string> ConnectionChanged;
|
||||||
|
|
||||||
|
public void Init(RtspConnectionParameter param) { }
|
||||||
|
public void StartRtspStreaming() { }
|
||||||
|
public void StopRtspStreaming() { }
|
||||||
|
public void ChangeAddress(RtspConnectionParameter param) { }
|
||||||
|
public void Dispose() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Inno.Rtsp.RawFramesReceiving
|
||||||
|
{
|
||||||
|
public class RawFramesSource { }
|
||||||
|
}
|
||||||
7
Inno.Common.Stubs/Inno/Wpf/EncodingCodeDefines.cs
Normal file
7
Inno.Common.Stubs/Inno/Wpf/EncodingCodeDefines.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Inno.Wpf
|
||||||
|
{
|
||||||
|
public static class EncodingCodeDefines
|
||||||
|
{
|
||||||
|
public const int CODE_EUC_KR = 51949;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Inno.Common.Stubs/RtspModule/RtspConnectionParameter.cs
Normal file
8
Inno.Common.Stubs/RtspModule/RtspConnectionParameter.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace RtspModule
|
||||||
|
{
|
||||||
|
public enum Company { Novitec }
|
||||||
|
public class RtspConnectionParameter
|
||||||
|
{
|
||||||
|
public RtspConnectionParameter(Company company, string ip, string port, string id, string pw) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Inno.LPR/DeepLearningLibraries/DeepLearning_Model.cs
Normal file
41
Inno.LPR/DeepLearningLibraries/DeepLearning_Model.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
namespace DeepLearning_Model_Sharp
|
||||||
|
{
|
||||||
|
public enum ENUM_MODEL_TYPE
|
||||||
|
{
|
||||||
|
// CPU
|
||||||
|
_TYPE_C_SSD = 0,
|
||||||
|
_TYPE_C_YLv4,
|
||||||
|
_TYPE_C_YLv5,
|
||||||
|
_TYPE_C_YLvX, // v5, v8
|
||||||
|
// DARKNET
|
||||||
|
_TYPE_D_TINY,
|
||||||
|
_TYPE_D_NORM,
|
||||||
|
// GPU
|
||||||
|
_TYPE_G_NORM,
|
||||||
|
_TYPE_G_CUDA,
|
||||||
|
_TYPE_G_VINO,
|
||||||
|
_TYPE_G_TRT,
|
||||||
|
_TYPE_G_DML,
|
||||||
|
_TYPE_G_DNNL,
|
||||||
|
// TRT
|
||||||
|
_TYPE_T_TRT,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ENUM_MODEL_DLL
|
||||||
|
{
|
||||||
|
_MODEL_DLL_CPU = 0, // CPU 21.4.2
|
||||||
|
_MODEL_DLL_CPU2, // CPU 22.3.0 XP
|
||||||
|
_MODEL_DLL_CPU3, // CPU 23.0.0
|
||||||
|
_MODEL_DLL_GPU, // DARKNET
|
||||||
|
_MODEL_DLL_GPU2, // GPU
|
||||||
|
_MODEL_DLL_GPU3, // TRT
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ENUM_DLL_RESULT
|
||||||
|
{
|
||||||
|
_DLL_RST_OK = 1,
|
||||||
|
_DLL_RST_ERROR = 0,
|
||||||
|
_DLL_RST_ERROR_KEY = -1,
|
||||||
|
_DLL_RST_ERROR_MODEL = -2,
|
||||||
|
}
|
||||||
|
}
|
||||||
372
Inno.LPR/DeepLearningLibraries/DeepLearning_Wrapper.cs
Normal file
372
Inno.LPR/DeepLearningLibraries/DeepLearning_Wrapper.cs
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using DeepLearningSharp;
|
||||||
|
using OpenCvSharp;
|
||||||
|
|
||||||
|
namespace DeepLearning_Model_Sharp
|
||||||
|
{
|
||||||
|
public struct BBOX_T
|
||||||
|
{
|
||||||
|
public uint x, y, w, h;
|
||||||
|
public float prob;
|
||||||
|
public uint obj_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct LPR_Boxes
|
||||||
|
{
|
||||||
|
public uint x, y, w, h;
|
||||||
|
public float prob;
|
||||||
|
public uint obj_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct LPR_RESULT_PTR
|
||||||
|
{
|
||||||
|
public IntPtr vLPR_Boxes_Ptr;
|
||||||
|
public int nLPR_Boxes;
|
||||||
|
|
||||||
|
public int nRST_LP;
|
||||||
|
public string mRST_Code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct LPR_RESULT_RST
|
||||||
|
{
|
||||||
|
public List<LPR_Boxes> vLPR_Boxes;
|
||||||
|
|
||||||
|
public LPR_Boxes mRect_LP;
|
||||||
|
public int nRST_LP;
|
||||||
|
public string mRST_Code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DeepLearning_Wrapper : IDisposable
|
||||||
|
{
|
||||||
|
public const string DLL_DETECTION = "DeepLearningLPR26.dll";
|
||||||
|
public const string DLL_OCR = "DeepLearningLPR.dll";
|
||||||
|
|
||||||
|
private IntPtr _pModel = IntPtr.Zero;
|
||||||
|
private readonly int _nModel_DLL = (int)ENUM_MODEL_DLL._MODEL_DLL_CPU3;
|
||||||
|
private readonly int _nModel_Type = (int)ENUM_MODEL_TYPE._TYPE_C_YLvX;
|
||||||
|
private readonly int _nMulti = 1;
|
||||||
|
private readonly int _nDecryption = 1;
|
||||||
|
private readonly int _GPUID = 0;
|
||||||
|
private int _nDLL_RST = (int)ENUM_DLL_RESULT._DLL_RST_ERROR;
|
||||||
|
|
||||||
|
private readonly string _cf = "";
|
||||||
|
private readonly string _wf;
|
||||||
|
|
||||||
|
private readonly DeepLearning_Model_Wrapper mModel_Wrapper;
|
||||||
|
|
||||||
|
public DeepLearning_Wrapper(int nModel_DLL, string cf, string wf, int nModel_Type, int nMulti, int nDecryption)
|
||||||
|
{
|
||||||
|
_nModel_DLL = nModel_DLL;
|
||||||
|
_nModel_Type = nModel_Type;
|
||||||
|
_nMulti = nMulti;
|
||||||
|
_nDecryption = nDecryption;
|
||||||
|
|
||||||
|
_cf = cf;
|
||||||
|
_wf = wf;
|
||||||
|
|
||||||
|
mModel_Wrapper = new DeepLearning_Model_Wrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeepLearning_Wrapper()
|
||||||
|
{
|
||||||
|
_wf = DLL_DETECTION;
|
||||||
|
mModel_Wrapper = new DeepLearning_Model_Wrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeepLearning_Wrapper(string wf)
|
||||||
|
{
|
||||||
|
_wf = wf;
|
||||||
|
mModel_Wrapper = new DeepLearning_Model_Wrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Model_Wrapper_Construct()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_nDLL_RST = mModel_Wrapper._Model_Construct(_nModel_DLL, ref _pModel, _cf, _wf, _nModel_Type, _nDecryption, _GPUID);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _nDLL_RST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Model_Wrapper_Destruct()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_nDLL_RST = mModel_Wrapper._Model_Destruct(_nModel_DLL, ref _pModel);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _nDLL_RST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Model_Wrapper_Getversion(ref string strVersion)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_nDLL_RST = mModel_Wrapper._Model_Getversion(_nModel_DLL, _pModel, ref strVersion);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _nDLL_RST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Model_Wrapper_Detect(Mat mat_SRC, ref List<BBOX_T> vBBOX_T)
|
||||||
|
{
|
||||||
|
IntPtr matPtr = mat_SRC.CvPtr;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IntPtr vBBOX_TPtr = IntPtr.Zero;
|
||||||
|
int nBBOX_T = 0;
|
||||||
|
|
||||||
|
_nDLL_RST = mModel_Wrapper._Model_Detect(_nModel_DLL, _pModel, matPtr, ref vBBOX_TPtr, ref nBBOX_T, _nModel_Type);
|
||||||
|
|
||||||
|
if (vBBOX_TPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Calculate the size of each struct element
|
||||||
|
int structSize = Marshal.SizeOf(typeof(BBOX_T));
|
||||||
|
|
||||||
|
// Iterate over the results and convert them to _LPR_Boxes structs
|
||||||
|
for (int i = 0; i < nBBOX_T; i++)
|
||||||
|
{
|
||||||
|
IntPtr structPtr = IntPtr.Add(vBBOX_TPtr, i * structSize);
|
||||||
|
BBOX_T resultItem = Marshal.PtrToStructure<BBOX_T>(structPtr);
|
||||||
|
vBBOX_T.Add(resultItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the allocated memory
|
||||||
|
mModel_Wrapper._Model_FreeHGlobal(vBBOX_TPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _nDLL_RST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Model_Wrapper_LPR_Plate(Mat mat_LPR, Rect mRect_ROI, ref List<LPR_Boxes> vLPR_Boxes)
|
||||||
|
{
|
||||||
|
IntPtr matPtr = mat_LPR.CvPtr;
|
||||||
|
IntPtr rectPtr = IntPtr.Zero;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rectPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Rect)));
|
||||||
|
Marshal.StructureToPtr(mRect_ROI, rectPtr, false);
|
||||||
|
|
||||||
|
IntPtr vLPR_BoxesPtr = IntPtr.Zero;
|
||||||
|
int nLPR_Boxes = 0;
|
||||||
|
|
||||||
|
_nDLL_RST = mModel_Wrapper._Model_LPR_Plate(_nModel_DLL, _pModel, matPtr, rectPtr, ref vLPR_BoxesPtr, ref nLPR_Boxes, _nMulti, _nModel_Type);
|
||||||
|
|
||||||
|
if (vLPR_BoxesPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Calculate the size of each struct element
|
||||||
|
int structSize = Marshal.SizeOf(typeof(LPR_Boxes));
|
||||||
|
|
||||||
|
// Iterate over the results and convert them to _LPR_Boxes structs
|
||||||
|
for (int i = 0; i < nLPR_Boxes; i++)
|
||||||
|
{
|
||||||
|
IntPtr structPtr = IntPtr.Add(vLPR_BoxesPtr, i * structSize);
|
||||||
|
LPR_Boxes resultItem = Marshal.PtrToStructure<LPR_Boxes>(structPtr);
|
||||||
|
vLPR_Boxes.Add(resultItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the allocated memory
|
||||||
|
mModel_Wrapper._Model_FreeHGlobal(vLPR_BoxesPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (rectPtr != IntPtr.Zero) Marshal.FreeHGlobal(rectPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _nDLL_RST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Model_Wrapper_LPR_Code(Mat mat_LPR, Rect mRect_LP, ref LPR_RESULT_RST mLPR_RESULT_RST)
|
||||||
|
{
|
||||||
|
IntPtr matPtr = mat_LPR.CvPtr;
|
||||||
|
IntPtr rectPtr = IntPtr.Zero;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rectPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Rect)));
|
||||||
|
Marshal.StructureToPtr(mRect_LP, rectPtr, false);
|
||||||
|
|
||||||
|
IntPtr LPR_ResultPtr = IntPtr.Zero;
|
||||||
|
int nLPR_Result = 0;
|
||||||
|
|
||||||
|
_nDLL_RST = mModel_Wrapper._Model_LPR_Code(_nModel_DLL, _pModel, matPtr, rectPtr, ref LPR_ResultPtr, ref nLPR_Result, _nModel_Type);
|
||||||
|
|
||||||
|
if (LPR_ResultPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Calculate the size of each struct element
|
||||||
|
int structSize = Marshal.SizeOf(typeof(LPR_RESULT_PTR));
|
||||||
|
|
||||||
|
List<LPR_RESULT_PTR> vLPR_RESULT = [];
|
||||||
|
// Iterate over the results and convert them to _LPR_Boxes struct
|
||||||
|
for (int i = 0; i < nLPR_Result; i++)
|
||||||
|
{
|
||||||
|
IntPtr structPtr = IntPtr.Add(LPR_ResultPtr, i * structSize);
|
||||||
|
LPR_RESULT_PTR resultItem = Marshal.PtrToStructure<LPR_RESULT_PTR>(structPtr);
|
||||||
|
vLPR_RESULT.Add(resultItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int k = 0; k < vLPR_RESULT.Count; k++)
|
||||||
|
{
|
||||||
|
IntPtr vLPR_BoxesPtr = vLPR_RESULT[k].vLPR_Boxes_Ptr;
|
||||||
|
int nLPR_Boxes = vLPR_RESULT[k].nLPR_Boxes;
|
||||||
|
|
||||||
|
if (vLPR_BoxesPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Calculate the size of each struct element
|
||||||
|
int structSize_Boxes = Marshal.SizeOf(typeof(LPR_Boxes));
|
||||||
|
|
||||||
|
mLPR_RESULT_RST.vLPR_Boxes = [];
|
||||||
|
// Iterate over the results and convert them to _LPR_Boxes struct
|
||||||
|
for (int i = 0; i < nLPR_Boxes; i++)
|
||||||
|
{
|
||||||
|
IntPtr structPtr = IntPtr.Add(vLPR_BoxesPtr, i * structSize);
|
||||||
|
LPR_Boxes resultItem = Marshal.PtrToStructure<LPR_Boxes>(structPtr);
|
||||||
|
|
||||||
|
if (i == 0) mLPR_RESULT_RST.mRect_LP = resultItem;
|
||||||
|
else mLPR_RESULT_RST.vLPR_Boxes.Add(resultItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLPR_RESULT_RST.nRST_LP = vLPR_RESULT[k].nRST_LP;
|
||||||
|
mLPR_RESULT_RST.mRST_Code = vLPR_RESULT[k].mRST_Code;
|
||||||
|
|
||||||
|
// Clean up the allocated memory
|
||||||
|
mModel_Wrapper._Model_FreeHGlobal(vLPR_BoxesPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the allocated memory
|
||||||
|
mModel_Wrapper._Model_FreeHGlobal(LPR_ResultPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (rectPtr != IntPtr.Zero) Marshal.FreeHGlobal(rectPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _nDLL_RST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Model_Wrapper_LPR_RST(Mat mat_LPR, Rect mRect_ROI, ref List<LPR_RESULT_RST> vLPR_RESULT_RST)
|
||||||
|
{
|
||||||
|
IntPtr matPtr = mat_LPR.CvPtr;
|
||||||
|
IntPtr rectPtr = IntPtr.Zero;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rectPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Rect)));
|
||||||
|
Marshal.StructureToPtr(mRect_ROI, rectPtr, false);
|
||||||
|
|
||||||
|
IntPtr LPR_ResultPtr = IntPtr.Zero;
|
||||||
|
int nLPR_Result = 0;
|
||||||
|
|
||||||
|
_nDLL_RST = mModel_Wrapper._Model_LPR_RST(_nModel_DLL, _pModel, matPtr, rectPtr, ref LPR_ResultPtr, ref nLPR_Result, _nMulti, _nModel_Type);
|
||||||
|
|
||||||
|
if (LPR_ResultPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Calculate the size of each struct element
|
||||||
|
int structSize = Marshal.SizeOf(typeof(LPR_RESULT_PTR));
|
||||||
|
|
||||||
|
List<LPR_RESULT_PTR> vLPR_RESULT = [];
|
||||||
|
// Iterate over the results and convert them to _LPR_Boxes struct
|
||||||
|
for (int i = 0; i < nLPR_Result; i++)
|
||||||
|
{
|
||||||
|
IntPtr structPtr = IntPtr.Add(LPR_ResultPtr, i * structSize);
|
||||||
|
LPR_RESULT_PTR resultItem = Marshal.PtrToStructure<LPR_RESULT_PTR>(structPtr);
|
||||||
|
vLPR_RESULT.Add(resultItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
LPR_RESULT_RST mLPR_RESULT_RST = new();
|
||||||
|
for (int k = 0; k < vLPR_RESULT.Count; k++)
|
||||||
|
{
|
||||||
|
IntPtr vLPR_BoxesPtr = vLPR_RESULT[k].vLPR_Boxes_Ptr;
|
||||||
|
int nLPR_Boxes = vLPR_RESULT[k].nLPR_Boxes;
|
||||||
|
|
||||||
|
if (vLPR_BoxesPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Calculate the size of each struct element
|
||||||
|
int structSize_Boxes = Marshal.SizeOf(typeof(LPR_Boxes));
|
||||||
|
|
||||||
|
mLPR_RESULT_RST.vLPR_Boxes = [];
|
||||||
|
// Iterate over the results and convert them to _LPR_Boxes struct
|
||||||
|
for (int i = 0; i < nLPR_Boxes; i++)
|
||||||
|
{
|
||||||
|
IntPtr structPtr = IntPtr.Add(vLPR_BoxesPtr, i * structSize);
|
||||||
|
LPR_Boxes resultItem = Marshal.PtrToStructure<LPR_Boxes>(structPtr);
|
||||||
|
|
||||||
|
if (i == 0) mLPR_RESULT_RST.mRect_LP = resultItem;
|
||||||
|
else mLPR_RESULT_RST.vLPR_Boxes.Add(resultItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLPR_RESULT_RST.nRST_LP = vLPR_RESULT[k].nRST_LP;
|
||||||
|
mLPR_RESULT_RST.mRST_Code = vLPR_RESULT[k].mRST_Code;
|
||||||
|
|
||||||
|
vLPR_RESULT_RST.Add(mLPR_RESULT_RST);
|
||||||
|
|
||||||
|
// Clean up the allocated memory
|
||||||
|
mModel_Wrapper._Model_FreeHGlobal(vLPR_BoxesPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the allocated memory
|
||||||
|
mModel_Wrapper._Model_FreeHGlobal(LPR_ResultPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (rectPtr != IntPtr.Zero) Marshal.FreeHGlobal(rectPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _nDLL_RST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_nDLL_RST = Model_Wrapper_Destruct();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.Message);
|
||||||
|
}
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Inno.LPR/Inno.LPR.csproj
Normal file
56
Inno.LPR/Inno.LPR.csproj
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OpenCvSharp4" Version="4.9.0.20240103" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="DeepLearning_Sharp">
|
||||||
|
<HintPath>Libraries\DeepLearning_Sharp.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Libraries\DeepLearningLPR.dll">
|
||||||
|
<Link>DeepLearningLPR.dll</Link>
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Libraries\DeepLearningLPR26.dll">
|
||||||
|
<Link>DeepLearningLPR26.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Libraries\DeepLearning_CPU3.dll">
|
||||||
|
<Link>DeepLearning_CPU3.dll</Link>
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Libraries\DeepLearning_Model.dll">
|
||||||
|
<Link>DeepLearning_Model.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Libraries\DeepLearning_Sharp.dll">
|
||||||
|
<Link>DeepLearning_Sharp.dll</Link>
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Libraries\opencv_world4120.dll">
|
||||||
|
<Link>opencv_world4120.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Libraries\opencv_world470.dll">
|
||||||
|
<Link>opencv_world470.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Libraries\tbb12.dll">
|
||||||
|
<Link>tbb12.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
90
Inno.LPR/LicensePlateMotionDetector.cs
Normal file
90
Inno.LPR/LicensePlateMotionDetector.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using DeepLearning_Model_Sharp;
|
||||||
|
using OpenCvSharp;
|
||||||
|
|
||||||
|
namespace Inno.LPR
|
||||||
|
{
|
||||||
|
public class LicensePlateMotionDetector : IDisposable
|
||||||
|
{
|
||||||
|
private readonly DeepLearning_Wrapper _wrapperDetection;
|
||||||
|
private readonly DeepLearning_Wrapper _wrapperRecognition;
|
||||||
|
|
||||||
|
public string ModelVersion { get; private set; }
|
||||||
|
|
||||||
|
public LicensePlateMotionDetector()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_wrapperDetection = new DeepLearning_Wrapper(DeepLearning_Wrapper.DLL_DETECTION);
|
||||||
|
_wrapperRecognition = new DeepLearning_Wrapper(DeepLearning_Wrapper.DLL_OCR);
|
||||||
|
|
||||||
|
var constructResult1 = _wrapperDetection.Model_Wrapper_Construct();
|
||||||
|
if (constructResult1 != 1) { throw new InvalidOperationException("Detection Construct Fail"); }
|
||||||
|
|
||||||
|
var constructResult2 = _wrapperRecognition.Model_Wrapper_Construct();
|
||||||
|
if (constructResult2 != 1) { throw new InvalidOperationException("Recognition Construct Fail"); }
|
||||||
|
|
||||||
|
string modelVersion = string.Empty;
|
||||||
|
var getVersionResult = _wrapperDetection.Model_Wrapper_Getversion(ref modelVersion);
|
||||||
|
|
||||||
|
if (getVersionResult != 1) { throw new InvalidOperationException("Getversion Fail"); }
|
||||||
|
ModelVersion = modelVersion;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public List<LPR_Boxes> DetectLicensePlateExistence(Mat image, Rect roi)
|
||||||
|
{
|
||||||
|
List<LPR_Boxes> vLPR_Boxes = [];
|
||||||
|
_ = _wrapperDetection.Model_Wrapper_LPR_Plate(image, roi, ref vLPR_Boxes);
|
||||||
|
return vLPR_Boxes;
|
||||||
|
}
|
||||||
|
public LicensePlateRecognitionMotionData DetectLicensePlateExistence(MotionImageData data)
|
||||||
|
{
|
||||||
|
using var originalImage = data.Image;
|
||||||
|
Rect rectangleROI = data.ROI;
|
||||||
|
List<LPR_Boxes> vLPR_Boxes = [];
|
||||||
|
|
||||||
|
var isLicensePlateExist = _wrapperDetection.Model_Wrapper_LPR_Plate(originalImage, rectangleROI, ref vLPR_Boxes);
|
||||||
|
bool value = isLicensePlateExist == 1 && vLPR_Boxes.Exists(x => x.w * x.h > data.MinimumArea && x.w * x.h < data.MaximumArea);
|
||||||
|
var motionData = new LicensePlateRecognitionMotionData(originalImage, data.CreatedTime, vLPR_Boxes, value);
|
||||||
|
|
||||||
|
return motionData;
|
||||||
|
}
|
||||||
|
public LPR_RESULT_RST DetectLicensePlateCode(Mat image, List<LPR_Boxes> lpList)
|
||||||
|
{
|
||||||
|
var mLPR_Boxes = lpList.OrderByDescending(box => box.y).First();
|
||||||
|
Rect rect_LP = new((int)mLPR_Boxes.x, (int)mLPR_Boxes.y, (int)mLPR_Boxes.w, (int)mLPR_Boxes.h);
|
||||||
|
LPR_RESULT_RST mLPR_RESULT_RST = new();
|
||||||
|
_ = _wrapperRecognition.Model_Wrapper_LPR_Code(image, rect_LP, ref mLPR_RESULT_RST);
|
||||||
|
return mLPR_RESULT_RST;
|
||||||
|
}
|
||||||
|
public LPR_RESULT_RST DetectLicensePlateCode(Mat image, LPR_Boxes mLPR_Boxes)
|
||||||
|
{
|
||||||
|
Rect rect_LP = new((int)mLPR_Boxes.x, (int)mLPR_Boxes.y, (int)mLPR_Boxes.w, (int)mLPR_Boxes.h);
|
||||||
|
LPR_RESULT_RST mLPR_RESULT_RST = new();
|
||||||
|
_ = _wrapperRecognition.Model_Wrapper_LPR_Code(image, rect_LP, ref mLPR_RESULT_RST);
|
||||||
|
return mLPR_RESULT_RST;
|
||||||
|
}
|
||||||
|
public void DetectLicensePlateCode(LicensePlateRecognitionMotionData motionData)
|
||||||
|
{
|
||||||
|
if (!motionData.IsLicensePlateExist) { return; }
|
||||||
|
var mLPR_Boxes = motionData.SelectedTrustedLprBox;
|
||||||
|
Rect rect_LP = new((int)mLPR_Boxes.x, (int)mLPR_Boxes.y, (int)mLPR_Boxes.w, (int)mLPR_Boxes.h);
|
||||||
|
LPR_RESULT_RST mLPR_RESULT_RST = new();
|
||||||
|
var isExistCode = _wrapperRecognition.Model_Wrapper_LPR_Code(motionData.OriginalImage, rect_LP, ref mLPR_RESULT_RST);
|
||||||
|
if (isExistCode != 1) { return; }
|
||||||
|
|
||||||
|
//motionData.DrawLicensePlateCharacterBoxes(mLPR_RESULT_RST.vLPR_Boxes, mLPR_Boxes.x, mLPR_Boxes.y);
|
||||||
|
motionData.AddLicensePlateCode(mLPR_RESULT_RST.mRST_Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_wrapperDetection?.Dispose();
|
||||||
|
_wrapperRecognition?.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Inno.LPR/LicensePlateRecognitionMotionData.cs
Normal file
45
Inno.LPR/LicensePlateRecognitionMotionData.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using DeepLearning_Model_Sharp;
|
||||||
|
using OpenCvSharp;
|
||||||
|
|
||||||
|
namespace Inno.LPR
|
||||||
|
{
|
||||||
|
public class LicensePlateRecognitionMotionData
|
||||||
|
{
|
||||||
|
public Mat OriginalImage { get; private set; }
|
||||||
|
public DateTime ImageCreatedTime { get; init; }
|
||||||
|
public List<LPR_Boxes> DetectedLprBoxList { get; init; } = [];
|
||||||
|
public LPR_Boxes SelectedTrustedLprBox { get; private set; }
|
||||||
|
public bool IsLicensePlateExist { get; private set; } = false;
|
||||||
|
public List<string> LicensePlateCodeList { get; set; } = [];
|
||||||
|
public string LicensePlateCode { get; private set; } = string.Empty;
|
||||||
|
public int FrameNumber { get; set; } = -1;
|
||||||
|
|
||||||
|
public LicensePlateRecognitionMotionData(Mat originalImage, DateTime imageCreatedTime, List<LPR_Boxes> lprBoxes, bool isLicensePlateExist)
|
||||||
|
{
|
||||||
|
OriginalImage = originalImage.Clone();
|
||||||
|
ImageCreatedTime = imageCreatedTime;
|
||||||
|
DetectedLprBoxList.AddRange(lprBoxes);
|
||||||
|
if (DetectedLprBoxList.Count > 0)
|
||||||
|
{
|
||||||
|
SelectedTrustedLprBox = DetectedLprBoxList.OrderByDescending(box => box.y).First();
|
||||||
|
}
|
||||||
|
IsLicensePlateExist = isLicensePlateExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawLicensePlateCharacterBoxes(List<LPR_Boxes> boxes, uint coordinateOriginX, uint coordinateOriginY)
|
||||||
|
{
|
||||||
|
if (!IsLicensePlateExist) { return; }
|
||||||
|
|
||||||
|
//foreach (_LPR_Boxes mLPR_Chars in boxes)
|
||||||
|
//{
|
||||||
|
// Rect rect_Char = new((int)mLPR_Chars.x + (int)coordinateOriginX, (int)mLPR_Chars.y + (int)coordinateOriginY,
|
||||||
|
// (int)mLPR_Chars.w, (int)mLPR_Chars.h);
|
||||||
|
// Cv2.Rectangle(LicensePlateDisplayImage, rect_Char, Scalar.Blue, 1);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
public void AddLicensePlateCode(string lpCode)
|
||||||
|
{
|
||||||
|
LicensePlateCode = lpCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Inno.LPR/MotionImageData.cs
Normal file
13
Inno.LPR/MotionImageData.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using OpenCvSharp;
|
||||||
|
|
||||||
|
namespace Inno.LPR
|
||||||
|
{
|
||||||
|
public class MotionImageData(Mat image, Rect roi, int minimumArea, int maximumArea)
|
||||||
|
{
|
||||||
|
public Mat Image { get; private set; } = image;
|
||||||
|
public DateTime CreatedTime { get; private set; } = DateTime.Now;
|
||||||
|
public Rect ROI { get; private set; } = roi;
|
||||||
|
public int MinimumArea { get; private set; } = minimumArea;
|
||||||
|
public int MaximumArea { get; private set; } = maximumArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
LPR_Manager.slnx
Normal file
12
LPR_Manager.slnx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<Solution>
|
||||||
|
<Configurations>
|
||||||
|
<Platform Name="Any CPU" />
|
||||||
|
<Platform Name="x64" />
|
||||||
|
</Configurations>
|
||||||
|
<Project Path="Inno.LPR/Inno.LPR.csproj">
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="LPR_Manager/LPR_Manager.csproj">
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
</Solution>
|
||||||
25
LPR_Manager/App.xaml
Normal file
25
LPR_Manager/App.xaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<Application x:Class="LPR_Manager.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="Indigo" SecondaryColor="Lime" />
|
||||||
|
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Button.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Card.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ListBox.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBox.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.DataGrid.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ProgressBar.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ComboBox.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.CheckBox.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ToggleButton.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.RadioButton.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TabControl.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
62
LPR_Manager/App.xaml.cs
Normal file
62
LPR_Manager/App.xaml.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace LPR_Manager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
protected override async void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
// 0. Initialize Logging
|
||||||
|
var configService = new Service.ConfigService();
|
||||||
|
var sysConfig = await configService.LoadSystemConfigAsync();
|
||||||
|
Service.LogService.ConfigureLogging(sysConfig.LogMode);
|
||||||
|
|
||||||
|
// 1. Create Splash Screen
|
||||||
|
var splash = new View.SplashScreen();
|
||||||
|
|
||||||
|
// 2. Show Main Window immediately
|
||||||
|
var main = new MainWindow();
|
||||||
|
this.MainWindow = main;
|
||||||
|
main.Show();
|
||||||
|
|
||||||
|
// Center Splash on Main Window
|
||||||
|
if (main.WindowState == WindowState.Maximized)
|
||||||
|
{
|
||||||
|
// If maximized, center on screen is usually correct, but let's ensure
|
||||||
|
splash.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Calculate center relative to Main
|
||||||
|
splash.WindowStartupLocation = WindowStartupLocation.Manual;
|
||||||
|
splash.Left = main.Left + (main.Width - splash.Width) / 2;
|
||||||
|
splash.Top = main.Top + (main.Height - splash.Height) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
splash.Topmost = true; // Re-enforce topmost just in case
|
||||||
|
splash.Show(); // Show Splash AFTER calculating position
|
||||||
|
|
||||||
|
// 3. Wait for MainViewModel Init OR 10s Timeout
|
||||||
|
if (main.DataContext is ViewModel.MainViewModel vm)
|
||||||
|
{
|
||||||
|
var timeoutTask = Task.Delay(10000); // 10 seconds max
|
||||||
|
var initTask = vm.InitializationTask;
|
||||||
|
|
||||||
|
await Task.WhenAny(initTask, timeoutTask);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
splash.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
LPR_Manager/AssemblyInfo.cs
Normal file
10
LPR_Manager/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)
|
||||||
|
)]
|
||||||
13
LPR_Manager/Code/GlobalObject/BaseInfo.cs
Normal file
13
LPR_Manager/Code/GlobalObject/BaseInfo.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace LPR_Manager.Code.GlobalObject
|
||||||
|
{
|
||||||
|
public static class BaseInfo
|
||||||
|
{
|
||||||
|
public static string DeviceCode { get; set; } = "D0001"; // Default mock
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MainDeviceItem
|
||||||
|
{
|
||||||
|
public static string LEDDisplayMessageNoMember { get; set; } = "미등록차량";
|
||||||
|
public static string LEDDisplayMessageMember { get; set; } = "정기권차량";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Converters
|
||||||
|
{
|
||||||
|
public class InverseBooleanToVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool boolValue)
|
||||||
|
{
|
||||||
|
return boolValue ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
}
|
||||||
|
return Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
LPR_Manager/Icon/CI.ico
Normal file
BIN
LPR_Manager/Icon/CI.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
BIN
LPR_Manager/Icon/CI.png
Normal file
BIN
LPR_Manager/Icon/CI.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 604 KiB |
52
LPR_Manager/LPR_Manager.csproj
Normal file
52
LPR_Manager/LPR_Manager.csproj
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<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;x64</Platforms>
|
||||||
|
<ApplicationIcon>Icon\CI.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="Icon\CI.png" />
|
||||||
|
<Resource Include="Icon\CI.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="MaterialDesignColors" Version="5.3.0" />
|
||||||
|
<PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
|
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
|
||||||
|
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
|
||||||
|
<PackageReference Include="OpenCvSharp4.WpfExtensions" Version="4.11.0.20250507" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Inno.LPR\Inno.LPR.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Libraries\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Libraries\NtcClientAPI4Net.dll">
|
||||||
|
<Link>NtcClientAPI4Net.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
264
LPR_Manager/LPR_Manager_0jqb0m4x_wpftmp.csproj
Normal file
264
LPR_Manager/LPR_Manager_0jqb0m4x_wpftmp.csproj
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>LPR_Manager</AssemblyName>
|
||||||
|
<IntermediateOutputPath>obj\Debug\</IntermediateOutputPath>
|
||||||
|
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
|
||||||
|
<MSBuildProjectExtensionsPath>D:\Documents\Projects\Parking\Center\LPR_Manager\LPR_Manager\obj\</MSBuildProjectExtensionsPath>
|
||||||
|
<_TargetAssemblyProjectName>LPR_Manager</_TargetAssemblyProjectName>
|
||||||
|
<RootNamespace>LPR_Manager</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="..\..\..\NtcSDK\x64\release_static_mt\NtcClientAPI4Net.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="MaterialDesignColors" Version="5.3.0" />
|
||||||
|
<PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
|
||||||
|
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
|
||||||
|
<PackageReference Include="OpenCvSharp4.WpfExtensions" Version="4.11.0.20250507" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Inno.LPR\Inno.LPR.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\Accessibility.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\communitytoolkit.mvvm\8.4.0\lib\net8.0\CommunityToolkit.Mvvm.dll" />
|
||||||
|
<ReferencePath Include="D:\Documents\Projects\Parking\Center\LPR_Manager\Inno.LPR\bin\Debug\net8.0-windows\Inno.LPR.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\materialdesigncolors\5.3.0\lib\net8.0-windows7.0\MaterialDesignColors.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\materialdesignthemes\5.3.0\lib\net8.0-windows7.0\MaterialDesignThemes.Wpf.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\Microsoft.CSharp.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\Microsoft.VisualBasic.Core.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\Microsoft.VisualBasic.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\Microsoft.Win32.Primitives.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\Microsoft.Win32.Registry.AccessControl.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\Microsoft.Win32.Registry.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\Microsoft.Win32.SystemEvents.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\microsoft.xaml.behaviors.wpf\1.1.77\lib\net6.0-windows7.0\Microsoft.Xaml.Behaviors.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\mscorlib.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\netstandard.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\opencvsharp4\4.11.0.20250507\lib\net6.0\OpenCvSharp.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\opencvsharp4.wpfextensions\4.11.0.20250507\lib\net6.0\OpenCvSharp.WpfExtensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationCore.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationFramework.Aero.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationFramework.Aero2.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationFramework.AeroLite.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationFramework.Classic.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationFramework.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationFramework.Luna.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationFramework.Royale.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\PresentationUI.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\ReachFramework.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\serilog\4.3.0\lib\net8.0\Serilog.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\serilog.sinks.file\7.0.0\lib\net8.0\Serilog.Sinks.File.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.AppContext.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Buffers.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.CodeDom.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Collections.Concurrent.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Collections.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Collections.Immutable.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Collections.NonGeneric.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Collections.Specialized.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ComponentModel.Annotations.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ComponentModel.DataAnnotations.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ComponentModel.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ComponentModel.EventBasedAsync.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ComponentModel.Primitives.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ComponentModel.TypeConverter.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Configuration.ConfigurationManager.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Configuration.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Console.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Core.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Data.Common.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Data.DataSetExtensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Data.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.Contracts.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.Debug.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.DiagnosticSource.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.EventLog.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.FileVersionInfo.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.PerformanceCounter.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.Process.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.StackTrace.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.TextWriterTraceListener.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.Tools.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.TraceSource.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Diagnostics.Tracing.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.DirectoryServices.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.dll" />
|
||||||
|
<ReferencePath Include="C:\Users\jabez\.nuget\packages\system.drawing.common\8.0.11\lib\net8.0\System.Drawing.Common.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Drawing.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Drawing.Primitives.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Dynamic.Runtime.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Formats.Asn1.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Formats.Tar.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Globalization.Calendars.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Globalization.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Globalization.Extensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.Compression.Brotli.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.Compression.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.Compression.FileSystem.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.Compression.ZipFile.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.FileSystem.AccessControl.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.FileSystem.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.FileSystem.DriveInfo.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.FileSystem.Primitives.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.FileSystem.Watcher.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.IsolatedStorage.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.MemoryMappedFiles.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.IO.Packaging.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.Pipes.AccessControl.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.Pipes.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.IO.UnmanagedMemoryStream.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Linq.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Linq.Expressions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Linq.Parallel.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Linq.Queryable.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Memory.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Http.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Http.Json.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.HttpListener.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Mail.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.NameResolution.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.NetworkInformation.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Ping.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Primitives.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Quic.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Requests.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Security.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.ServicePoint.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.Sockets.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.WebClient.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.WebHeaderCollection.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.WebProxy.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.WebSockets.Client.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Net.WebSockets.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Numerics.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Numerics.Vectors.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ObjectModel.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Printing.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.DispatchProxy.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.Emit.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.Emit.ILGeneration.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.Emit.Lightweight.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.Extensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.Metadata.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.Primitives.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Reflection.TypeExtensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Resources.Extensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Resources.Reader.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Resources.ResourceManager.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Resources.Writer.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.CompilerServices.Unsafe.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.CompilerServices.VisualC.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Extensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Handles.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.InteropServices.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.InteropServices.JavaScript.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.InteropServices.RuntimeInformation.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Intrinsics.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Loader.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Numerics.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Serialization.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Serialization.Formatters.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Serialization.Json.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Serialization.Primitives.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Runtime.Serialization.Xml.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.AccessControl.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Claims.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.Algorithms.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.Cng.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.Csp.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.Encoding.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.OpenSsl.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.Pkcs.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.Primitives.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.ProtectedData.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.X509Certificates.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Security.Cryptography.Xml.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Security.Permissions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Principal.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.Principal.Windows.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Security.SecureString.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ServiceModel.Web.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ServiceProcess.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Text.Encoding.CodePages.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Text.Encoding.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Text.Encoding.Extensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Text.Encodings.Web.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Text.Json.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Text.RegularExpressions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Threading.AccessControl.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.Channels.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.Overlapped.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.Tasks.Dataflow.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.Tasks.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.Tasks.Extensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.Tasks.Parallel.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.Thread.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.ThreadPool.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Threading.Timer.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Transactions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Transactions.Local.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.ValueTuple.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Web.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Web.HttpUtility.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Windows.Controls.Ribbon.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Windows.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Windows.Extensions.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Windows.Input.Manipulations.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Windows.Presentation.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\System.Xaml.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.Linq.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.ReaderWriter.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.Serialization.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.XDocument.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.XmlDocument.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.XmlSerializer.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.XPath.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\ref\net8.0\System.Xml.XPath.XDocument.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\UIAutomationClient.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\UIAutomationClientSideProviders.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\UIAutomationProvider.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\UIAutomationTypes.dll" />
|
||||||
|
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.23\ref\net8.0\WindowsBase.dll" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="D:\Documents\Projects\Parking\Center\LPR_Manager\LPR_Manager\obj\Debug\net8.0-windows\MainWindow.g.cs" />
|
||||||
|
<Compile Include="D:\Documents\Projects\Parking\Center\LPR_Manager\LPR_Manager\obj\Debug\net8.0-windows\App.g.cs" />
|
||||||
|
<Compile Include="D:\Documents\Projects\Parking\Center\LPR_Manager\LPR_Manager\obj\Debug\net8.0-windows\LPR_Manager_Content.g.cs" />
|
||||||
|
<Compile Include="D:\Documents\Projects\Parking\Center\LPR_Manager\LPR_Manager\obj\Debug\net8.0-windows\GeneratedInternalTypeHelper.g.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Analyzer Include="C:\Program Files\dotnet\sdk\10.0.102\Sdks\Microsoft.NET.Sdk\targets\..\analyzers\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll" />
|
||||||
|
<Analyzer Include="C:\Program Files\dotnet\sdk\10.0.102\Sdks\Microsoft.NET.Sdk\targets\..\analyzers\Microsoft.CodeAnalysis.NetAnalyzers.dll" />
|
||||||
|
<Analyzer Include="C:\Users\jabez\.nuget\packages\communitytoolkit.mvvm\8.4.0\analyzers\dotnet\roslyn4.12\cs\CommunityToolkit.Mvvm.CodeFixers.dll" />
|
||||||
|
<Analyzer Include="C:\Users\jabez\.nuget\packages\communitytoolkit.mvvm\8.4.0\analyzers\dotnet\roslyn4.12\cs\CommunityToolkit.Mvvm.SourceGenerators.dll" />
|
||||||
|
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\analyzers/dotnet/cs/Microsoft.Interop.ComInterfaceGenerator.dll" />
|
||||||
|
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\analyzers/dotnet/cs/Microsoft.Interop.JavaScript.JSImportGenerator.dll" />
|
||||||
|
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\analyzers/dotnet/cs/Microsoft.Interop.LibraryImportGenerator.dll" />
|
||||||
|
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\analyzers/dotnet/cs/Microsoft.Interop.SourceGeneration.dll" />
|
||||||
|
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\analyzers/dotnet/cs/System.Text.Json.SourceGeneration.dll" />
|
||||||
|
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.23\analyzers/dotnet/cs/System.Text.RegularExpressions.Generator.dll" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
|
||||||
|
</Project>
|
||||||
312
LPR_Manager/MainWindow.xaml
Normal file
312
LPR_Manager/MainWindow.xaml
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
<Window x:Class="LPR_Manager.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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:vm="clr-namespace:LPR_Manager.ViewModel"
|
||||||
|
xmlns:view="clr-namespace:LPR_Manager.View"
|
||||||
|
xmlns:converters="clr-namespace:LPR_Manager.Converters"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
|
||||||
|
TextElement.FontWeight="Regular"
|
||||||
|
TextElement.FontSize="13"
|
||||||
|
TextOptions.TextFormattingMode="Ideal"
|
||||||
|
TextOptions.TextRenderingMode="Auto"
|
||||||
|
Background="{DynamicResource MaterialDesignPaper}"
|
||||||
|
FontFamily="{DynamicResource MaterialDesignFont}"
|
||||||
|
Title="LPR Manager" Height="823" Width="1505" Icon="pack://application:,,,/Icon/CI.ico">
|
||||||
|
<Window.Resources>
|
||||||
|
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||||
|
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/>
|
||||||
|
</Window.Resources>
|
||||||
|
<Window.DataContext>
|
||||||
|
<vm:MainViewModel/>
|
||||||
|
</Window.DataContext>
|
||||||
|
<Grid Background="#F8F9FB">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<!-- Main Content Area -->
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 1. Sidebar -->
|
||||||
|
<Border Grid.Column="0" Background="White" BorderBrush="#EEEEEE" BorderThickness="0,0,1,0">
|
||||||
|
<DockPanel LastChildFill="True" Width="70">
|
||||||
|
<!-- Logo -->
|
||||||
|
<StackPanel DockPanel.Dock="Top" Margin="0,30,0,50" HorizontalAlignment="Center">
|
||||||
|
<Border Width="42" Height="42" CornerRadius="10" Background="{DynamicResource PrimaryHueMidBrush}">
|
||||||
|
<TextBlock Text="P" Foreground="White" FontSize="26" FontWeight="ExtraBold" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,-2,0,0"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Settings at Bottom -->
|
||||||
|
<Button DockPanel.Dock="Bottom" Style="{DynamicResource MaterialDesignFlatButton}"
|
||||||
|
Foreground="#B0BEC5" Margin="0,20" Padding="0" Height="50"
|
||||||
|
Command="{Binding GoToSettingsCommand}">
|
||||||
|
<materialDesign:PackIcon Kind="SettingsOutline" Width="26" Height="26"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Navigation List -->
|
||||||
|
<ListBox SelectedIndex="{Binding SelectedTabIndex}"
|
||||||
|
Style="{DynamicResource MaterialDesignNavigationListBox}"
|
||||||
|
ItemContainerStyle="{DynamicResource MaterialDesignNavigationListBoxItem}"
|
||||||
|
BorderThickness="0" Background="Transparent"
|
||||||
|
SelectionMode="Single">
|
||||||
|
<ListBox.Resources>
|
||||||
|
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignNavigationListBoxItem}">
|
||||||
|
<Setter Property="Padding" Value="0,18"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</ListBox.Resources>
|
||||||
|
<ListBoxItem ToolTip="Dashboard">
|
||||||
|
<materialDesign:PackIcon Kind="ViewDashboard" Width="26" Height="26" HorizontalAlignment="Center"/>
|
||||||
|
</ListBoxItem>
|
||||||
|
<ListBoxItem ToolTip="Analytics">
|
||||||
|
<materialDesign:PackIcon Kind="MonitorDashboard" Width="26" Height="26" HorizontalAlignment="Center" Cursor=""/>
|
||||||
|
</ListBoxItem>
|
||||||
|
<ListBoxItem ToolTip="History">
|
||||||
|
<materialDesign:PackIcon Kind="History" Width="26" Height="26" HorizontalAlignment="Center"/>
|
||||||
|
</ListBoxItem>
|
||||||
|
<ListBoxItem ToolTip="Notifications">
|
||||||
|
<Grid>
|
||||||
|
<materialDesign:PackIcon Kind="BellOutline" Width="26" Height="26" HorizontalAlignment="Center"
|
||||||
|
Visibility="{Binding DataContext.HasCriticalErrors, RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource InverseBooleanToVisibilityConverter}}"/>
|
||||||
|
<materialDesign:PackIcon Kind="BellAlert" Width="26" Height="26" HorizontalAlignment="Center" Foreground="#FF5252"
|
||||||
|
Visibility="{Binding DataContext.HasCriticalErrors, RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||||
|
</Grid>
|
||||||
|
</ListBoxItem>
|
||||||
|
<ListBoxItem ToolTip="Settings">
|
||||||
|
<materialDesign:PackIcon Kind="Cog" Width="26" Height="26" HorizontalAlignment="Center"/>
|
||||||
|
</ListBoxItem>
|
||||||
|
</ListBox>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 2. Main Content Container -->
|
||||||
|
<Grid Grid.Column="1">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<!-- Top Header -->
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<!-- Tab Content -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Top Header -->
|
||||||
|
<Border Grid.Row="0" Background="White" Padding="24,14" BorderBrush="#EEEEEE" BorderThickness="0,0,0,1">
|
||||||
|
<DockPanel LastChildFill="True">
|
||||||
|
<!-- Right side User Profile -->
|
||||||
|
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right" VerticalAlignment="Center">
|
||||||
|
<!-- Session Timer -->
|
||||||
|
<TextBlock Text="{Binding SessionTimeRemaining}"
|
||||||
|
Foreground="#FF8A65" FontWeight="Bold" FontSize="14"
|
||||||
|
VerticalAlignment="Center" Margin="0,0,16,0"
|
||||||
|
Visibility="{Binding SessionTimeRemaining, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"/>
|
||||||
|
|
||||||
|
<StackPanel VerticalAlignment="Center" Margin="0,0,14,0" HorizontalAlignment="Right">
|
||||||
|
<TextBlock Text="{Binding UserName}" FontSize="13" FontWeight="Bold" Foreground="#263238" HorizontalAlignment="Right"/>
|
||||||
|
<TextBlock Text="{Binding UserRole}" FontSize="10" FontWeight="Bold" Foreground="#9FA8DA" HorizontalAlignment="Right"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Style="{DynamicResource MaterialDesignFlatButton}" Padding="0" Margin="0" Width="40" Height="40"
|
||||||
|
Command="{Binding LoginCommand}" ToolTip="Click to Toggle Login/Logout">
|
||||||
|
<Border Width="40" Height="40" CornerRadius="20" Background="#F1F3F4">
|
||||||
|
<materialDesign:PackIcon Kind="Account" Width="28" Height="28" Foreground="#B0BEC5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Left side Title & Status -->
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="Multi-Site Command" FontSize="18" FontWeight="Bold" Foreground="#263238" VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<Border Width="1" Height="24" Background="#EEEEEE" Margin="24,0"/>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<Ellipse Width="8" Height="8" Fill="#4CAF50" Margin="0,1,8,0"/>
|
||||||
|
<TextBlock Text="IIS STATUS: " Foreground="#90A4AE" FontSize="12"/>
|
||||||
|
<TextBlock Text="{Binding HealthStatus}" Foreground="#4CAF50" FontSize="12" FontWeight="Bold"/>
|
||||||
|
<TextBlock Text="{Binding CurrentTime}" Foreground="#90A4AE" FontSize="12" Margin="20,0,0,0" FontFamily="Segoe UI Semibold"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Main Tab Control -->
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<!-- Navigation Arrows (Mockup UI) -->
|
||||||
|
<Button HorizontalAlignment="Left" VerticalAlignment="Center" Margin="12,0"
|
||||||
|
Style="{DynamicResource MaterialDesignFloatingActionMiniButton}"
|
||||||
|
Background="White" Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||||
|
Width="36" Height="36" BorderThickness="0"
|
||||||
|
Click="OnScrollLeft">
|
||||||
|
<materialDesign:PackIcon Kind="ChevronLeft" Width="20" Height="20"/>
|
||||||
|
<Button.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Opacity="0.08"/>
|
||||||
|
</Button.Effect>
|
||||||
|
</Button>
|
||||||
|
<Button HorizontalAlignment="Right" VerticalAlignment="Center" Margin="12,0"
|
||||||
|
Style="{DynamicResource MaterialDesignFloatingActionMiniButton}"
|
||||||
|
Background="White" Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||||
|
Width="36" Height="36" BorderThickness="0"
|
||||||
|
Click="OnScrollRight">
|
||||||
|
<materialDesign:PackIcon Kind="ChevronRight" Width="20" Height="20"/>
|
||||||
|
<Button.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Opacity="0.08"/>
|
||||||
|
</Button.Effect>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<materialDesign:TransitioningContent Margin="60,20">
|
||||||
|
<TabControl SelectedIndex="{Binding SelectedTabIndex}"
|
||||||
|
Style="{DynamicResource MaterialDesignTabControl}"
|
||||||
|
BorderThickness="0">
|
||||||
|
<TabControl.ItemContainerStyle>
|
||||||
|
<Style TargetType="TabItem">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</Style>
|
||||||
|
</TabControl.ItemContainerStyle>
|
||||||
|
|
||||||
|
<!-- 0: DASHBOARD -->
|
||||||
|
<TabItem>
|
||||||
|
<ScrollViewer x:Name="DashboardScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled">
|
||||||
|
<ItemsControl ItemsSource="{Binding Lanes}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<view:LprLaneControl/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- 1: ANALYTICS -->
|
||||||
|
<TabItem>
|
||||||
|
<Grid Margin="20">
|
||||||
|
<DockPanel>
|
||||||
|
<Border DockPanel.Dock="Top" Margin="0,0,0,20">
|
||||||
|
<TextBlock Text="Recognition Performance Analytics" FontSize="20" FontWeight="Bold" Foreground="#37474F"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<ItemsControl ItemsSource="{Binding Lanes}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<materialDesign:Card Width="300" Margin="10" Padding="16" UniformCornerRadius="8">
|
||||||
|
<StackPanel>
|
||||||
|
<DockPanel LastChildFill="True" Margin="0,0,0,10">
|
||||||
|
<TextBlock Text="{Binding LaneName}" FontWeight="Bold" Foreground="#455A64" FontSize="16"/>
|
||||||
|
<TextBlock Text="{Binding SuccessRate, StringFormat={}{0:F1}%}" HorizontalAlignment="Right" FontWeight="Bold" Foreground="{DynamicResource PrimaryHueMidBrush}"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<Grid Height="10" Margin="0,0,0,15">
|
||||||
|
<ProgressBar Value="{Binding SuccessRate}" Maximum="100" Height="10"
|
||||||
|
Background="#ECEFF1" BorderThickness="0"
|
||||||
|
Foreground="{DynamicResource PrimaryHueMidBrush}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="Total" FontSize="11" Foreground="#90A4AE"/>
|
||||||
|
<TextBlock Text="{Binding TotalRecognitions}" FontSize="16" FontWeight="SemiBold"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="1">
|
||||||
|
<TextBlock Text="Success" FontSize="11" Foreground="#90A4AE"/>
|
||||||
|
<TextBlock Text="{Binding SuccessCount}" FontSize="16" FontWeight="SemiBold" Foreground="#4CAF50"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="2">
|
||||||
|
<TextBlock Text="Fail" FontSize="11" Foreground="#90A4AE"/>
|
||||||
|
<TextBlock Text="{Binding FailCount}" FontSize="16" FontWeight="SemiBold" Foreground="#EF5350"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</materialDesign:Card>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- 2: HISTORY -->
|
||||||
|
<TabItem>
|
||||||
|
<Grid Margin="20">
|
||||||
|
<materialDesign:Card Padding="0" UniformCornerRadius="12">
|
||||||
|
<DockPanel>
|
||||||
|
<Border DockPanel.Dock="Top" Background="#F1F3F4" Padding="20,12">
|
||||||
|
<TextBlock Text="System Activity Logs" FontWeight="Bold" Foreground="#37474F"/>
|
||||||
|
</Border>
|
||||||
|
<ListBox ItemsSource="{Binding Logs}" Padding="10" BorderThickness="0">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</DockPanel>
|
||||||
|
</materialDesign:Card>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- 3: ALERTS / NOTIFICATIONS -->
|
||||||
|
<TabItem>
|
||||||
|
<Grid Margin="20">
|
||||||
|
<DockPanel>
|
||||||
|
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,20">
|
||||||
|
<Button DockPanel.Dock="Right" Content="Clear All" Command="{Binding ClearErrorsCommand}" Style="{DynamicResource MaterialDesignOutlinedButton}"/>
|
||||||
|
<StackPanel VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="Critical Errors" FontSize="20" FontWeight="Bold" Foreground="#D32F2F"/>
|
||||||
|
<TextBlock Text="Review and resolve critical system issues" FontSize="12" Foreground="#90A4AE"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<ScrollViewer>
|
||||||
|
<ItemsControl ItemsSource="{Binding ErrorLogs}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<materialDesign:Card Margin="0,0,0,10" Padding="16" UniformCornerRadius="6" BorderBrush="#FFEBEE" BorderThickness="1">
|
||||||
|
<DockPanel>
|
||||||
|
<materialDesign:PackIcon Kind="AlertCircleOutline" Width="24" Height="24" Foreground="#D32F2F" Margin="0,0,16,0" VerticalAlignment="Top"/>
|
||||||
|
<StackPanel>
|
||||||
|
<DockPanel Margin="0,0,0,4">
|
||||||
|
<TextBlock Text="{Binding Timestamp, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}" Foreground="#90A4AE" FontSize="12" DockPanel.Dock="Right"/>
|
||||||
|
<TextBlock Text="System Error" FontWeight="Bold" Foreground="#37474F"/>
|
||||||
|
</DockPanel>
|
||||||
|
<TextBlock Text="{Binding Message}" TextWrapping="Wrap" Foreground="#546E7A"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
</materialDesign:Card>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- 4: SETTINGS -->
|
||||||
|
<TabItem>
|
||||||
|
<view:SettingsControl/>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
</materialDesign:TransitioningContent>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
45
LPR_Manager/MainWindow.xaml.cs
Normal file
45
LPR_Manager/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace LPR_Manager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPreviewMouseMove(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPreviewMouseMove(e);
|
||||||
|
if (DataContext is ViewModel.MainViewModel vm) vm.ResetIdleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPreviewKeyDown(e);
|
||||||
|
if (DataContext is ViewModel.MainViewModel vm) vm.ResetIdleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScrollLeft(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DashboardScrollViewer.ScrollToHorizontalOffset(DashboardScrollViewer.HorizontalOffset - 380);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScrollRight(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DashboardScrollViewer.ScrollToHorizontalOffset(DashboardScrollViewer.HorizontalOffset + 380);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
LPR_Manager/Model/AppLogEntry.cs
Normal file
26
LPR_Manager/Model/AppLogEntry.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model
|
||||||
|
{
|
||||||
|
public enum LogSeverity
|
||||||
|
{
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppLogEntry
|
||||||
|
{
|
||||||
|
public DateTime Timestamp { get; set; } = DateTime.Now;
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
public LogSeverity Severity { get; set; } = LogSeverity.Info;
|
||||||
|
public string Source { get; set; } = "System";
|
||||||
|
|
||||||
|
public string DisplayTimestamp => Timestamp.ToString("HH:mm:ss");
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[{DisplayTimestamp}] [{Severity}] {Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
LPR_Manager/Model/Device/GateController.cs
Normal file
64
LPR_Manager/Model/Device/GateController.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
//using Inno.Communication.TCP;
|
||||||
|
using LPR_Manager.Model.Device.Interfaces;
|
||||||
|
using LPR_Manager.Protocol.Gate;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model.Device
|
||||||
|
{
|
||||||
|
public class GateController : IGateController
|
||||||
|
{
|
||||||
|
// private readonly TcpClientConnector _connector = new();
|
||||||
|
private readonly string _ip;
|
||||||
|
private readonly int _port;
|
||||||
|
private CancellationTokenSource _cts;
|
||||||
|
|
||||||
|
public GateStatus Status { get; private set; } = GateStatus.CLOSED;
|
||||||
|
|
||||||
|
// public bool IsConnected => _connector.IsConnected;
|
||||||
|
|
||||||
|
public GateController(string ip, int port)
|
||||||
|
{
|
||||||
|
_ip = ip;
|
||||||
|
_port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ConnectAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
// await _connector.ConnectAsync(_ip, _port, 2000, _cts.Token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
// _connector.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Open()
|
||||||
|
{
|
||||||
|
SendCommand(GateSendCMD.OPEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
SendCommand(GateSendCMD.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendCommand(GateSendCMD cmd)
|
||||||
|
{
|
||||||
|
var cmdStr = FPTV1.GetCommand(cmd);
|
||||||
|
//_connector.WriteAsync(cmdStr, Encoding.ASCII, CancellationToken.None).Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
LPR_Manager/Model/Device/Interfaces/ICmsClient.cs
Normal file
14
LPR_Manager/Model/Device/Interfaces/ICmsClient.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using LPR_Manager.Model.Device.SubClass;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model.Device.Interfaces
|
||||||
|
{
|
||||||
|
public interface ICmsClient
|
||||||
|
{
|
||||||
|
void SendResult(LPRDataSet data);
|
||||||
|
bool IsConnected { get; }
|
||||||
|
Task<bool> ConnectAsync();
|
||||||
|
void Disconnect();
|
||||||
|
event EventHandler<CMSDataSet> OnCommandReceived;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
LPR_Manager/Model/Device/Interfaces/IGateController.cs
Normal file
13
LPR_Manager/Model/Device/Interfaces/IGateController.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using LPR_Manager.Model;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model.Device.Interfaces
|
||||||
|
{
|
||||||
|
public interface IGateController
|
||||||
|
{
|
||||||
|
void Open();
|
||||||
|
void Close();
|
||||||
|
GateStatus Status { get; }
|
||||||
|
Task<bool> ConnectAsync();
|
||||||
|
void Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
12
LPR_Manager/Model/Device/Interfaces/ILedController.cs
Normal file
12
LPR_Manager/Model/Device/Interfaces/ILedController.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using LPR_Manager.Model;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model.Device.Interfaces
|
||||||
|
{
|
||||||
|
public interface ILedController
|
||||||
|
{
|
||||||
|
void SetText(string top, string bottom, LEDColor topColor = LEDColor.GREEN, LEDColor bottomColor = LEDColor.YELLOW);
|
||||||
|
void Send();
|
||||||
|
Task<bool> ConnectAsync();
|
||||||
|
void Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
80
LPR_Manager/Model/Device/LedController.cs
Normal file
80
LPR_Manager/Model/Device/LedController.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
//using Inno.Communication.TCP;
|
||||||
|
using LPR_Manager.Model.Device.Interfaces;
|
||||||
|
using LPR_Manager.Model.Device.SubClass;
|
||||||
|
using LPR_Manager.Protocol.LED;
|
||||||
|
using LPR_Manager.Model;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model.Device
|
||||||
|
{
|
||||||
|
public class LedController : ILedController
|
||||||
|
{
|
||||||
|
// private readonly TcpClientConnector _connector = new();
|
||||||
|
private readonly string _ip;
|
||||||
|
private readonly int _port;
|
||||||
|
private readonly LEDProtocolType _protocolType;
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
private LEDTextSet _top = new();
|
||||||
|
private LEDTextSet _bottom = new();
|
||||||
|
|
||||||
|
public LedController(string ip, int port, LEDProtocolType protocolType = LEDProtocolType.YJMICRO)
|
||||||
|
{
|
||||||
|
_ip = ip;
|
||||||
|
_port = port;
|
||||||
|
_protocolType = protocolType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public bool IsConnected => _connector.IsConnected;
|
||||||
|
|
||||||
|
public async Task<bool> ConnectAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
// await _connector.ConnectAsync(_ip, _port, 2000, _cts.Token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
// _connector.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetText(string top, string bottom, LEDColor topColor = LEDColor.GREEN, LEDColor bottomColor = LEDColor.YELLOW)
|
||||||
|
{
|
||||||
|
_top.Message = top;
|
||||||
|
_top.Color = topColor;
|
||||||
|
_bottom.Message = bottom;
|
||||||
|
_bottom.Color = bottomColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send()
|
||||||
|
{
|
||||||
|
byte[] sendData;
|
||||||
|
if (_protocolType == LEDProtocolType.UJIN)
|
||||||
|
{
|
||||||
|
sendData = UJIN.GetMatrixMessage(_top, _bottom);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var cmd = YJMICRO.GetCommand(_top, _bottom);
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
sendData = Encoding.GetEncoding(949).GetBytes(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendData != null && sendData.Length > 0)
|
||||||
|
{
|
||||||
|
//_connector.WriteAsync(sendData, CancellationToken.None).Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
LPR_Manager/Model/Device/SubClass/CMSDataSet.cs
Normal file
14
LPR_Manager/Model/Device/SubClass/CMSDataSet.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model.Device.SubClass
|
||||||
|
{
|
||||||
|
public class CMSDataSet
|
||||||
|
{
|
||||||
|
public CMSCMD MainCmd { get; set; }
|
||||||
|
public string CenterID { get; set; }
|
||||||
|
public string Data1 { get; set; }
|
||||||
|
public string Data2 { get; set; }
|
||||||
|
public string Data3 { get; set; }
|
||||||
|
public Enum EnumData { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
LPR_Manager/Model/Device/SubClass/LEDSendTextSet.cs
Normal file
9
LPR_Manager/Model/Device/SubClass/LEDSendTextSet.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace LPR_Manager.Model.Device.SubClass
|
||||||
|
{
|
||||||
|
public class LEDSendTextSet
|
||||||
|
{
|
||||||
|
public LEDSendCMD Cmd { get; set; }
|
||||||
|
public LEDTextSet TopTextSet { get; set; }
|
||||||
|
public LEDTextSet BottomTextSet { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
LPR_Manager/Model/Device/SubClass/LEDTextSet.cs
Normal file
17
LPR_Manager/Model/Device/SubClass/LEDTextSet.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model.Device.SubClass
|
||||||
|
{
|
||||||
|
public class LEDTextSet : ICloneable
|
||||||
|
{
|
||||||
|
public string Message { get; set; } = "";
|
||||||
|
public LEDColor Color { get; set; } = LEDColor.GREEN;
|
||||||
|
public LEDColor Color2 { get; set; } = LEDColor.GREEN; // Used in YJMICRO
|
||||||
|
public TextLocation Location { get; set; } = TextLocation.TOP;
|
||||||
|
|
||||||
|
public object Clone()
|
||||||
|
{
|
||||||
|
return this.MemberwiseClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
LPR_Manager/Model/Device/SubClass/LPRDataSet.cs
Normal file
42
LPR_Manager/Model/Device/SubClass/LPRDataSet.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model.Device.SubClass
|
||||||
|
{
|
||||||
|
public class LPRDataSet : ICloneable
|
||||||
|
{
|
||||||
|
public LPRCMDType LPRCMDType { get; set; } = LPRCMDType.OUTOFCMD;
|
||||||
|
public RFIDCMDType RFIDCMDType { get; set; } = RFIDCMDType.OUTOFCMD;
|
||||||
|
public string RFIDRcvdata { get; set; }
|
||||||
|
public string CMDString { get; set; }
|
||||||
|
public string DeviceID { get; set; }
|
||||||
|
public string TimeRaWData { get; set; }
|
||||||
|
public string ImagePath { get; set; }
|
||||||
|
public DateTime Time { get; set; }
|
||||||
|
public string RCarNumber { get; set; }
|
||||||
|
public bool IsMember { get; set; }
|
||||||
|
public bool IsInsertedCar { get; set; } = false;
|
||||||
|
public bool IsSended { get; set; } = false;
|
||||||
|
public bool IsDone { get; set; } = false;
|
||||||
|
public bool IsDBInserted { get; set; } = false;
|
||||||
|
public bool IsBlackList { get; set; } = false;
|
||||||
|
public bool IsVisitor { get; set; } = false;
|
||||||
|
public bool IsEmergencyVehicle { get; set; } = false;
|
||||||
|
|
||||||
|
public string GetProtocolString()
|
||||||
|
{
|
||||||
|
// P0001,20110719-124841-000002-T1,24누3545,/2011/07/19/12484102S_XXXX.jpg,1234567890123456
|
||||||
|
return $"{DeviceID},{TimeRaWData},{RCarNumber},{ImagePath},{Time:yyyyMMddHHmmss},{RFIDRcvdata}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetRawProtocolString()
|
||||||
|
{
|
||||||
|
// STXF0001 | TMP | LPRS | 20100330 - 003241 - 000002 - T1 | 07저9682 | ETX
|
||||||
|
return $"\u0002{DeviceID}|TMP|LPRS|{TimeRaWData}|{RCarNumber}|\u0003";
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Clone()
|
||||||
|
{
|
||||||
|
return this.MemberwiseClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
LPR_Manager/Model/GlobalEnums.cs
Normal file
28
LPR_Manager/Model/GlobalEnums.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model
|
||||||
|
{
|
||||||
|
public enum GateStatus { OPENDED = 0, OPENING = 1, CLOSED = 2, CLOSING = 3, ERROR = 4, UPLOCK = 5, INITED = 6, CHECKINGSTATUSE = 7 }
|
||||||
|
public enum GateSendCMD { OPEN, CLOSE, STATUS, UPLOCK, UNLOCK, RESET }
|
||||||
|
|
||||||
|
public enum CMSCMD { OUTOFCMD, CONFIRM, GATECTRL, LEDDISPLAY, LEDDISPLAYC, REGREFRESH }
|
||||||
|
|
||||||
|
public enum RunningStatus { NORMAL, ERROR }
|
||||||
|
|
||||||
|
public enum LEDColor { RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }
|
||||||
|
public enum LEDProtocolType { YJMICRO, UJIN }
|
||||||
|
public enum TextLocation { TOP, BOTTOM }
|
||||||
|
public enum LEDSendCMD { NORMALTEXT, REGISTERTEXT, NOREGISTERTEXT, NOREGAFTER, NOREGAFTER2, DAYVALIDATION, REMAINDAYOFREG, TWOCAMPVIOLATION, FIVECAMPVIOLATION, BLACKLIST, APTNER, Extra }
|
||||||
|
public enum LEDMessage { NORMALTOP, NORMALBOTTOM, REGISTERTOP, REGISTERBOTTOM, NOREGISTERTOP, NOREGISTERBOTTOM, NOREGAFTERTOP, NOREGAFTERBOTTOM, NOREGAFTER2TOP, NOREGAFTER2BOTTOM, DAYVALIDATIONTOP, DAYVALIDATIONBOTTOM, REMAINDAYOFREGTOP, REMAINDAYOFREGBOTTOM, TWOCAMPVIOLATIONTOP, TWOCAMPVIOLATIONBOTTOM, FIVECAMPVIOLATIONTOP, FIVECAMPVIOLATIONBOTTOM, BLACKLISTTOP, BLACKLISTBOTTOM, APTNERTOP, APTNERBOTTOM }
|
||||||
|
|
||||||
|
public enum LPRCMDType { OUTOFCMD, NORMAL }
|
||||||
|
public enum RFIDCMDType { OUTOFCMD, NORMAL }
|
||||||
|
|
||||||
|
public enum RecognitionOrder
|
||||||
|
{
|
||||||
|
BottomUp,
|
||||||
|
TopDown,
|
||||||
|
LeftRight,
|
||||||
|
RightLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
21
LPR_Manager/Model/LaneConfig.cs
Normal file
21
LPR_Manager/Model/LaneConfig.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model
|
||||||
|
{
|
||||||
|
public class LaneConfig
|
||||||
|
{
|
||||||
|
public string LaneName { get; set; } = string.Empty;
|
||||||
|
public string LaneId { get; set; } = string.Empty;
|
||||||
|
public string LaneDescription { get; set; } = string.Empty;
|
||||||
|
public string CameraIp { get; set; } = string.Empty;
|
||||||
|
public int CameraPort { get; set; } = 80;
|
||||||
|
public string CameraId { get; set; } = "admin";
|
||||||
|
public string CameraPassword { get; set; } = "Inno!@#$5678";
|
||||||
|
public RecognitionOrder RecognitionOrder { get; set; } = RecognitionOrder.BottomUp;
|
||||||
|
public string RecognitionArea { get; set; } = "0,0,0,0"; // x,y,w,h
|
||||||
|
public string NoPlateCode { get; set; } = "XXXXXXXX"; // "????????", "XXXXXXXX"
|
||||||
|
public bool UseSingleShot { get; set; } = false;
|
||||||
|
public string CmsIp { get; set; } = "127.0.0.1";
|
||||||
|
public int CmsPort { get; set; } = 6000;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
LPR_Manager/Model/LogMode.cs
Normal file
9
LPR_Manager/Model/LogMode.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace LPR_Manager.Model
|
||||||
|
{
|
||||||
|
public enum LogMode
|
||||||
|
{
|
||||||
|
Seq,
|
||||||
|
File,
|
||||||
|
Both
|
||||||
|
}
|
||||||
|
}
|
||||||
18
LPR_Manager/Model/LogModel.cs
Normal file
18
LPR_Manager/Model/LogModel.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model
|
||||||
|
{
|
||||||
|
public class LogModel
|
||||||
|
{
|
||||||
|
public string Time { get; set; }
|
||||||
|
public string Plate { get; set; }
|
||||||
|
public string Result { get; set; }
|
||||||
|
|
||||||
|
public LogModel(string plate, string result)
|
||||||
|
{
|
||||||
|
Time = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
Plate = plate;
|
||||||
|
Result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
LPR_Manager/Model/SystemConfig.cs
Normal file
22
LPR_Manager/Model/SystemConfig.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Model
|
||||||
|
{
|
||||||
|
public partial class SystemConfig : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _imageServerPort = 5000;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _imageServerId = "image";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _imageServerPassword = "1234";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _imageRootPath = "Images";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private LogMode _logMode = LogMode.Both;
|
||||||
|
}
|
||||||
|
}
|
||||||
115
LPR_Manager/Protocol/CMS/InnoV1.cs
Normal file
115
LPR_Manager/Protocol/CMS/InnoV1.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using LPR_Manager.Code.GlobalObject;
|
||||||
|
using LPR_Manager.Model;
|
||||||
|
using LPR_Manager.Model.Device.SubClass;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Protocol.CMS
|
||||||
|
{
|
||||||
|
class InnoV1
|
||||||
|
{
|
||||||
|
internal static string GetStatusChkDetailStr(string status, string devicecode)
|
||||||
|
{
|
||||||
|
// #1|CMSDTST|1-1,0-0,1-0|||^
|
||||||
|
return $"#{devicecode}|CMSDTST|{status}|||^";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetStatusChkStr(string devicecode)
|
||||||
|
{
|
||||||
|
// #1|LPRST|OK|||^
|
||||||
|
return $"#{devicecode}|CMSST|OK|||^";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetResultStr(LPRDataSet data, string fpath)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(data.ImagePath) && data.ImagePath.Length >= fpath.Length && data.ImagePath.Substring(0, fpath.Length) != fpath)
|
||||||
|
{
|
||||||
|
data.ImagePath = $"{fpath}{data.ImagePath}";
|
||||||
|
}
|
||||||
|
return $"#{BaseInfo.DeviceCode}|RESULT|{data.GetProtocolString()}|||^";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetResultUpdateStr(LPRDataSet data, string fpath)
|
||||||
|
{
|
||||||
|
if (data.ImagePath.Length >= fpath.Length && data.ImagePath.Substring(0, fpath.Length) != fpath)
|
||||||
|
{
|
||||||
|
data.ImagePath = $"{fpath}{data.ImagePath}";
|
||||||
|
}
|
||||||
|
return $"#{BaseInfo.DeviceCode}|RESULTUP|{data.GetProtocolString()}|||^";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetRawResultStr(LPRDataSet data)
|
||||||
|
{
|
||||||
|
return data.GetRawProtocolString();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static CMSDataSet Parse(string strdata)
|
||||||
|
{
|
||||||
|
CMSDataSet tmpResultDataset = new CMSDataSet();
|
||||||
|
// #C1|CMSCMD|REGREFRESH|||^
|
||||||
|
strdata = strdata.Substring(1);
|
||||||
|
string[] spdata = strdata.Split('|');
|
||||||
|
|
||||||
|
if (spdata[1] == "CMSCMD" && spdata.Length > 4)
|
||||||
|
{
|
||||||
|
tmpResultDataset.CenterID = spdata[0];
|
||||||
|
if (Enum.TryParse(spdata[2], out CMSCMD tmpParseredcmd))
|
||||||
|
{
|
||||||
|
tmpResultDataset.MainCmd = tmpParseredcmd;
|
||||||
|
switch (tmpResultDataset.MainCmd)
|
||||||
|
{
|
||||||
|
case CMSCMD.CONFIRM:
|
||||||
|
tmpResultDataset.Data1 = spdata[3];
|
||||||
|
break;
|
||||||
|
case CMSCMD.GATECTRL:
|
||||||
|
tmpResultDataset.EnumData = (GateSendCMD)Enum.Parse(typeof(GateSendCMD), spdata[3]);
|
||||||
|
break;
|
||||||
|
case CMSCMD.LEDDISPLAY:
|
||||||
|
tmpResultDataset.Data1 = spdata[3];
|
||||||
|
if (spdata[4].Contains("일반"))
|
||||||
|
{
|
||||||
|
tmpResultDataset.Data2 = MainDeviceItem.LEDDisplayMessageNoMember;
|
||||||
|
}
|
||||||
|
else if (spdata[4].Contains("등록"))
|
||||||
|
{
|
||||||
|
tmpResultDataset.Data2 = MainDeviceItem.LEDDisplayMessageMember;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CMSCMD.LEDDISPLAYC:
|
||||||
|
tmpResultDataset.MainCmd = CMSCMD.LEDDISPLAY;
|
||||||
|
tmpResultDataset.Data1 = spdata[3];
|
||||||
|
tmpResultDataset.Data2 = spdata[4];
|
||||||
|
tmpResultDataset.Data3 = spdata[5];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tmpResultDataset.MainCmd = CMSCMD.OUTOFCMD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tmpResultDataset.MainCmd = CMSCMD.OUTOFCMD;
|
||||||
|
}
|
||||||
|
return tmpResultDataset;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetSendToServerCMD(CMSCMD cmd, Enum subcmd, string data1 = "", string data2 = "")
|
||||||
|
{
|
||||||
|
string result = "";
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case CMSCMD.CONFIRM:
|
||||||
|
result = $"#{BaseInfo.DeviceCode}|CMSCMD|CONFIRM|{data1}||^";
|
||||||
|
break;
|
||||||
|
case CMSCMD.GATECTRL:
|
||||||
|
result = $"#{BaseInfo.DeviceCode}|CMSCMD|GATECTRL|{((GateSendCMD)subcmd)}||^";
|
||||||
|
break;
|
||||||
|
case CMSCMD.LEDDISPLAY:
|
||||||
|
result = $"#{BaseInfo.DeviceCode}|CMSCMD|LEDDISPLAY|{data1}|{data2}|^";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
LPR_Manager/Protocol/Gate/FPTV1.cs
Normal file
66
LPR_Manager/Protocol/Gate/FPTV1.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using LPR_Manager.Model;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Protocol.Gate
|
||||||
|
{
|
||||||
|
class FPTV1
|
||||||
|
{
|
||||||
|
internal static string NewLine = "\u0003";
|
||||||
|
|
||||||
|
internal static GateStatus GetGateStatus(string strdata)
|
||||||
|
{
|
||||||
|
GateStatus result = GateStatus.CLOSED;
|
||||||
|
strdata = strdata.Replace($"{(char)0x02}", "").Replace($"{(char)0x03}", "");
|
||||||
|
|
||||||
|
if (strdata.Contains(","))
|
||||||
|
{
|
||||||
|
string[] spdata = strdata.Split(',');
|
||||||
|
strdata = spdata[0].Replace("GATE=", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strdata.Contains("GATE UP ACTION")) result = GateStatus.OPENING;
|
||||||
|
else if (strdata.Contains("GATE DOWN ACTION")) result = GateStatus.CLOSING;
|
||||||
|
else if (strdata.Contains("GATE DOWN OK")) result = GateStatus.CLOSED;
|
||||||
|
else if (strdata.Contains("GATE UP OK")) result = GateStatus.OPENDED;
|
||||||
|
else if (strdata.Contains("ERROR")) result = GateStatus.ERROR;
|
||||||
|
else if (strdata.Contains("UPLOCK")) result = GateStatus.UPLOCK;
|
||||||
|
else if (strdata.Contains("INIT")) result = GateStatus.INITED;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GateStatus ParseGateStatusIndirectMode(string strdata)
|
||||||
|
{
|
||||||
|
GateStatus result = GateStatus.ERROR;
|
||||||
|
string[] spstr = strdata.Split('|');
|
||||||
|
if (spstr.Length >= 5)
|
||||||
|
{
|
||||||
|
if (spstr[1] == "CMSDTST")
|
||||||
|
{
|
||||||
|
string[] tmpspstrgate = spstr[2].Split(',');
|
||||||
|
if (tmpspstrgate.Length >= 1)
|
||||||
|
{
|
||||||
|
string[] spstrgatestatus = tmpspstrgate[0].Split('-');
|
||||||
|
Enum.TryParse(spstrgatestatus[1], out result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetCommand(GateSendCMD cmd)
|
||||||
|
{
|
||||||
|
string result = string.Empty;
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case GateSendCMD.OPEN: result = $"{(char)0x02}GATE UP{(char)0x03}"; break;
|
||||||
|
case GateSendCMD.CLOSE: result = $"{(char)0x02}GATE DOWN{(char)0x03}"; break;
|
||||||
|
case GateSendCMD.STATUS: result = $"{(char)0x02}STATUS{(char)0x03}"; break;
|
||||||
|
case GateSendCMD.UPLOCK: result = $"{(char)0x02}GATE UPLOCK{(char)0x03}"; break;
|
||||||
|
case GateSendCMD.UNLOCK: result = $"{(char)0x02}GATE UNLOCK{(char)0x03}"; break;
|
||||||
|
case GateSendCMD.RESET: result = $"{(char)0x02}SYSTEM RESET{(char)0x03}"; break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
LPR_Manager/Protocol/LED/UJIN.cs
Normal file
98
LPR_Manager/Protocol/LED/UJIN.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using LPR_Manager.Model;
|
||||||
|
using LPR_Manager.Model.Device.SubClass;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Protocol.LED
|
||||||
|
{
|
||||||
|
public static class UJIN
|
||||||
|
{
|
||||||
|
public static byte[] GetMatrixMessage(LEDTextSet topMessage, LEDTextSet bottomMessage)
|
||||||
|
{
|
||||||
|
// Windows-949 encoding (Korean)
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
var encoding = Encoding.GetEncoding(949);
|
||||||
|
|
||||||
|
var color1 = CreateColorBytes(topMessage.Message, topMessage.Color, encoding);
|
||||||
|
var color2 = CreateColorBytes(bottomMessage.Message, bottomMessage.Color, encoding);
|
||||||
|
|
||||||
|
var alignmentString1 = GetAlignmentString(encoding, topMessage.Message);
|
||||||
|
var alignmentString2 = GetAlignmentString(encoding, bottomMessage.Message);
|
||||||
|
|
||||||
|
var char1 = encoding.GetBytes(alignmentString1);
|
||||||
|
var char2 = encoding.GetBytes(alignmentString2);
|
||||||
|
|
||||||
|
var dataLength = color1.Length + color2.Length + char1.Length + char2.Length + 16 + 1;
|
||||||
|
var lengthPacket = ConvertToBigEndian((short)dataLength);
|
||||||
|
|
||||||
|
var packets = new List<byte>();
|
||||||
|
packets.AddRange(new byte[] { 0x10, 0x02, 0x01 });
|
||||||
|
packets.AddRange(lengthPacket);
|
||||||
|
packets.AddRange(new byte[]
|
||||||
|
{
|
||||||
|
0x94, 0x00, 0x00, 0x63, 0x01, 0x00,
|
||||||
|
0x03, 0x01, 0x01, 0x00, 0x00, 0xFF,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
});
|
||||||
|
packets.AddRange(color1);
|
||||||
|
packets.AddRange(color2);
|
||||||
|
packets.AddRange(char1);
|
||||||
|
packets.AddRange(char2);
|
||||||
|
packets.AddRange(new byte[] { 0x10, 0x03 });
|
||||||
|
|
||||||
|
return packets.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] CreateColorBytes(string text, LEDColor color, Encoding encoding)
|
||||||
|
{
|
||||||
|
var bytes = new List<byte>();
|
||||||
|
var alignmentString = GetAlignmentString(encoding, text);
|
||||||
|
var colorByte = ParseColorToColorMessage(color);
|
||||||
|
|
||||||
|
foreach (var ch in alignmentString)
|
||||||
|
{
|
||||||
|
bytes.Add(colorByte);
|
||||||
|
if (encoding.GetByteCount(ch.ToString()) != 1)
|
||||||
|
bytes.Add(0x00);
|
||||||
|
}
|
||||||
|
return bytes.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAlignmentString(Encoding encoding, string message, int maxlength = 12)
|
||||||
|
{
|
||||||
|
var checkBytes = encoding.GetBytes(message);
|
||||||
|
if (checkBytes.Length > maxlength)
|
||||||
|
{
|
||||||
|
Array.Resize(ref checkBytes, maxlength);
|
||||||
|
}
|
||||||
|
byte[] resultstr = Enumerable.Repeat((byte)0x20, maxlength).ToArray();
|
||||||
|
Array.Copy(checkBytes, 0, resultstr, 0, checkBytes.Length);
|
||||||
|
return encoding.GetString(resultstr).TrimEnd((char)0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] ConvertToBigEndian(short value)
|
||||||
|
{
|
||||||
|
var bytes = BitConverter.GetBytes(value);
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte ParseColorToColorMessage(LEDColor color)
|
||||||
|
{
|
||||||
|
return color switch
|
||||||
|
{
|
||||||
|
LEDColor.RED => 0x01,
|
||||||
|
LEDColor.GREEN => 0x02,
|
||||||
|
LEDColor.YELLOW => 0x03,
|
||||||
|
LEDColor.BLUE => 0x04,
|
||||||
|
LEDColor.MAGENTA => 0x05,
|
||||||
|
LEDColor.CYAN => 0x06,
|
||||||
|
LEDColor.WHITE => 0x07,
|
||||||
|
_ => 0x00
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
LPR_Manager/Protocol/LED/YJMICRO.cs
Normal file
18
LPR_Manager/Protocol/LED/YJMICRO.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using LPR_Manager.Model;
|
||||||
|
using LPR_Manager.Model.Device.SubClass;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Protocol.LED
|
||||||
|
{
|
||||||
|
class YJMICRO
|
||||||
|
{
|
||||||
|
internal static string NewLine = "[COMOFF]";
|
||||||
|
internal static string Space = " ";
|
||||||
|
|
||||||
|
internal static string GetCommand(LEDTextSet textsettop, LEDTextSet textsetbottom)
|
||||||
|
{
|
||||||
|
return $"[TMODE]\r[COMON][PX0 PY0][RTX0 RTY0][CR{(int)textsettop.Color}]{textsettop.Message}[ACT0][PX0 PY0][RTX0 RTY16][CR{(int)textsetbottom.Color}]{textsetbottom.Message}[ACT0][COMOFF]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
78
LPR_Manager/Service/ConfigService.cs
Normal file
78
LPR_Manager/Service/ConfigService.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Service
|
||||||
|
{
|
||||||
|
public class ConfigService
|
||||||
|
{
|
||||||
|
private const string ConfigDir = "Configs";
|
||||||
|
|
||||||
|
public ConfigService()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(ConfigDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(ConfigDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveLaneAsync(Model.LaneConfig lane)
|
||||||
|
{
|
||||||
|
var fileName = Path.Combine(ConfigDir, $"{lane.LaneId}.json");
|
||||||
|
var json = JsonConvert.SerializeObject(lane, Formatting.Indented);
|
||||||
|
await File.WriteAllTextAsync(fileName, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteLaneConfig(string laneId)
|
||||||
|
{
|
||||||
|
var fileName = Path.Combine(ConfigDir, $"{laneId}.json");
|
||||||
|
if (File.Exists(fileName))
|
||||||
|
{
|
||||||
|
File.Delete(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Model.LaneConfig>> LoadAllLanesAsync()
|
||||||
|
{
|
||||||
|
var lanes = new List<Model.LaneConfig>();
|
||||||
|
if (!Directory.Exists(ConfigDir)) return lanes;
|
||||||
|
|
||||||
|
foreach (var file in Directory.GetFiles(ConfigDir, "*.json"))
|
||||||
|
{
|
||||||
|
if (Path.GetFileName(file).Equals("system_config.json", System.StringComparison.OrdinalIgnoreCase)) continue;
|
||||||
|
|
||||||
|
var json = await File.ReadAllTextAsync(file);
|
||||||
|
var lane = JsonConvert.DeserializeObject<Model.LaneConfig>(json);
|
||||||
|
if (lane != null) lanes.Add(lane);
|
||||||
|
}
|
||||||
|
return lanes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.SystemConfig> LoadSystemConfigAsync()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(ConfigDir, "system_config.json");
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return new Model.SystemConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = await File.ReadAllTextAsync(path);
|
||||||
|
return JsonConvert.DeserializeObject<Model.SystemConfig>(json) ?? new Model.SystemConfig();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new Model.SystemConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveSystemConfigAsync(Model.SystemConfig config)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(ConfigDir, "system_config.json");
|
||||||
|
var json = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||||
|
await File.WriteAllTextAsync(path, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
141
LPR_Manager/Service/ImageServer.cs
Normal file
141
LPR_Manager/Service/ImageServer.cs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Service
|
||||||
|
{
|
||||||
|
public class ImageServer
|
||||||
|
{
|
||||||
|
private WebApplication? _app;
|
||||||
|
private readonly string _webRootPath;
|
||||||
|
private readonly int _port;
|
||||||
|
private readonly string _username;
|
||||||
|
private readonly string _password;
|
||||||
|
public bool IsRunning => _app != null;
|
||||||
|
|
||||||
|
public ImageServer(Model.SystemConfig config)
|
||||||
|
{
|
||||||
|
if (Path.IsPathRooted(config.ImageRootPath))
|
||||||
|
{
|
||||||
|
_webRootPath = config.ImageRootPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_webRootPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, config.ImageRootPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
_port = config.ImageServerPort;
|
||||||
|
_username = config.ImageServerId;
|
||||||
|
_password = config.ImageServerPassword;
|
||||||
|
|
||||||
|
Directory.CreateDirectory(_webRootPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder();
|
||||||
|
|
||||||
|
builder.WebHost.ConfigureKestrel(options =>
|
||||||
|
{
|
||||||
|
options.ListenAnyIP(_port);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.AddSerilog();
|
||||||
|
|
||||||
|
_app = builder.Build();
|
||||||
|
|
||||||
|
// Basic Auth Middleware
|
||||||
|
_app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
if (context.Request.Path.StartsWithSegments("/images"))
|
||||||
|
{
|
||||||
|
string? authHeader = context.Request.Headers["Authorization"];
|
||||||
|
if (authHeader != null && authHeader.StartsWith("Basic "))
|
||||||
|
{
|
||||||
|
var encodedUsernamePassword = authHeader.Substring(6).Trim();
|
||||||
|
var encoding = Encoding.UTF8;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
|
||||||
|
int separatorIndex = usernamePassword.IndexOf(':');
|
||||||
|
if (separatorIndex >= 0)
|
||||||
|
{
|
||||||
|
var username = usernamePassword.Substring(0, separatorIndex);
|
||||||
|
var password = usernamePassword.Substring(separatorIndex + 1);
|
||||||
|
|
||||||
|
if (username == _username && password == _password)
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore parsing errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = 401;
|
||||||
|
context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"ParkingImages\"";
|
||||||
|
await context.Response.WriteAsync("Unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
_app.UseStaticFiles(new StaticFileOptions
|
||||||
|
{
|
||||||
|
FileProvider = new PhysicalFileProvider(_webRootPath),
|
||||||
|
RequestPath = "/images"
|
||||||
|
});
|
||||||
|
|
||||||
|
_app.MapGet("/", () => Results.Content(@"
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', sans-serif; text-align: center; padding: 50px; background-color: #f0f2f5; }
|
||||||
|
.container { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); display: inline-block; }
|
||||||
|
h1 { color: #0078d4; margin-bottom: 10px; }
|
||||||
|
p { color: #333; font-size: 1.1em; }
|
||||||
|
.status { color: green; font-weight: bold; }
|
||||||
|
.footer { margin-top: 20px; font-size: 0.8em; color: #666; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class='container'>
|
||||||
|
<h1>Parking Interface Image Server</h1>
|
||||||
|
<p>Status: <span class='status'>Active</span></p>
|
||||||
|
<p>The image server is running and ready to serve files.</p>
|
||||||
|
<hr/>
|
||||||
|
<p style='font-size: 0.9em'>Authentication Required for /images path.</p>
|
||||||
|
<div class='footer'>ParkingInterface System v1.0</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>", "text/html"));
|
||||||
|
|
||||||
|
await _app.StartAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (_app != null)
|
||||||
|
{
|
||||||
|
await _app.StopAsync(cancellationToken);
|
||||||
|
await _app.DisposeAsync();
|
||||||
|
_app = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
LPR_Manager/Service/LogService.cs
Normal file
30
LPR_Manager/Service/LogService.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Serilog;
|
||||||
|
using System.IO;
|
||||||
|
using LPR_Manager.Model;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Service
|
||||||
|
{
|
||||||
|
public static class LogService
|
||||||
|
{
|
||||||
|
public static void ConfigureLogging(LogMode mode)
|
||||||
|
{
|
||||||
|
var loggerConfig = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Debug();
|
||||||
|
|
||||||
|
var logPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Logs", "log.txt");
|
||||||
|
|
||||||
|
if (mode == LogMode.Seq || mode == LogMode.Both)
|
||||||
|
{
|
||||||
|
loggerConfig.WriteTo.Seq("http://localhost:5341");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == LogMode.File || mode == LogMode.Both)
|
||||||
|
{
|
||||||
|
loggerConfig.WriteTo.File(logPath, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Logger = loggerConfig.CreateLogger();
|
||||||
|
Log.Information($"Logging configured. Mode: {mode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
91
LPR_Manager/Service/WushiCamera.cs
Normal file
91
LPR_Manager/Service/WushiCamera.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace LPR_Manager.Service
|
||||||
|
{
|
||||||
|
public class WushiCamera : IDisposable
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void NtcConnectCallback([MarshalAs(UnmanagedType.LPUTF8Str)] string id, bool connected);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void NtcTriggerCallback([MarshalAs(UnmanagedType.LPUTF8Str)] string id, [MarshalAs(UnmanagedType.LPUTF8Str)] string plate, IntPtr pImage, int imageSize);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void NtcAuthCallback(int code, [MarshalAs(UnmanagedType.LPUTF8Str)] string msg);
|
||||||
|
|
||||||
|
[DllImport("NtcClientAPI4Net.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void NtcApi_Init([MarshalAs(UnmanagedType.LPStr)] string logPath);
|
||||||
|
|
||||||
|
[DllImport("NtcClientAPI4Net.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern IntPtr NtcApi_CreateClient(
|
||||||
|
[MarshalAs(UnmanagedType.LPStr)] string host,
|
||||||
|
[MarshalAs(UnmanagedType.LPUTF8Str)] string id,
|
||||||
|
[MarshalAs(UnmanagedType.LPStr)] string adminId,
|
||||||
|
[MarshalAs(UnmanagedType.LPStr)] string adminPw,
|
||||||
|
NtcConnectCallback ccb, NtcTriggerCallback tcb, NtcAuthCallback acb);
|
||||||
|
|
||||||
|
[DllImport("NtcClientAPI4Net.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void NtcApi_DeleteClient(IntPtr pClient);
|
||||||
|
|
||||||
|
[DllImport("NtcClientAPI4Net.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern int NtcApi_ClientStart(IntPtr pClient);
|
||||||
|
|
||||||
|
[DllImport("NtcClientAPI4Net.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern int NtcApi_ClientStop(IntPtr pClient);
|
||||||
|
|
||||||
|
private IntPtr _pClient = IntPtr.Zero;
|
||||||
|
private NtcConnectCallback _connectCallback;
|
||||||
|
private NtcTriggerCallback _triggerCallback;
|
||||||
|
private NtcAuthCallback _authCallback;
|
||||||
|
|
||||||
|
public bool IsConnected { get; private set; }
|
||||||
|
public event EventHandler<byte[]> OnImageReceived;
|
||||||
|
public event EventHandler<string> OnMessage;
|
||||||
|
|
||||||
|
public WushiCamera()
|
||||||
|
{
|
||||||
|
string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", "NtcSDK");
|
||||||
|
if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath);
|
||||||
|
NtcApi_Init(logPath);
|
||||||
|
|
||||||
|
_connectCallback = (id, connected) =>
|
||||||
|
{
|
||||||
|
IsConnected = connected;
|
||||||
|
OnMessage?.Invoke(this, $"Camera {id} Connected: {connected}");
|
||||||
|
};
|
||||||
|
_authCallback = (code, msg) => OnMessage?.Invoke(this, $"Camera Auth: {code}, {msg}");
|
||||||
|
_triggerCallback = (id, plate, pImage, imageSize) =>
|
||||||
|
{
|
||||||
|
byte[] jpgImage = new byte[imageSize];
|
||||||
|
Marshal.Copy(pImage, jpgImage, 0, imageSize);
|
||||||
|
OnImageReceived?.Invoke(this, jpgImage);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(string ip, string id, string user, string pw)
|
||||||
|
{
|
||||||
|
_pClient = NtcApi_CreateClient(ip, id, user, pw, _connectCallback, _triggerCallback, _authCallback);
|
||||||
|
if (_pClient != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
NtcApi_ClientStart(_pClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
if (_pClient != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
NtcApi_ClientStop(_pClient);
|
||||||
|
NtcApi_DeleteClient(_pClient);
|
||||||
|
_pClient = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
LPR_Manager/View/LoginWindow.xaml
Normal file
109
LPR_Manager/View/LoginWindow.xaml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<Window x:Class="LPR_Manager.View.LoginWindow"
|
||||||
|
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Sign In" Height="650" Width="400"
|
||||||
|
WindowStartupLocation="CenterOwner" WindowStyle="None" AllowsTransparency="True" Background="Transparent"
|
||||||
|
ResizeMode="NoResize" FontFamily="Segoe UI">
|
||||||
|
|
||||||
|
<Grid Margin="20">
|
||||||
|
<!-- Main Card -->
|
||||||
|
<Border CornerRadius="25" Background="White">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="40" ShadowDepth="0" Opacity="0.2" Color="#9E9E9E"/>
|
||||||
|
</Border.Effect>
|
||||||
|
|
||||||
|
<Grid Margin="30">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/> <!-- Top Bar -->
|
||||||
|
<RowDefinition Height="Auto"/> <!-- Avatar -->
|
||||||
|
<RowDefinition Height="Auto"/> <!-- Header -->
|
||||||
|
<RowDefinition Height="*"/> <!-- Inputs -->
|
||||||
|
<RowDefinition Height="Auto"/> <!-- Button -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 1. Top Bar -->
|
||||||
|
<Grid Grid.Row="0">
|
||||||
|
<!-- 'P' Logo -->
|
||||||
|
<Border HorizontalAlignment="Left" Width="32" Height="32" CornerRadius="16" Background="#E3F2FD">
|
||||||
|
<TextBlock Text="P" Foreground="#1565C0" FontWeight="Bold" FontSize="18"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Close Button -->
|
||||||
|
<Button HorizontalAlignment="Right" Width="30" Height="30" Padding="0"
|
||||||
|
Background="Transparent" BorderBrush="Transparent"
|
||||||
|
Click="OnCloseClick" Cursor="Hand">
|
||||||
|
<materialDesign:PackIcon Kind="Close" Width="20" Height="20" Foreground="#757575"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 2. Avatar -->
|
||||||
|
<Border Grid.Row="1" Width="80" Height="80" CornerRadius="40" Background="#F5F6FA" Margin="0,30,0,20">
|
||||||
|
<materialDesign:PackIcon Kind="AccountCircle" Width="50" Height="50" Foreground="#1565C0"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 3. Header -->
|
||||||
|
<StackPanel Grid.Row="2" HorizontalAlignment="Center" Margin="0,0,0,30">
|
||||||
|
<TextBlock Text="Sign In" FontSize="26" FontWeight="Bold" Foreground="Black" HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||||
|
<TextBlock Text="Enter your credentials to access the parking" Foreground="#78909C" FontSize="13" HorizontalAlignment="Center"/>
|
||||||
|
<TextBlock Text="management portal" Foreground="#78909C" FontSize="13" HorizontalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 4. Inputs -->
|
||||||
|
<StackPanel Grid.Row="3" VerticalAlignment="Top">
|
||||||
|
<!-- Username -->
|
||||||
|
<TextBlock Text="Username" FontWeight="SemiBold" Foreground="#37474F" Margin="5,0,0,5"/>
|
||||||
|
<Border BorderThickness="1" BorderBrush="#E0E0E0" CornerRadius="15" Padding="5" Margin="0,0,0,20">
|
||||||
|
<TextBox x:Name="UserBox"
|
||||||
|
materialDesign:HintAssist.Hint="admin_parking"
|
||||||
|
materialDesign:TextFieldAssist.DecorationVisibility="Hidden"
|
||||||
|
BorderThickness="0" Background="Transparent"
|
||||||
|
VerticalContentAlignment="Center" Height="35" Padding="10,0" FontSize="14"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<TextBlock Text="Password" FontWeight="SemiBold" Foreground="#37474F" Margin="5,0,0,5"/>
|
||||||
|
<Border BorderThickness="1" BorderBrush="#E0E0E0" CornerRadius="15" Padding="5">
|
||||||
|
<Grid>
|
||||||
|
<PasswordBox x:Name="PassBox"
|
||||||
|
materialDesign:HintAssist.Hint="••••••••"
|
||||||
|
materialDesign:TextFieldAssist.DecorationVisibility="Hidden"
|
||||||
|
BorderThickness="0" Background="Transparent"
|
||||||
|
VerticalContentAlignment="Center" Height="35" Padding="10,0" FontSize="14"
|
||||||
|
FontFamily="Courier New"/>
|
||||||
|
<materialDesign:PackIcon Kind="Eye" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||||
|
Margin="0,0,10,0" Foreground="#90A4AE" Cursor="Hand"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Error Message -->
|
||||||
|
<TextBlock x:Name="ErrorText" Text="Invalid credentials" Foreground="#D32F2F" FontSize="12"
|
||||||
|
HorizontalAlignment="Center" Margin="0,15,0,0" Visibility="Collapsed"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 5. Login Button -->
|
||||||
|
<Button Grid.Row="4" IsDefault="True" Click="OnLogin"
|
||||||
|
Background="#1565C0" BorderBrush="#1565C0" Foreground="White"
|
||||||
|
Height="50" materialDesign:ButtonAssist.CornerRadius="25"
|
||||||
|
Margin="0,20,0,0" FontSize="16" FontWeight="Bold">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="LOGIN" Margin="0,0,10,0"/>
|
||||||
|
<materialDesign:PackIcon Kind="ArrowRight"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Close Button (Hidden/Cancel functionality via 'Esc' or top right logic) -->
|
||||||
|
<!-- Since windowstyle is none, we should probably have a way to close if user cancels,
|
||||||
|
but the request removed the cancel button. We'll map ESC key in code behind or rely on user knowing standard behaviors.
|
||||||
|
Ideally, let's add a small 'Close' or 'Cancel' text logic safely below or use the X top right if needed.
|
||||||
|
Mockup shows three dots. I'll make the three dots clickable to close for now or just trust IsCancel hidden button. -->
|
||||||
|
<Button IsCancel="True" Click="OnCancel" Visibility="Collapsed"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
46
LPR_Manager/View/LoginWindow.xaml.cs
Normal file
46
LPR_Manager/View/LoginWindow.xaml.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace LPR_Manager.View
|
||||||
|
{
|
||||||
|
public partial class LoginWindow : Window
|
||||||
|
{
|
||||||
|
public bool IsAuthenticated { get; private set; } = false;
|
||||||
|
|
||||||
|
public LoginWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
UserBox.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLogin(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Fixed credentials
|
||||||
|
var user = UserBox.Text;
|
||||||
|
var pass = PassBox.Password;
|
||||||
|
|
||||||
|
if (user == "admin" && pass == "090706")
|
||||||
|
{
|
||||||
|
IsAuthenticated = true;
|
||||||
|
DialogResult = true;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorText.Visibility = Visibility.Visible;
|
||||||
|
PassBox.Password = "";
|
||||||
|
PassBox.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancel(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
private void OnCloseClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
224
LPR_Manager/View/LprLaneControl.xaml
Normal file
224
LPR_Manager/View/LprLaneControl.xaml
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
<UserControl x:Class="LPR_Manager.View.LprLaneControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:local="clr-namespace:LPR_Manager.View"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Width="360" Height="680" Margin="10">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||||
|
|
||||||
|
<!-- Storyboard for vehicle detection glow animation -->
|
||||||
|
<Storyboard x:Key="VehicleDetectedGlow" RepeatBehavior="5x" Duration="0:0:5">
|
||||||
|
<ColorAnimation Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"
|
||||||
|
From="#3F51B5" To="Transparent" Duration="0:0:1"/>
|
||||||
|
</Storyboard>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<!-- Main Card -->
|
||||||
|
<md:Card UniformCornerRadius="12" md:ElevationAssist.Elevation="Dp2" Background="White">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<!-- Header -->
|
||||||
|
<RowDefinition Height="200"/>
|
||||||
|
<!-- Main Image with Overlay -->
|
||||||
|
<RowDefinition Height="110"/>
|
||||||
|
<!-- Recognition Info -->
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<!-- Status Pills -->
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<!-- Logs -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 1. Header Area with Lane ID -->
|
||||||
|
<Border Grid.Row="0" Padding="16,12" Background="White" BorderBrush="#EEEEEE" BorderThickness="0,0,0,0">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="{Binding LaneId, TargetNullValue='#P-001'}" DockPanel.Dock="Right"
|
||||||
|
FontSize="11" Foreground="#BDBDBD" FontWeight="Bold" VerticalAlignment="Top" Margin="0,2,0,0" FontFamily="Courier New"/>
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="{Binding LaneName}" FontWeight="Bold" Foreground="{DynamicResource PrimaryHueMidBrush}" FontSize="11"/>
|
||||||
|
</DockPanel>
|
||||||
|
<TextBlock Text="{Binding LaneDescription}" FontSize="16" FontWeight="Bold" Foreground="#263238" Margin="0,2,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 2. Main Image Area -->
|
||||||
|
<Border Grid.Row="1" BorderThickness="2" BorderBrush="Transparent" CornerRadius="4">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsVehicleDetected}" Value="True">
|
||||||
|
<DataTrigger.EnterActions>
|
||||||
|
<BeginStoryboard Storyboard="{StaticResource VehicleDetectedGlow}"/>
|
||||||
|
</DataTrigger.EnterActions>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<!-- Vehicle Image -->
|
||||||
|
<Image Source="{Binding CameraImage}" Stretch="UniformToFill" VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- Placeholder if no image -->
|
||||||
|
<Grid Visibility="{Binding ShowPlaceholder, Converter={StaticResource BooleanToVisibilityConverter}}" Background="#F5F5F5">
|
||||||
|
<md:PackIcon Kind="Car" Width="50" Height="50" Foreground="#E0E0E0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Live Badge -->
|
||||||
|
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 3. Recognition Info -->
|
||||||
|
<Grid Grid.Row="2" Margin="16,16,16,8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<DockPanel LastChildFill="False">
|
||||||
|
<TextBlock Text="RECOGNITION RESULT" Foreground="#90A4AE" FontSize="10" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding LastDetectionTime, StringFormat=HH:mm:ss, TargetNullValue='--:--:--'}" Foreground="#90A4AE" FontSize="10" DockPanel.Dock="Right" VerticalAlignment="Center"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding LastCarNumber}" FontSize="32" FontWeight="Black" Foreground="#263238" Margin="0,4,0,2"/>
|
||||||
|
|
||||||
|
<TextBlock Text="AUTHORIZED MATCH" Foreground="#388E3C" FontSize="11" FontWeight="Bold"
|
||||||
|
Visibility="{Binding IsAuthorized, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsAuthorized}" Value="False">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<Grid Grid.Column="1">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Plate Overlay (Picture-in-Picture) -->
|
||||||
|
<Border Margin="2" CornerRadius="4" BorderBrush="White" BorderThickness="1" Background="White">
|
||||||
|
<Grid>
|
||||||
|
<Image Source="{Binding PlateImage}" Stretch="Uniform"/>
|
||||||
|
<TextBlock Text="No Plate" Foreground="Gray" FontSize="10" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Visibility="{Binding ShowNoPlate, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||||
|
</Grid>
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="8" ShadowDepth="2" Opacity="0.4"/>
|
||||||
|
</Border.Effect>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 4. Device Status Pills -->
|
||||||
|
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="16,12,16,8">
|
||||||
|
<StackPanel.Resources>
|
||||||
|
<Style TargetType="Border" x:Key="StatusPill">
|
||||||
|
<Setter Property="Background" Value="White"/>
|
||||||
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
|
<Setter Property="Padding" Value="10,6"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,8,0"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#EEEEEE"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Effect">
|
||||||
|
<Setter.Value>
|
||||||
|
<DropShadowEffect BlurRadius="4" ShadowDepth="1" Direction="270" Opacity="0.1" Color="#000000"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Ellipse" x:Key="StatusDot">
|
||||||
|
<Setter Property="Width" Value="8"/>
|
||||||
|
<Setter Property="Height" Value="8"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,6,0"/>
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Resources>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- CAM -->
|
||||||
|
<Border Style="{StaticResource StatusPill}">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<Ellipse>
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsCameraConnected}" Value="True">
|
||||||
|
<Setter Property="Fill" Value="#2E7D32"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding IsCameraConnected}" Value="False">
|
||||||
|
<Setter Property="Fill" Value="#C62828"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock Text="CAM" FontSize="11" FontWeight="Bold" Foreground="#37474F"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- CMS -->
|
||||||
|
<Border Style="{StaticResource StatusPill}">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<Ellipse>
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsCmsConnected}" Value="True">
|
||||||
|
<Setter Property="Fill" Value="#2E7D32"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding IsCmsConnected}" Value="False">
|
||||||
|
<Setter Property="Fill" Value="#C62828"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock Text="CMS" FontSize="11" FontWeight="Bold" Foreground="#37474F"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 5. Logs Grid -->
|
||||||
|
<DataGrid Grid.Row="4" Background="White" BorderThickness="0,1,0,0" BorderBrush="#EEEEEE"
|
||||||
|
ItemsSource="{Binding Logs}"
|
||||||
|
AutoGenerateColumns="False" HeadersVisibility="None" IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal" HorizontalGridLinesBrush="#F5F5F5"
|
||||||
|
Style="{DynamicResource MaterialDesignDataGrid}" Margin="16,8,16,16"
|
||||||
|
RowHeight="28">
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Binding="{Binding .}" Width="*" Foreground="#546E7A">
|
||||||
|
<DataGridTextColumn.ElementStyle>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="TextWrapping" Value="NoWrap"/>
|
||||||
|
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.ElementStyle>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</md:Card>
|
||||||
|
</UserControl>
|
||||||
12
LPR_Manager/View/LprLaneControl.xaml.cs
Normal file
12
LPR_Manager/View/LprLaneControl.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace LPR_Manager.View
|
||||||
|
{
|
||||||
|
public partial class LprLaneControl : UserControl
|
||||||
|
{
|
||||||
|
public LprLaneControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
257
LPR_Manager/View/SettingsControl.xaml
Normal file
257
LPR_Manager/View/SettingsControl.xaml
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
<UserControl x:Class="LPR_Manager.View.SettingsControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
|
xmlns:model="clr-namespace:LPR_Manager.Model"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{DynamicResource MaterialDesignPaper}"
|
||||||
|
d:DesignHeight="600" d:DesignWidth="1000">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ObjectDataProvider x:Key="RecognitionOrderEnum" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
|
||||||
|
<ObjectDataProvider.MethodParameters>
|
||||||
|
<x:Type TypeName="model:RecognitionOrder"/>
|
||||||
|
</ObjectDataProvider.MethodParameters>
|
||||||
|
</ObjectDataProvider>
|
||||||
|
<ObjectDataProvider x:Key="LogModeEnum" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
|
||||||
|
<ObjectDataProvider.MethodParameters>
|
||||||
|
<x:Type TypeName="model:LogMode"/>
|
||||||
|
</ObjectDataProvider.MethodParameters>
|
||||||
|
</ObjectDataProvider>
|
||||||
|
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Grid Margin="16">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="SETTINGS" Style="{DynamicResource MaterialDesignHeadline5TextBlock}" Margin="0,0,0,16"/>
|
||||||
|
|
||||||
|
<TabControl Grid.Row="1" Style="{DynamicResource MaterialDesignNaviRailTabControl}" TabStripPlacement="Top">
|
||||||
|
|
||||||
|
<!-- System Setting Tab -->
|
||||||
|
<TabItem Header="System Setting" Style="{DynamicResource MaterialDesignNavigationRailTabItem}">
|
||||||
|
<Grid Margin="20">
|
||||||
|
<StackPanel MaxWidth="500" HorizontalAlignment="Left" VerticalAlignment="Top">
|
||||||
|
<TextBlock Text="Image Server Configuration" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0,0,0,20"/>
|
||||||
|
|
||||||
|
<TextBox materialDesign:HintAssist.Hint="Server Port (Default: 5000)"
|
||||||
|
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
|
||||||
|
Text="{Binding SystemConfig.ImageServerPort}"
|
||||||
|
Margin="0,0,0,20"/>
|
||||||
|
|
||||||
|
<TextBox materialDesign:HintAssist.Hint="Server ID"
|
||||||
|
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
|
||||||
|
Text="{Binding SystemConfig.ImageServerId}"
|
||||||
|
Margin="0,0,0,20"/>
|
||||||
|
|
||||||
|
<TextBox materialDesign:HintAssist.Hint="Server Password"
|
||||||
|
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
|
||||||
|
Text="{Binding SystemConfig.ImageServerPassword}"
|
||||||
|
Margin="0,0,0,20"/>
|
||||||
|
|
||||||
|
<DockPanel Margin="0,0,0,30">
|
||||||
|
<Button Command="{Binding BrowseImageFolderCommand}"
|
||||||
|
Content="..."
|
||||||
|
ToolTip="Browse Folder"
|
||||||
|
Style="{DynamicResource MaterialDesignOutlinedButton}"
|
||||||
|
Width="40" Padding="0" Margin="10,0,0,0" DockPanel.Dock="Right"/>
|
||||||
|
|
||||||
|
<TextBox materialDesign:HintAssist.Hint="Image Root Path (Or Absolute Path)"
|
||||||
|
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
|
||||||
|
Text="{Binding SystemConfig.ImageRootPath}"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<TextBlock Text="Log Configuration" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0,0,0,20"/>
|
||||||
|
<ComboBox ItemsSource="{Binding Source={StaticResource LogModeEnum}}"
|
||||||
|
SelectedItem="{Binding SystemConfig.LogMode}"
|
||||||
|
materialDesign:HintAssist.Hint="Log Storage Mode"
|
||||||
|
Style="{StaticResource MaterialDesignFloatingHintComboBox}"
|
||||||
|
Margin="0,0,0,30"/>
|
||||||
|
|
||||||
|
<Button Command="{Binding SaveSystemConfigCommand}"
|
||||||
|
Content="Save System Settings"
|
||||||
|
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||||
|
HorizontalAlignment="Left" Width="200"/>
|
||||||
|
|
||||||
|
<TextBlock Text="* Restart required to apply changes"
|
||||||
|
Foreground="Gray" Margin="0,10,0,0" FontStyle="Italic"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- SITE SETTING TAB -->
|
||||||
|
<TabItem Header="SITE SETTING">
|
||||||
|
<Grid Margin="0,16,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="350"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- LEFT: SITE LIST -->
|
||||||
|
<materialDesign:Card Grid.Column="0" VerticalAlignment="Stretch" Margin="0,0,16,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<DockPanel Margin="8">
|
||||||
|
<TextBlock Text="Site List" FontWeight="Bold" VerticalAlignment="Center" Margin="8,0"/>
|
||||||
|
<Button Command="{Binding AddLaneCommand}" Style="{DynamicResource MaterialDesignFlatButton}"
|
||||||
|
HorizontalAlignment="Right" ToolTip="Add New Site">
|
||||||
|
<materialDesign:PackIcon Kind="Plus"/>
|
||||||
|
</Button>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<DataGrid Grid.Row="1" ItemsSource="{Binding Lanes}" SelectedItem="{Binding SelectedLane}"
|
||||||
|
AutoGenerateColumns="False" CanUserAddRows="False" IsReadOnly="True"
|
||||||
|
HeadersVisibility="Column" SelectionMode="Single"
|
||||||
|
Style="{DynamicResource MaterialDesignDataGrid}">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="ID" Binding="{Binding LaneId}" Width="60"/>
|
||||||
|
<DataGridTextColumn Header="NAME" Binding="{Binding LaneName}" Width="*"/>
|
||||||
|
<DataGridTemplateColumn Width="50">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Button Command="{Binding DataContext.RemoveLaneCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Style="{DynamicResource MaterialDesignFlatButton}"
|
||||||
|
Foreground="#EF5350" Padding="0" Width="24" Height="24">
|
||||||
|
<materialDesign:PackIcon Kind="Delete" Width="16" Height="16"/>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</materialDesign:Card>
|
||||||
|
|
||||||
|
<!-- RIGHT: DETAILS -->
|
||||||
|
<materialDesign:Card Grid.Column="1" VerticalAlignment="Stretch" Padding="24">
|
||||||
|
<Grid>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Style>
|
||||||
|
<Style TargetType="Grid">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding SelectedLane}" Value="{x:Null}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Grid.Style>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<DockPanel Margin="0,0,0,24">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{Binding SelectedLane.LaneName}" Style="{DynamicResource MaterialDesignHeadline6TextBlock}"/>
|
||||||
|
<TextBlock Text="{Binding SelectedLane.LaneId}" FontSize="12" Foreground="Gray"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel>
|
||||||
|
<!-- Basic Info -->
|
||||||
|
<TextBlock Text="Basic Information" FontWeight="Bold" Margin="0,0,0,8" Foreground="{DynamicResource PrimaryHueMidBrush}"/>
|
||||||
|
<TextBox Text="{Binding SelectedLane.LaneName}" materialDesign:HintAssist.Hint="Site Name" Style="{DynamicResource MaterialDesignFloatingHintTextBox}" Margin="0,0,0,16"/>
|
||||||
|
<TextBox Text="{Binding SelectedLane.LaneDescription}" materialDesign:HintAssist.Hint="Description" Style="{DynamicResource MaterialDesignFloatingHintTextBox}" Margin="0,0,0,24"/>
|
||||||
|
|
||||||
|
<!-- Camera Settings -->
|
||||||
|
<TextBlock Text="Camera Configuration" FontWeight="Bold" Margin="0,0,0,8" Foreground="{DynamicResource PrimaryHueMidBrush}"/>
|
||||||
|
<Grid Margin="0,0,0,16">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox Grid.Column="0" Text="{Binding SelectedLane.CameraIpString}" materialDesign:HintAssist.Hint="IP Address" Style="{DynamicResource MaterialDesignFloatingHintTextBox}" Margin="0,0,8,0"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding SelectedLane.CameraPort}" materialDesign:HintAssist.Hint="Port" Style="{DynamicResource MaterialDesignFloatingHintTextBox}" Margin="8,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Margin="0,0,0,24">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox Grid.Column="0" Text="{Binding SelectedLane.CameraId}" materialDesign:HintAssist.Hint="User ID" Style="{DynamicResource MaterialDesignFloatingHintTextBox}" Margin="0,0,8,0"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding SelectedLane.CameraPassword}" materialDesign:HintAssist.Hint="Password" Style="{DynamicResource MaterialDesignFloatingHintTextBox}" Margin="8,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Recognition Settings -->
|
||||||
|
<TextBlock Text="Recognition Settings" FontWeight="Bold" Margin="0,0,0,8" Foreground="{DynamicResource PrimaryHueMidBrush}"/>
|
||||||
|
|
||||||
|
<Grid Margin="0,0,0,16">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<ComboBox Grid.Column="0"
|
||||||
|
ItemsSource="{Binding Source={StaticResource RecognitionOrderEnum}}"
|
||||||
|
SelectedItem="{Binding SelectedLane.RecognitionOrder}"
|
||||||
|
materialDesign:HintAssist.Hint="Recognition Order"
|
||||||
|
Style="{DynamicResource MaterialDesignFloatingHintComboBox}" Margin="0,0,8,0"/>
|
||||||
|
|
||||||
|
<!-- No Plate Code -->
|
||||||
|
<StackPanel Grid.Column="1" Margin="8,0,0,0" VerticalAlignment="Bottom">
|
||||||
|
<TextBlock Text="No Plate Result" FontSize="10" Foreground="Gray" Margin="0,0,0,4"/>
|
||||||
|
<ComboBox Text="{Binding SelectedLane.NoPlateCode}" IsEditable="True" materialDesign:HintAssist.Hint="Select or Type Code" Style="{DynamicResource MaterialDesignFloatingHintComboBox}">
|
||||||
|
<ComboBoxItem Content="XXXXXXXX"/>
|
||||||
|
<ComboBoxItem Content="????????"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- ROI / Single Shot -->
|
||||||
|
<TextBlock Text="Recognition Area (ROI)" FontWeight="Bold" Margin="0,0,0,8" Foreground="{DynamicResource PrimaryHueMidBrush}"/>
|
||||||
|
<DockPanel LastChildFill="False">
|
||||||
|
<Button Content="SINGLE SHOT" Command="{Binding SelectedLane.SingleShotCommand}" Style="{DynamicResource MaterialDesignOutlinedButton}" Margin="0,0,16,0"/>
|
||||||
|
<TextBlock Text="{Binding SelectedLane.RecognitionArea}" VerticalAlignment="Center" Foreground="Gray"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0">
|
||||||
|
<Button Content="SAVE SETTINGS" Command="{Binding SaveLaneCommand}" Style="{DynamicResource MaterialDesignRaisedButton}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.Visibility>
|
||||||
|
<Binding Path="SelectedLane" Converter="{StaticResource BooleanToVisibilityConverter}" ConverterParameter="Inverse" FallbackValue="Visible"/>
|
||||||
|
</Grid.Visibility>
|
||||||
|
<!-- Note: BooleanToVisibilityConverter standard usually True=Visible. Need Inverse for empty state if SelectedLane is null (False) -> Visible.
|
||||||
|
Standard WPF doesn't have Inverse param. Using Trigger instead. -->
|
||||||
|
<Grid.Style>
|
||||||
|
<Style TargetType="Grid">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding SelectedLane}" Value="{x:Null}">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Grid.Style>
|
||||||
|
<TextBlock Text="Select a site to configure" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#BDBDBD" FontSize="16"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</materialDesign:Card>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
12
LPR_Manager/View/SettingsControl.xaml.cs
Normal file
12
LPR_Manager/View/SettingsControl.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace LPR_Manager.View
|
||||||
|
{
|
||||||
|
public partial class SettingsControl : UserControl
|
||||||
|
{
|
||||||
|
public SettingsControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
LPR_Manager/View/SplashScreen.xaml
Normal file
50
LPR_Manager/View/SplashScreen.xaml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<Window x:Class="LPR_Manager.View.SplashScreen"
|
||||||
|
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="SplashScreen" Height="400" Width="400"
|
||||||
|
Topmost="True"
|
||||||
|
WindowStartupLocation="CenterScreen" WindowStyle="None" AllowsTransparency="True" Background="Transparent"
|
||||||
|
ResizeMode="NoResize" FontFamily="Segoe UI">
|
||||||
|
|
||||||
|
<Border CornerRadius="20" Background="#111111" BorderBrush="#333333" BorderThickness="1">
|
||||||
|
<Grid Margin="20">
|
||||||
|
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||||
|
|
||||||
|
<!-- Logo Animation Container -->
|
||||||
|
<Grid Width="120" Height="120" Margin="0,0,0,40">
|
||||||
|
<!-- Rotating Arc -->
|
||||||
|
<materialDesign:Card UniformCornerRadius="60" Background="Transparent" Padding="0">
|
||||||
|
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}"
|
||||||
|
Value="0" IsIndeterminate="True"
|
||||||
|
Width="100" Height="100"
|
||||||
|
Foreground="#2962FF"
|
||||||
|
BorderThickness="0"/>
|
||||||
|
</materialDesign:Card>
|
||||||
|
|
||||||
|
<!-- Center 'P' -->
|
||||||
|
<TextBlock Text="P" FontSize="48" FontWeight="Bold" Foreground="#2962FF"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Effect="{DynamicResource MaterialDesignShadowDepth2}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Text Branding -->
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,20">
|
||||||
|
<TextBlock Text="INNO" FontSize="20" FontWeight="Bold" Foreground="White" Margin="0,0,0,0"/>
|
||||||
|
<TextBlock Text="MATRICS" FontSize="20" FontWeight="Bold" Foreground="#2962FF"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Loading Text -->
|
||||||
|
<TextBlock Text="LOADING..." FontSize="12" FontWeight="SemiBold" Foreground="Gray"
|
||||||
|
HorizontalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- Small Progress Line -->
|
||||||
|
<ProgressBar IsIndeterminate="True" Height="2" Width="100" Margin="0,10,0,0"
|
||||||
|
Foreground="#2962FF" Background="#333333" BorderThickness="0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
||||||
12
LPR_Manager/View/SplashScreen.xaml.cs
Normal file
12
LPR_Manager/View/SplashScreen.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace LPR_Manager.View
|
||||||
|
{
|
||||||
|
public partial class SplashScreen : Window
|
||||||
|
{
|
||||||
|
public SplashScreen()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
531
LPR_Manager/ViewModel/LprLaneViewModel.cs
Normal file
531
LPR_Manager/ViewModel/LprLaneViewModel.cs
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using LPR_Manager.Model.Device.SubClass;
|
||||||
|
using LPR_Manager.Service;
|
||||||
|
using Inno.LPR;
|
||||||
|
using LPR_Manager.Model;
|
||||||
|
using LPR_Manager.Model.Device;
|
||||||
|
using Serilog;
|
||||||
|
using OpenCvSharp;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Globalization;
|
||||||
|
using LPR_Manager.Model;
|
||||||
|
|
||||||
|
namespace LPR_Manager.ViewModel
|
||||||
|
{
|
||||||
|
public partial class LprLaneViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private WushiCamera _camera;
|
||||||
|
private LicensePlateMotionDetector? _lprDetector;
|
||||||
|
//private CmsClient _cms;
|
||||||
|
private System.Threading.Timer? _statusTimer;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _laneName;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _laneId = "F0001"; // Placeholder or ctor param
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _laneDescription = "Main Entrance"; // Default or passed in ctor
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _cameraId = "admin";
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _cameraPassword = "Inno!@#$5678";
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _cameraPort = 80;
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _cameraIpString = "127.0.0.1"; // Renamed to avoid confusion with existing logic if any, but clearer to match config
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private RecognitionOrder _recognitionOrder = RecognitionOrder.BottomUp;
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _recognitionArea = "0,0,0,0";
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _noPlateCode = "XXXXXXXX";
|
||||||
|
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(ShowPlaceholder))]
|
||||||
|
private BitmapSource? _cameraImage;
|
||||||
|
|
||||||
|
public bool ShowPlaceholder => CameraImage == null;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(ShowNoPlate))]
|
||||||
|
private BitmapSource? _plateImage;
|
||||||
|
|
||||||
|
public bool ShowNoPlate => PlateImage == null;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _lastCarNumber = "-";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isVehicleDetected;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isAuthorized;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _connectionStatus = "Init";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isCameraConnected;
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isCmsConnected;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _cameraStatusText = "Disconnected";
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _cmsStatusText = "Disconnected";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _totalRecognitions;
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _successCount;
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _failCount;
|
||||||
|
|
||||||
|
public double SuccessRate => TotalRecognitions == 0 ? 0 : (double)SuccessCount / TotalRecognitions * 100;
|
||||||
|
|
||||||
|
|
||||||
|
public event Action<string, LogSeverity>? OnLog;
|
||||||
|
public event Action<LPRDataSet>? OnCarDetected;
|
||||||
|
public event Action<LprLaneViewModel>? OnRemove;
|
||||||
|
|
||||||
|
// Config properties
|
||||||
|
private LaneConfig _config;
|
||||||
|
|
||||||
|
private readonly Serilog.ILogger _logger;
|
||||||
|
|
||||||
|
public LprLaneViewModel(LaneConfig config, LogMode logMode)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
LaneName = config.LaneName;
|
||||||
|
LaneId = config.LaneId;
|
||||||
|
LaneDescription = config.LaneDescription;
|
||||||
|
CameraId = config.CameraId;
|
||||||
|
CameraPassword = config.CameraPassword;
|
||||||
|
CameraPort = config.CameraPort;
|
||||||
|
CameraIpString = config.CameraIp;
|
||||||
|
RecognitionOrder = config.RecognitionOrder;
|
||||||
|
RecognitionArea = config.RecognitionArea;
|
||||||
|
NoPlateCode = config.NoPlateCode;
|
||||||
|
|
||||||
|
// Configure Serilog
|
||||||
|
var loggerConfig = new LoggerConfiguration()
|
||||||
|
.Enrich.WithProperty("LaneId", LaneId);
|
||||||
|
|
||||||
|
if (logMode == LogMode.File || logMode == LogMode.Both)
|
||||||
|
{
|
||||||
|
loggerConfig.WriteTo.File($"Logs/{LaneId}/log-.txt", rollingInterval: RollingInterval.Day);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logMode == LogMode.Seq || logMode == LogMode.Both)
|
||||||
|
{
|
||||||
|
loggerConfig.WriteTo.Seq("http://localhost:5341");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger = loggerConfig.CreateLogger();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lprDetector = new LicensePlateMotionDetector();
|
||||||
|
Log("Engine", "Loaded");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Init Error", ex.Message, LogSeverity.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
_camera = new WushiCamera();
|
||||||
|
_camera.OnImageReceived += Camera_OnImageReceived;
|
||||||
|
_camera.OnMessage += (s, msg) => Log("Camera", msg);
|
||||||
|
|
||||||
|
// _cms = new CmsClient(_config.CmsIp, _config.CmsPort);
|
||||||
|
|
||||||
|
StartStatusPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConnectionStatus = "Connecting...";
|
||||||
|
//var cmsTask = _cms.ConnectAsync();
|
||||||
|
|
||||||
|
// await Task.WhenAll(cmsTask);
|
||||||
|
|
||||||
|
// Assuming fixed credentials for now, or pass them in if needed
|
||||||
|
_camera.Start(_config.CameraIp, LaneName, "admin", "Inno!@#$5678");
|
||||||
|
|
||||||
|
ConnectionStatus = "Running";
|
||||||
|
Log("System", "Started");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ConnectionStatus = "Error";
|
||||||
|
Log("Start Error", ex.Message, LogSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void StartStatusPolling()
|
||||||
|
{
|
||||||
|
_statusTimer = new System.Threading.Timer((state) =>
|
||||||
|
{
|
||||||
|
UpdateStatus();
|
||||||
|
}, null, 1000, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStatus()
|
||||||
|
{
|
||||||
|
if (Application.Current == null) return;
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
IsCameraConnected = _camera.IsConnected;
|
||||||
|
// IsCmsConnected = _cms.IsConnected;
|
||||||
|
|
||||||
|
CameraStatusText = IsCameraConnected ? "Connected" : "Disconnected";
|
||||||
|
CmsStatusText = IsCmsConnected ? "Connected" : "Disconnected";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LaneConfig GetConfig() => _config;
|
||||||
|
|
||||||
|
// Method to update config object before saving
|
||||||
|
public void UpdateConfig()
|
||||||
|
{
|
||||||
|
_config.LaneName = LaneName;
|
||||||
|
_config.LaneDescription = LaneDescription;
|
||||||
|
_config.CameraId = CameraId;
|
||||||
|
_config.CameraPassword = CameraPassword;
|
||||||
|
_config.CameraPort = CameraPort;
|
||||||
|
_config.CameraIp = CameraIpString;
|
||||||
|
_config.RecognitionOrder = RecognitionOrder;
|
||||||
|
_config.RecognitionArea = RecognitionArea;
|
||||||
|
_config.NoPlateCode = NoPlateCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Camera_OnImageReceived(object? sender, byte[] buffer)
|
||||||
|
{
|
||||||
|
if (buffer == null || buffer.Length == 0) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var mat = Cv2.ImDecode(buffer, ImreadModes.Color);
|
||||||
|
//mat.SaveImage("test.jpg");
|
||||||
|
|
||||||
|
var bitmapSource = OpenCvSharp.WpfExtensions.WriteableBitmapConverter.ToWriteableBitmap(mat);
|
||||||
|
bitmapSource.Freeze();
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
CameraImage = bitmapSource;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mat.Empty()) return;
|
||||||
|
|
||||||
|
// LPR Processing
|
||||||
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
if (_lprDetector == null)
|
||||||
|
{
|
||||||
|
Log("LPR Error", "Detector not initialized", LogSeverity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var boxes = _lprDetector.DetectLicensePlateExistence(mat, new OpenCvSharp.Rect(0, 0, mat.Width, mat.Height));
|
||||||
|
sw.Stop();
|
||||||
|
long detectionTime = sw.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
if (boxes.Count > 0)
|
||||||
|
{
|
||||||
|
// 1. Sort boxes based on user configuration
|
||||||
|
var sortedBoxes = _config.RecognitionOrder switch
|
||||||
|
{
|
||||||
|
RecognitionOrder.TopDown => boxes.OrderBy(b => b.y).ToList(),
|
||||||
|
RecognitionOrder.BottomUp => boxes.OrderByDescending(b => b.y).ToList(),
|
||||||
|
RecognitionOrder.LeftRight => boxes.OrderBy(b => b.x).ToList(),
|
||||||
|
RecognitionOrder.RightLeft => boxes.OrderByDescending(b => b.x).ToList(),
|
||||||
|
_ => boxes
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool validFound = false;
|
||||||
|
foreach (var box in sortedBoxes)
|
||||||
|
{
|
||||||
|
// Crop Plate Image logic
|
||||||
|
var plateRect = new OpenCvSharp.Rect((int)box.x, (int)box.y, (int)box.w, (int)box.h);
|
||||||
|
plateRect = plateRect.Intersect(new OpenCvSharp.Rect(0, 0, mat.Width, mat.Height));
|
||||||
|
|
||||||
|
if (plateRect.Width <= 0 || plateRect.Height <= 0) continue;
|
||||||
|
|
||||||
|
using var plateMat = new Mat(mat, plateRect);
|
||||||
|
|
||||||
|
|
||||||
|
var singleBoxList = new List<DeepLearning_Model_Sharp.LPR_Boxes> { box };
|
||||||
|
|
||||||
|
sw.Restart();
|
||||||
|
var result = _lprDetector.DetectLicensePlateCode(mat, singleBoxList);
|
||||||
|
sw.Stop();
|
||||||
|
long recognitionTime = sw.ElapsedMilliseconds;
|
||||||
|
var carNum = result.mRST_Code;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(carNum)) continue;
|
||||||
|
|
||||||
|
bool isValid = Regex.IsMatch(carNum, @"\d[가-힣]\d{4}");
|
||||||
|
|
||||||
|
if (isValid)
|
||||||
|
{
|
||||||
|
// Update UI with this Plate Image
|
||||||
|
var plateBitmap = OpenCvSharp.WpfExtensions.WriteableBitmapConverter.ToWriteableBitmap(plateMat);
|
||||||
|
plateBitmap.Freeze();
|
||||||
|
Application.Current.Dispatcher.Invoke(() => PlateImage = plateBitmap);
|
||||||
|
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
LastCarNumber = carNum;
|
||||||
|
IsAuthorized = true;
|
||||||
|
IsVehicleDetected = true;
|
||||||
|
|
||||||
|
TotalRecognitions++;
|
||||||
|
SuccessCount++;
|
||||||
|
OnPropertyChanged(nameof(SuccessRate));
|
||||||
|
|
||||||
|
Log($"[LP: {carNum}] (Valid) Order:{_config.RecognitionOrder} 검지: {detectionTime}ms, 인식: {recognitionTime}ms");
|
||||||
|
|
||||||
|
Task.Delay(3000).ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
if (Application.Current != null)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() => IsVehicleDetected = false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProcessDetection(carNum, "ImagePath_Placeholder.jpg");
|
||||||
|
|
||||||
|
// Clone the mat for background processing to avoid race conditions with disposal
|
||||||
|
var matClone = mat.Clone();
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SaveRecognitionImage(matClone, carNum);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
matClone.Dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Process Error", ex.Message, LogSeverity.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// STOP processing detection for this frame as we found a valid one
|
||||||
|
validFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Fallback: No Valid Plate Found but Boxes Existed
|
||||||
|
if (!validFound && boxes.Count > 0)
|
||||||
|
{
|
||||||
|
var failCode = _config.NoPlateCode; // "XXXXXXXX" or "????????"
|
||||||
|
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
LastCarNumber = failCode;
|
||||||
|
IsAuthorized = false;
|
||||||
|
IsVehicleDetected = true;
|
||||||
|
|
||||||
|
TotalRecognitions++;
|
||||||
|
FailCount++;
|
||||||
|
OnPropertyChanged(nameof(SuccessRate));
|
||||||
|
|
||||||
|
Log($"[LP: {failCode}] (No Valid Plate) Count:{boxes.Count}", LogSeverity.Warning);
|
||||||
|
|
||||||
|
Task.Delay(3000).ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
if (Application.Current != null)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() => IsVehicleDetected = false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProcessDetection(failCode, "ImagePath_Placeholder_Fail.jpg");
|
||||||
|
|
||||||
|
// Clone for background save
|
||||||
|
var matClone = mat.Clone();
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SaveRecognitionImage(matClone, failCode);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
matClone.Dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Process Fail Error", ex.Message, LogSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Error(ex, "Image Processing Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void SingleShot()
|
||||||
|
{
|
||||||
|
Log("Action", "Single Shot Triggered - Future: Open ROI Editor");
|
||||||
|
// Placeholder for ROI capture logic
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? _imageRootPath;
|
||||||
|
|
||||||
|
private void SaveRecognitionImage(Mat originalMat, string resultText)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ImageRootPath)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. Create Directory Structure
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var datePath = Path.Combine(ImageRootPath, LaneId, now.ToString("yyyy"), now.ToString("MM"), now.ToString("dd"));
|
||||||
|
if (!Directory.Exists(datePath)) Directory.CreateDirectory(datePath);
|
||||||
|
|
||||||
|
// 2. Generate Filename
|
||||||
|
var last4 = resultText.Length > 4 ? resultText.Substring(resultText.Length - 4) : resultText;
|
||||||
|
var fileName = $"{now:HHmmssfff}_{last4}.jpg";
|
||||||
|
var fullPath = Path.Combine(datePath, fileName);
|
||||||
|
|
||||||
|
// 3. Render and Encode on UI Thread (WPF requirement)
|
||||||
|
byte[]? imageBytes = null;
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
// Convert Mat to BitmapSource
|
||||||
|
var bitmapSource = OpenCvSharp.WpfExtensions.WriteableBitmapConverter.ToWriteableBitmap(originalMat);
|
||||||
|
|
||||||
|
var visual = new DrawingVisual();
|
||||||
|
using (var dc = visual.RenderOpen())
|
||||||
|
{
|
||||||
|
// Draw Image
|
||||||
|
dc.DrawImage(bitmapSource, new System.Windows.Rect(0, 0, bitmapSource.Width, bitmapSource.Height));
|
||||||
|
|
||||||
|
// Draw Text
|
||||||
|
var overlayText = $"{now:yyyy-MM-dd HH:mm:ss} {resultText}";
|
||||||
|
var typeface = new Typeface(new FontFamily("Malgun Gothic"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal);
|
||||||
|
var fontSize = 24.0;
|
||||||
|
var culture = CultureInfo.CurrentCulture;
|
||||||
|
var textPos = new System.Windows.Point(10, 10);
|
||||||
|
|
||||||
|
// Shadow (Black)
|
||||||
|
var shadowText = new FormattedText(overlayText, culture, FlowDirection.LeftToRight, typeface, fontSize, Brushes.Black, VisualTreeHelper.GetDpi(visual).PixelsPerDip);
|
||||||
|
dc.DrawText(shadowText, new System.Windows.Point(textPos.X + 2, textPos.Y + 2));
|
||||||
|
|
||||||
|
// Main (Yellow)
|
||||||
|
var mainText = new FormattedText(overlayText, culture, FlowDirection.LeftToRight, typeface, fontSize, Brushes.Yellow, VisualTreeHelper.GetDpi(visual).PixelsPerDip);
|
||||||
|
dc.DrawText(mainText, textPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render to Bitmap
|
||||||
|
var rtb = new RenderTargetBitmap(bitmapSource.PixelWidth, bitmapSource.PixelHeight, 96, 96, PixelFormats.Pbgra32);
|
||||||
|
rtb.Render(visual);
|
||||||
|
|
||||||
|
// Encode to Jpeg
|
||||||
|
var encoder = new JpegBitmapEncoder();
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(rtb));
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
encoder.Save(ms);
|
||||||
|
imageBytes = ms.ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Save to File (Background Thread)
|
||||||
|
if (imageBytes != null)
|
||||||
|
{
|
||||||
|
File.WriteAllBytes(fullPath, imageBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Image Save Error", ex.Message, LogSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessDetection(string carNum, string imagePath)
|
||||||
|
{
|
||||||
|
// Notify MainViewModel to send CMS
|
||||||
|
var data = new LPRDataSet
|
||||||
|
{
|
||||||
|
DeviceID = LaneName, // Use LaneName as DeviceID for now
|
||||||
|
RCarNumber = carNum,
|
||||||
|
Time = DateTime.Now,
|
||||||
|
ImagePath = imagePath,
|
||||||
|
TimeRaWData = DateTime.Now.ToString("yyyyMMdd-HHmmss-ffffff")
|
||||||
|
};
|
||||||
|
|
||||||
|
//if (_cms.IsConnected)
|
||||||
|
{
|
||||||
|
// _cms.SendResult(data);
|
||||||
|
Log(carNum, "Sent CMS");
|
||||||
|
}
|
||||||
|
|
||||||
|
OnCarDetected?.Invoke(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ObservableCollection<string> Logs { get; } = new ObservableCollection<string>();
|
||||||
|
|
||||||
|
|
||||||
|
private void Log(string plate, string result, LogSeverity severity = LogSeverity.Info)
|
||||||
|
{
|
||||||
|
Log($"{plate}: {result}", severity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Log(string msg, LogSeverity severity = LogSeverity.Info)
|
||||||
|
{
|
||||||
|
_logger?.Information(msg);
|
||||||
|
|
||||||
|
if (Application.Current == null) return;
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Logs.Insert(0, $"[{DateTime.Now:HH:mm:ss}] {msg}");
|
||||||
|
if (Logs.Count > 50) Logs.RemoveAt(Logs.Count - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
OnLog?.Invoke($"[{LaneName}] {msg}", severity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
_statusTimer?.Dispose();
|
||||||
|
// Dispose other resources if implemented in their classes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
377
LPR_Manager/ViewModel/MainViewModel.cs
Normal file
377
LPR_Manager/ViewModel/MainViewModel.cs
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Inno.LPR;
|
||||||
|
using LPR_Manager.Model.Device;
|
||||||
|
using LPR_Manager.Model.Device.SubClass;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Model = LPR_Manager.Model;
|
||||||
|
|
||||||
|
namespace LPR_Manager.ViewModel
|
||||||
|
{
|
||||||
|
public partial class MainViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public ObservableCollection<LprLaneViewModel> Lanes { get; } = new ObservableCollection<LprLaneViewModel>();
|
||||||
|
|
||||||
|
public ObservableCollection<LPR_Manager.Model.AppLogEntry> Logs { get; } = new ObservableCollection<LPR_Manager.Model.AppLogEntry>();
|
||||||
|
public ObservableCollection<LPR_Manager.Model.AppLogEntry> ErrorLogs { get; } = new ObservableCollection<LPR_Manager.Model.AppLogEntry>();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _hasCriticalErrors;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void ClearErrors()
|
||||||
|
{
|
||||||
|
ErrorLogs.Clear();
|
||||||
|
HasCriticalErrors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private LprLaneViewModel? _selectedLane;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _selectedTabIndex;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _connectionStatus = "Init";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _currentTime = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _healthStatus = "Optimal";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _userName = "Guest";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _userRole = "Log in to Access";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _sessionTimeRemaining = "";
|
||||||
|
|
||||||
|
private bool _isLoggedIn = false;
|
||||||
|
private DateTime _lastActivity;
|
||||||
|
|
||||||
|
private System.Threading.Timer? _statusTimer;
|
||||||
|
|
||||||
|
// Expose init task for App startup waiting
|
||||||
|
public Task InitializationTask { get; private set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private Model.SystemConfig _systemConfig = new();
|
||||||
|
|
||||||
|
private Service.ImageServer? _imageServer;
|
||||||
|
private readonly Service.ConfigService _configService = new();
|
||||||
|
|
||||||
|
public MainViewModel()
|
||||||
|
{
|
||||||
|
InitializationTask = InitializeSystemAsync();
|
||||||
|
StartStatusPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetIdleTimer()
|
||||||
|
{
|
||||||
|
_lastActivity = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartStatusPolling()
|
||||||
|
{
|
||||||
|
_statusTimer = new System.Threading.Timer((state) =>
|
||||||
|
{
|
||||||
|
UpdateStatus();
|
||||||
|
}, null, 1000, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStatus()
|
||||||
|
{
|
||||||
|
CurrentTime = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
|
||||||
|
if (_isLoggedIn)
|
||||||
|
{
|
||||||
|
var remaining = TimeSpan.FromMinutes(10) - (DateTime.Now - _lastActivity);
|
||||||
|
if (remaining.TotalSeconds <= 0)
|
||||||
|
{
|
||||||
|
Logout();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SessionTimeRemaining = remaining.ToString(@"mm\:ss");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SessionTimeRemaining = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Image Server Status
|
||||||
|
if (_imageServer != null && _imageServer.IsRunning)
|
||||||
|
{
|
||||||
|
HealthStatus = "Running";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HealthStatus = "Stopped";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitializeSystemAsync()
|
||||||
|
{
|
||||||
|
await Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConnectionStatus = "Connecting...";
|
||||||
|
|
||||||
|
// Load System Config
|
||||||
|
SystemConfig = await _configService.LoadSystemConfigAsync();
|
||||||
|
|
||||||
|
// Start Image Server
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_imageServer = new Service.ImageServer(SystemConfig);
|
||||||
|
await _imageServer.StartAsync();
|
||||||
|
AddLog($"Image Server started on port {SystemConfig.ImageServerPort}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddLog($"Image Server Start Failed: {ex.Message}", LPR_Manager.Model.LogSeverity.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
var configs = await _configService.LoadAllLanesAsync();
|
||||||
|
if (configs.Count == 0)
|
||||||
|
{
|
||||||
|
var defaultLane = new Model.LaneConfig
|
||||||
|
{
|
||||||
|
LaneName = "Entrance",
|
||||||
|
CameraIp = "192.168.100.99",
|
||||||
|
CameraPort = 80,
|
||||||
|
LaneId = "F0001",
|
||||||
|
LaneDescription = "Main Entrance",
|
||||||
|
CmsIp = "127.0.0.1",
|
||||||
|
CmsPort = 6000
|
||||||
|
};
|
||||||
|
configs.Add(defaultLane);
|
||||||
|
await _configService.SaveLaneAsync(defaultLane);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI Update must be on Dispatcher
|
||||||
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
foreach (var cfg in configs)
|
||||||
|
{
|
||||||
|
AddLaneInternal(cfg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update ImageRootPath for all lanes
|
||||||
|
foreach (var lane in Lanes)
|
||||||
|
{
|
||||||
|
lane.ImageRootPath = SystemConfig.ImageRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect All Lanes
|
||||||
|
var laneTasks = new System.Collections.Generic.List<Task>();
|
||||||
|
foreach (var lane in Lanes)
|
||||||
|
{
|
||||||
|
laneTasks.Add(lane.InitializeAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(laneTasks);
|
||||||
|
|
||||||
|
ConnectionStatus = "Running";
|
||||||
|
AddLog("System Started (Multi-Lane Mode).");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ConnectionStatus = "Error";
|
||||||
|
AddLog($"Start Error: {ex.Message}", LPR_Manager.Model.LogSeverity.Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task SaveSystemConfig()
|
||||||
|
{
|
||||||
|
await _configService.SaveSystemConfigAsync(SystemConfig);
|
||||||
|
Service.LogService.ConfigureLogging(SystemConfig.LogMode); // Apply changes immediately
|
||||||
|
|
||||||
|
// Apply Image settings immediately
|
||||||
|
foreach (var lane in Lanes)
|
||||||
|
{
|
||||||
|
lane.ImageRootPath = SystemConfig.ImageRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddLog("System settings saved. Restart required to apply changes.");
|
||||||
|
MessageBox.Show("System settings saved. Please restart the application to apply changes.", "Settings Saved", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void BrowseImageFolder()
|
||||||
|
{
|
||||||
|
var dialog = new OpenFolderDialog
|
||||||
|
{
|
||||||
|
Title = "Select Image Server Root Folder",
|
||||||
|
Multiselect = false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial Directory
|
||||||
|
if (!string.IsNullOrEmpty(SystemConfig.ImageRootPath) && System.IO.Directory.Exists(SystemConfig.ImageRootPath))
|
||||||
|
{
|
||||||
|
dialog.InitialDirectory = SystemConfig.ImageRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
SystemConfig.ImageRootPath = dialog.FolderName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task AddLane()
|
||||||
|
{
|
||||||
|
// Fix: Calculate ID based on existing max ID, not Count
|
||||||
|
int maxId = 0;
|
||||||
|
foreach (var lane in Lanes)
|
||||||
|
{
|
||||||
|
if (lane.LaneId.StartsWith("F") && int.TryParse(lane.LaneId.Substring(1), out int id))
|
||||||
|
{
|
||||||
|
if (id > maxId) maxId = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var newIdx = maxId + 1;
|
||||||
|
|
||||||
|
var cfg = new Model.LaneConfig
|
||||||
|
{
|
||||||
|
LaneName = $"Lane {newIdx}",
|
||||||
|
CameraIp = "127.0.0.1",
|
||||||
|
CameraPort = 80,
|
||||||
|
LaneId = $"F{newIdx:D4}",
|
||||||
|
LaneDescription = "New Site",
|
||||||
|
CmsIp = "127.0.0.1",
|
||||||
|
CmsPort = 6000
|
||||||
|
};
|
||||||
|
|
||||||
|
var laneVm = AddLaneInternal(cfg);
|
||||||
|
_ = laneVm.InitializeAsync();
|
||||||
|
await _configService.SaveLaneAsync(cfg);
|
||||||
|
SelectedLane = laneVm; // Auto-select new lane
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task SaveLane()
|
||||||
|
{
|
||||||
|
if (SelectedLane == null) return;
|
||||||
|
|
||||||
|
SelectedLane.UpdateConfig();
|
||||||
|
|
||||||
|
|
||||||
|
await _configService.SaveLaneAsync(SelectedLane.GetConfig());
|
||||||
|
AddLog($"Saved settings for {SelectedLane.LaneName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private LprLaneViewModel AddLaneInternal(Model.LaneConfig cfg)
|
||||||
|
{
|
||||||
|
var lane = new LprLaneViewModel(cfg, SystemConfig.LogMode);
|
||||||
|
lane.ImageRootPath = SystemConfig.ImageRootPath; // Set path
|
||||||
|
lane.OnLog += AddLog;
|
||||||
|
lane.OnRemove += async (vm) => await RemoveLane(vm);
|
||||||
|
Lanes.Add(lane);
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task RemoveLane(LprLaneViewModel vm)
|
||||||
|
{
|
||||||
|
if (vm == null) return;
|
||||||
|
|
||||||
|
var result = MessageBox.Show(Application.Current.MainWindow, $"Are you sure you want to delete {vm.LaneName}?", "Confirm Delete", MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||||
|
if (result != MessageBoxResult.Yes) return;
|
||||||
|
|
||||||
|
vm.Cleanup();
|
||||||
|
Lanes.Remove(vm);
|
||||||
|
_configService.DeleteLaneConfig(vm.LaneId);
|
||||||
|
|
||||||
|
if (SelectedLane == vm) SelectedLane = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Login()
|
||||||
|
{
|
||||||
|
if (_isLoggedIn)
|
||||||
|
{
|
||||||
|
Logout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginWin = new View.LoginWindow();
|
||||||
|
|
||||||
|
if (Application.Current.MainWindow != null && Application.Current.MainWindow != loginWin)
|
||||||
|
{
|
||||||
|
loginWin.Owner = Application.Current.MainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginWin.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
_isLoggedIn = true;
|
||||||
|
UserName = "Admin User";
|
||||||
|
UserRole = "ADMIN";
|
||||||
|
ResetIdleTimer();
|
||||||
|
SelectedTabIndex = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Logout()
|
||||||
|
{
|
||||||
|
_isLoggedIn = false;
|
||||||
|
UserName = "Guest";
|
||||||
|
UserRole = "Log in to Access";
|
||||||
|
|
||||||
|
// Redirect to Dashboard if currently on Settings
|
||||||
|
if (Application.Current != null)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
if (SelectedTabIndex == 4) SelectedTabIndex = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void GoToSettings()
|
||||||
|
{
|
||||||
|
if (_isLoggedIn)
|
||||||
|
{
|
||||||
|
SelectedTabIndex = 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If not logged in, trigger login flow
|
||||||
|
Login();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddLog(string msg, LPR_Manager.Model.LogSeverity severity = LPR_Manager.Model.LogSeverity.Info)
|
||||||
|
{
|
||||||
|
if (Application.Current == null) return;
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
var entry = new LPR_Manager.Model.AppLogEntry { Message = msg, Severity = severity, Timestamp = DateTime.Now };
|
||||||
|
Logs.Insert(0, entry);
|
||||||
|
if (Logs.Count > 1000) Logs.RemoveAt(Logs.Count - 1);
|
||||||
|
|
||||||
|
if (severity == LPR_Manager.Model.LogSeverity.Error)
|
||||||
|
{
|
||||||
|
ErrorLogs.Insert(0, entry);
|
||||||
|
HasCriticalErrors = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Logs/#P-001/log-20260130.txt
Normal file
16
Logs/#P-001/log-20260130.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
2026-01-30 12:47:10.575 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 12:47:12.661 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 12:47:12.680 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:47:12.781 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 12:48:38.420 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 12:48:40.498 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 12:48:40.518 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:48:40.623 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 12:50:10.281 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 12:50:12.346 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 12:50:12.363 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:50:12.467 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 12:54:21.792 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 12:54:23.882 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 12:54:23.890 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:54:23.995 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
11
Logs/#P-001/log-20260130_001.txt
Normal file
11
Logs/#P-001/log-20260130_001.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
2026-01-30 12:54:21.863 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 12:54:23.906 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 12:54:23.910 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:54:24.058 +09:00 [INF] Camera: Camera Auth: -1, ERROR
|
||||||
|
2026-01-30 12:54:41.173 +09:00 [INF] Camera: Camera Entrance Connected: False
|
||||||
|
2026-01-30 12:54:41.176 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:54:41.244 +09:00 [INF] Camera: Camera Auth: -1, ERROR
|
||||||
|
2026-01-30 12:54:46.047 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 12:54:48.126 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 12:54:48.137 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:54:48.240 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
153
Logs/#P-001/log-20260130_002.txt
Normal file
153
Logs/#P-001/log-20260130_002.txt
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
2026-01-30 12:54:46.108 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 12:54:48.149 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 12:54:48.153 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:54:48.302 +09:00 [INF] Camera: Camera Auth: -1, ERROR
|
||||||
|
2026-01-30 12:55:11.209 +09:00 [INF] Camera: Camera Entrance Connected: False
|
||||||
|
2026-01-30 12:55:11.212 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:55:11.278 +09:00 [INF] Camera: Camera Auth: -1, ERROR
|
||||||
|
2026-01-30 13:10:32.619 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:10:34.700 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:10:34.719 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:10:34.825 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:12:51.133 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:12:53.211 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:12:53.215 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:12:53.327 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:13:57.728 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:13:59.803 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:13:59.819 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:13:59.927 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:17:43.362 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:17:45.424 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:17:45.428 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:17:45.543 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:20:08.779 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:20:10.843 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:20:10.846 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:20:10.957 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:21:49.918 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:21:51.992 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:21:52.011 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:21:52.117 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:22:50.512 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:22:52.571 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:22:52.595 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:22:52.696 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:26:54.547 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:26:56.618 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:26:56.636 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:26:56.743 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:27:07.603 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:27:09.660 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:27:09.664 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:27:09.779 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:28:53.224 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:28:55.283 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:28:55.286 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:28:55.397 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 13:30:41.204 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 13:30:43.276 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 13:30:43.293 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 13:30:43.396 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:00:12.290 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:00:14.384 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:00:14.400 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:00:14.509 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:22:59.841 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:23:01.932 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:23:01.951 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:23:02.059 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:24:54.813 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:24:57.095 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:24:57.114 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:24:57.225 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:26:36.015 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:26:38.072 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:26:38.075 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:26:38.186 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:27:39.066 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:27:41.134 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:27:41.137 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:27:41.247 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:28:27.130 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:28:29.191 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:28:29.195 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:28:29.308 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:31:20.028 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:31:22.104 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:31:22.107 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:31:22.223 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:40:18.823 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:41:10.320 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:41:12.382 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:41:12.386 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:41:12.494 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:44:46.031 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:44:48.088 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:44:48.092 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:44:48.205 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 14:55:07.333 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 14:55:09.393 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 14:55:09.413 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 14:55:09.513 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 15:01:55.884 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 15:01:57.949 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 15:01:57.959 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 15:01:58.074 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 15:14:14.379 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 15:14:16.462 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 15:14:16.480 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 15:14:16.584 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 15:20:47.653 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 15:20:49.716 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 15:20:49.737 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 15:20:49.844 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 15:58:16.570 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 15:58:18.687 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 15:58:18.710 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 15:58:18.822 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:02:43.224 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:02:45.285 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:02:45.321 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:02:45.424 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:03:04.640 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:03:18.423 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:03:20.472 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:03:20.492 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:03:20.600 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:05:42.811 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:05:44.897 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:05:44.920 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:05:45.028 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:08:50.505 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:08:52.562 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:08:52.582 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:08:52.692 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:13:06.500 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:13:08.566 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:13:08.570 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:13:08.680 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:14:45.711 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:14:47.774 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:14:47.803 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:14:47.913 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:29:28.994 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:29:31.094 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:29:31.115 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:29:31.220 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:33:25.655 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:33:27.765 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:33:27.778 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:33:27.905 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:37:45.848 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:37:47.938 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:37:47.956 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:37:48.057 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:40:07.541 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:40:09.640 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:40:09.663 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:40:09.774 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 16:41:01.389 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 16:41:03.462 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 16:41:03.480 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 16:41:03.585 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
44
Logs/#P-001/log-20260202.txt
Normal file
44
Logs/#P-001/log-20260202.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
2026-02-02 11:05:31.977 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 11:05:32.184 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 11:05:32.202 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 11:05:32.338 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 11:31:04.489 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 11:31:04.633 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 11:31:04.653 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 11:31:04.846 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 11:35:57.381 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 11:35:57.425 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 11:35:57.443 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 11:35:57.590 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 11:36:23.530 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 11:36:23.569 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 11:36:23.591 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 11:36:23.724 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 11:40:46.010 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 11:40:46.066 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 11:40:46.085 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 11:40:46.228 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 11:42:27.318 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 11:42:27.358 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 11:42:27.363 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 11:42:27.470 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 11:47:36.740 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 11:47:36.771 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 11:47:36.791 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 11:47:36.953 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 11:59:05.924 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 11:59:05.990 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 11:59:06.007 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 11:59:06.106 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 12:00:08.907 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 12:00:08.943 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 12:00:08.963 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 12:00:09.105 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 12:01:01.317 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 12:01:01.361 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 12:01:01.375 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 12:01:01.474 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-02-02 14:45:54.423 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-02-02 14:45:54.574 +09:00 [INF] System: Started
|
||||||
|
2026-02-02 14:45:54.579 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-02-02 14:45:54.739 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
8
Logs/Entrance/log-20260130.txt
Normal file
8
Logs/Entrance/log-20260130.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
2026-01-30 10:29:15.351 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:29:17.427 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:29:17.430 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 10:29:17.544 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 12:41:27.784 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 12:41:30.049 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 12:41:30.086 +09:00 [INF] Camera: Camera Entrance Connected: True
|
||||||
|
2026-01-30 12:41:30.196 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
60
Logs/Lane 1/log-20260130.txt
Normal file
60
Logs/Lane 1/log-20260130.txt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
2026-01-30 09:13:18.574 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:13:20.676 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:13:20.695 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 09:13:20.804 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 09:18:38.521 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:18:40.646 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:18:40.650 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 09:18:40.772 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 09:22:23.876 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:22:25.982 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:22:25.986 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 09:22:26.128 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 09:26:31.302 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:26:33.494 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:26:33.516 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 09:26:33.628 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 09:50:20.434 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:50:22.543 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:50:22.559 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 09:50:22.665 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 09:56:16.842 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:56:18.934 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:56:18.939 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 09:56:19.055 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 09:57:47.899 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:57:49.989 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:57:49.992 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 09:57:50.112 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 09:58:53.338 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:58:55.437 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:58:55.442 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 09:58:55.562 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 10:00:23.003 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:00:25.091 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:00:25.094 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 10:00:25.213 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 10:03:30.282 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:03:32.388 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:03:32.408 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 10:03:32.519 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 10:14:03.816 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:14:05.909 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:14:05.927 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 10:14:06.026 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 10:14:43.046 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:14:45.142 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:14:45.152 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 10:14:45.286 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 10:15:14.922 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:15:17.017 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:15:17.022 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 10:15:17.156 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 10:20:44.132 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:20:46.225 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:20:46.242 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 10:20:46.348 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
|
2026-01-30 10:23:25.697 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:23:27.783 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:23:27.787 +09:00 [INF] Camera: Camera Lane 1 Connected: True
|
||||||
|
2026-01-30 10:23:27.910 +09:00 [INF] Camera: Camera Auth: 0, OK
|
||||||
30
Logs/Lane 2/log-20260130.txt
Normal file
30
Logs/Lane 2/log-20260130.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
2026-01-30 09:13:18.619 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:13:20.673 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:18:38.589 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:18:40.650 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:22:23.925 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:22:25.978 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:26:31.457 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:26:33.498 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:50:20.494 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:50:22.540 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:56:16.889 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:56:18.936 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:57:47.948 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:57:49.992 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 09:58:53.386 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 09:58:55.435 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:00:23.047 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:00:25.089 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:03:30.331 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:03:32.385 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:14:03.862 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:14:05.903 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:14:43.096 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:14:45.148 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:15:14.970 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:15:17.022 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:20:44.177 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:20:46.229 +09:00 [INF] System: Started
|
||||||
|
2026-01-30 10:23:25.747 +09:00 [INF] Init Error: Detection Construct Fail
|
||||||
|
2026-01-30 10:23:27.786 +09:00 [INF] System: Started
|
||||||
91
ProjectAnalysis.md
Normal file
91
ProjectAnalysis.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# LPR_Manager 프로젝트 분석 보고서
|
||||||
|
|
||||||
|
## 1. 개요
|
||||||
|
- **목적**: 다차선 주차장 번호판 인식 결과를 실시간으로 모니터링하고, 카메라/LED/차단기를 통합 제어하는 WPF 클라이언트.
|
||||||
|
- **플랫폼**: .NET 8 WPF (`net8.0-windows`), MaterialDesign XAML 테마, CommunityToolkit.Mvvm 기반 MVVM 구조.
|
||||||
|
- **주요 의존성**: `OpenCvSharp4`/`WpfExtensions`(영상 처리), `Serilog`(로그), `Microsoft.AspNetCore.App`(내장 이미지 서버), 외부 네이티브 SDK `NtcClientAPI4Net.dll`, 사내 라이브러리 `Inno.LPR`.
|
||||||
|
- **구조 요약**: `View`/`ViewModel`/`Model`/`Service`/`Protocol` 계층으로 분리, Lane 단위를 중심으로 한 ObservableCollection 관리.
|
||||||
|
|
||||||
|
## 2. 계층별 구성
|
||||||
|
### UI (XAML)
|
||||||
|
- `MainWindow.xaml`: 좌측 네비게이션 + 상단 상태바 + 탭 기반 콘텐츠. 대시보드 탭은 `LprLaneControl` 반복, 나머지는 Placeholder UI.
|
||||||
|
- `View/LprLaneControl.xaml`: 단일 차선 카드. 카메라 스트림, 인식 번호판, 상태 Pill, 최근 로그 DataGrid 등으로 구성.
|
||||||
|
- `View/SettingsControl.xaml`: 시스템/차선 설정 편집 UI. Lane 목록과 상세 편집 폼을 하나의 뷰로 처리.
|
||||||
|
- `View/LoginWindow.xaml`, `View/SplashScreen.xaml`: 로그인 및 초기 스플래시 UX.
|
||||||
|
|
||||||
|
### ViewModel & 상태 관리
|
||||||
|
- `ViewModel/MainViewModel.cs`: 전역 상태, Lane 목록, 설정 및 인증 흐름을 담당.
|
||||||
|
- 앱 시작 시 `InitializationTask`에서 설정 로드 → 이미지 서버 기동 (`Service.ImageServer`) → Lane ViewModel 생성/초기화.
|
||||||
|
- `System.Threading.Timer`로 UI 타임스탬프, 세션 타이머, 이미지 서버 상태를 1초 주기로 갱신.
|
||||||
|
- Lane 추가/저장/삭제, 시스템 설정 저장, 로그인/로그아웃 Command 제공.
|
||||||
|
- `ViewModel/LprLaneViewModel.cs`: 카메라/인식/LED/CMS 제어에 대한 핵심 로직.
|
||||||
|
- `WushiCamera` SDK 이벤트를 받아 OpenCvSharp로 영상 디코딩/번호판 탐지/인식 수행.
|
||||||
|
- Serilog Logger를 Lane별로 구성하여 파일 혹은 Seq 출력.
|
||||||
|
- 인식 결과를 UI 속성 및 `OnCarDetected` 이벤트로 전달하고, 로컬 이미지 저장/추후 CMS 전송 준비.
|
||||||
|
|
||||||
|
### Services
|
||||||
|
- `Service.ConfigService`: `Configs/` 폴더에 Lane 및 시스템 설정을 JSON 으로 직렬화/역직렬화.
|
||||||
|
- `Service.ImageServer`: Minimal API (`WebApplication`)로 구동되는 정적 파일 서버. `/images/*` 경로를 Basic Auth 로 보호.
|
||||||
|
- `Service.LogService`: 전역 Serilog 싱크 구성(파일/Seq).
|
||||||
|
- `Service.WushiCamera`: `NtcClientAPI4Net.dll` P/Invoke 래퍼. 카메라 연결 및 트리거 콜백 처리.
|
||||||
|
|
||||||
|
### Device & Protocol Layer
|
||||||
|
- `Model/Device/*`: Gate/LED/CMS 컨트롤러 인터페이스 + 기본 구현. 현재 TCP 커넥터 부분은 주석 처리되어 있으며, 프로토콜 패킷 조합만 남아 있음.
|
||||||
|
- `Protocol/*`: LED(UJIN, YJMICRO), Gate(FPTV1), CMS(InnoV1) 규격 문자열/바이트 생성 및 파싱 함수.
|
||||||
|
- `Model/Device/SubClass/*`: CMS/LPR 데이터 전송 DTO, LED 텍스트 세트 등 부가 모델 정의.
|
||||||
|
|
||||||
|
## 3. 실행 흐름 요약
|
||||||
|
1. `App.OnStartup` (`App.xaml.cs`)
|
||||||
|
- `ConfigService`로 시스템 설정 로드 → `LogService` 설정.
|
||||||
|
- 메인 윈도우를 표시한 뒤 스플래시를 메인 중앙에 띄우고 `MainViewModel.InitializationTask` 또는 10초 타임아웃을 기다림.
|
||||||
|
2. `MainViewModel.InitializeSystemAsync`
|
||||||
|
- 이미지 서버 (`Service.ImageServer`)를 `SystemConfig` 기반으로 기동.
|
||||||
|
- Lane 설정을 로드하고 없으면 기본 Lane 생성/저장.
|
||||||
|
- 각 Lane마다 `LprLaneViewModel` 생성 → `InitializeAsync`로 카메라 연결 및 엔진 준비.
|
||||||
|
3. `LprLaneViewModel`
|
||||||
|
- `WushiCamera.OnImageReceived` 이벤트에서 JPEG 버퍼를 OpenCV `Mat`으로 디코드 → 번호판 박스 탐지 → 사용자가 지정한 정렬 기준으로 우선순위 결정.
|
||||||
|
- 정상 판독 시 UI 상태, 로그, `ProcessDetection` 호출, 이미지 파일 저장.
|
||||||
|
- CMS/LED/Gate 제어 부분은 주석/스텁으로 남아 있어 확장 여지 존재.
|
||||||
|
|
||||||
|
## 4. 데이터 & 설정 파일
|
||||||
|
- `Configs/system_config.json`: 이미지 서버 포트/자격증명, 이미지 저장 경로, 로그 모드.
|
||||||
|
- `Configs/*.json`: Lane 별 카메라/CMS 설정 (동적으로 생성).
|
||||||
|
- `Logs/`: Serilog 및 `WushiCamera` 로그, Lane별 로그.
|
||||||
|
- 이미지 저장 경로: `SystemConfig.ImageRootPath` (기본 `Images/`). 날짜/차선 단위 폴더 구조로 저장.
|
||||||
|
|
||||||
|
## 5. 개선이 필요한 부분 (우선순위 순)
|
||||||
|
1. **잘못된 바인딩 / 누락된 속성**
|
||||||
|
- `MainWindow.xaml:92`에서 `SessionTimeRemaining`(문자열)을 `BooleanToVisibilityConverter`에 그대로 바인딩하여 런타임 예외가 발생. 표시 여부를 위한 bool 속성을 별도로 두거나 `StringIsNullOrEmpty` 변환기를 구현해야 함.
|
||||||
|
- `View/LprLaneControl.xaml:91`는 `LastDetectionTime`에 바인딩하지만 `LprLaneViewModel`에 해당 속성이 전혀 없음. 인식 시점 추적용 속성을 추가하고 `Camera_OnImageReceived`에서 갱신해야 함.
|
||||||
|
2. **리소스 해제 부재**
|
||||||
|
- `MainViewModel`과 `LprLaneViewModel`이 `System.Threading.Timer`와 네이티브 카메라 핸들을 생성하지만 `IDisposable`을 구현하지 않아 윈도우를 닫아도 백그라운드 스레드와 네이티브 리소스가 남음 (`MainWindow.xaml.cs`는 종료 훅도 없음).
|
||||||
|
- `Service.ImageServer`는 `StartAsync`만 호출하고 애플리케이션 종료 시 `StopAsync`를 호출하지 않음. Graceful shutdown 경로를 추가해야 포트가 해제됨.
|
||||||
|
3. **동기식/무제한 영상 처리** (`ViewModel/LprLaneViewModel.cs:210` 이후)
|
||||||
|
- 카메라 SDK 콜백에서 OpenCV 처리와 WPF `Dispatcher.Invoke`가 연쇄적으로 실행되어 콜백 쓰레드가 블로킹되고 GC 압박이 큼. CPU 집약 작업을 전용 `Task.Run` 파이프라인으로 분리하고, UI 접근은 `Dispatcher.BeginInvoke`로 최소화할 필요가 있음.
|
||||||
|
- 인식 실패 시에도 모든 박스를 순회하므로 고주파 입력에서 backlog가 쉽게 발생. 프레임 드랍/율 제한이 필요.
|
||||||
|
4. **하드코딩된 인증 정보 및 보안 취약점**
|
||||||
|
- `View/LoginWindow.xaml.cs`에 관리자 ID/비밀번호가 평문으로 고정되어 있으며 시도 횟수 제한도 없음. 구성 파일 또는 외부 인증과 연동하고 암호는 해시로 비교하도록 변경 필요.
|
||||||
|
- `Service.ImageServer`는 HTTP + Basic Auth를 사용하지만 TLS가 없고 비밀번호가 `Configs/system_config.json`에 평문 저장됨. 최소한 HTTPS(개발 인증서)와 암호 해싱/암호화, 혹은 내부망 한정이라는 주석/문서가 필요.
|
||||||
|
5. **설정값 미사용**
|
||||||
|
- `LprLaneViewModel.InitializeAsync`에서 카메라 로그인 정보를 `_config` 대신 하드코딩(`admin` / `Inno!@#$5678`)으로 넘김. 설정 UI에서 값을 바꿔도 실제 카메라에는 적용되지 않음.
|
||||||
|
- `RecognitionArea` 문자열, `UseSingleShot`, CMS/LED 관련 설정이 실제 로직에 연결되어 있지 않아 UI/Config와 동작 간 괴리가 큼.
|
||||||
|
6. **저장 이미지 경로 처리 오류**
|
||||||
|
- `ProcessDetection`에서 CMS로 넘기는 `ImagePath`가 항상 플레이스홀더 문자열이라 실제 저장 경로를 외부 시스템이 참조할 수 없음 (`ImageRootPath`를 기준으로 상대/절대 경로를 반환해야 함).
|
||||||
|
- `SaveRecognitionImage`는 WPF UI 스레드에서 `RenderTargetBitmap`를 생성하므로 고해상도 프레임 처리 시 UI 프리즈가 발생. 비 UI 스레드 인코딩 또는 WriteableBitmap → JPEG 변환 파이프라인으로 교체 필요.
|
||||||
|
7. **예외 및 복구 전략 부족**
|
||||||
|
- `MainViewModel.InitializeSystemAsync`는 `Task.Run` 내부에서 예외를 삼키고 상태만 "Error"로 바꾸지만 사용자에게 구체적 원인을 표시하지 않으며 재시작 버튼도 없음.
|
||||||
|
- `WushiCamera.Start` 호출 실패 케이스(핸들 null, 반환 코드) 미검증. 연결 재시도 로직 필요.
|
||||||
|
8. **테스트/운영 편의성**
|
||||||
|
- Lane 구성이 파일로만 존재하여 UI 없이도 배포 환경에서 초기 Config를 넣기 어렵고, 버전 관리도 힘듦. 샘플 config와 Schema 설명 문서가 필요.
|
||||||
|
- `Inno.LPR` 프로젝트 및 네이티브 DLL 에 대한 빌드/배포 가이드가 없음 (README 미제공).
|
||||||
|
|
||||||
|
## 6. 권장 후속 작업
|
||||||
|
1. **ViewModel 정비**: `LprLaneViewModel`에 `LastDetectionTime` 및 bool 노출 추가, `RecognitionArea` 파싱/적용, 카메라 인증 정보/이미지 경로 반영.
|
||||||
|
2. **리소스 & 수명 주기 관리**: `MainWindow` 종료 시점에 Lane VM/이미지 서버/Timer/카메라를 명시적으로 Dispose. `App.xaml.cs` 혹은 `MainViewModel`에 `IDisposable` 구현 권장.
|
||||||
|
3. **영상 처리 파이프라인 개선**: 카메라 콜백에서 Frame Buffer를 채널로 넘기고, 전용 Task에서 OpenCV 및 저장 처리, UI 업데이트는 최소화. FPS 제한·중복 판독 Debounce 도입.
|
||||||
|
4. **보안 강화**: 로그인 절차를 Config 기반 혹은 사내 인증 연동으로 교체, 이미지 서버에 HTTPS/TLS 적용 및 비밀번호 암호화, Config 파일 접근권한 가이드 문서화.
|
||||||
|
5. **운영 도구화**: Config 폴더 구조/필드 설명, 샘플 JSON, 배포 스크립트, 로그 경로 정리 등을 README 또는 위키에 문서화.
|
||||||
|
6. **CMS/LED/Gate 통합 마무리**: 현재 주석으로 남아있는 TCP 커넥터 부분(`Model.Device`)과 `Inno.LPR` 결과 연동을 구현하여 실제 하드웨어 제어까지 닫기.
|
||||||
|
|
||||||
|
---
|
||||||
|
이 문서는 `D:/Documents/Projects/Parking/Center/LPR_Manager/LPR_Manager` 기준 소스(2026-02-02) 상태를 바탕으로 작성되었습니다.
|
||||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# LPR_Manager
|
||||||
|
|
||||||
|
Center-Based Recognition & Control System.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
- **Framework**: .NET 8.0 (WPF)
|
||||||
|
- **Architecture**: MVVM
|
||||||
|
- **Dependencies**:
|
||||||
|
- `Inno.LPR` (LPR Engine integration)
|
||||||
|
- `Inno.Novitec` (Camera SDK integration)
|
||||||
|
- `MaterialDesignThemes` (UI)
|
||||||
|
- `CommunityToolkit.Mvvm`
|
||||||
|
- `Serilog`
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
- `LPR_Manager`: Main Application (WPF).
|
||||||
|
- `Model`: Data models and Enums.
|
||||||
|
- `Protocol`: Protocol implementations (CMS, Gate, LED).
|
||||||
|
- `ViewModel`: UI Logic (`MainViewModel`).
|
||||||
|
- `Code`: Global objects.
|
||||||
|
- `Inno.LPR`: Legacy LPR Engine wrapper (Migrated to .NET 8).
|
||||||
|
- `Inno.Novitec`: Camera SDK wrapper (Migrated to .NET 8).
|
||||||
|
- `Inno.Common.Stubs`: Re-implementation of missing dependencies (`Inno.Communication`, `Inno.Rtsp`) to ensure compilation without full legacy codebase.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- The solution relies on `NtcSDK` and `Inno.LPR` native DLLs.
|
||||||
|
- Ensure `Libraries` folder in `Inno.LPR` contains the necessary DLLs (`DeepLearning*.dll`, `opencv*.dll`). These are copied to output directory on build.
|
||||||
|
|
||||||
|
## How to Run
|
||||||
|
1. Open `LPR_Manager.slnx` or `LPR_Manager.sln` in Visual Studio 2022.
|
||||||
|
2. Build Solution.
|
||||||
|
3. Run `LPR_Manager`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- **Protocols**:
|
||||||
|
- CMS: `InnoV1`
|
||||||
|
- Gate: `FPTV1`
|
||||||
|
- LED: `YJMICRO`
|
||||||
|
- **Networking**: TCP communication is implemented using a lightweight `TcpClientConnector` in `Inno.Common.Stubs`.
|
||||||
|
- **UI**: Material Design based Dashboard.
|
||||||
12
lanes_config.json
Normal file
12
lanes_config.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"LaneName": "Entrance",
|
||||||
|
"CameraIp": "192.168.100.99",
|
||||||
|
"GateIp": "127.0.0.1",
|
||||||
|
"GatePort": 5000,
|
||||||
|
"LedIp": "127.0.0.1",
|
||||||
|
"LedPort": 5001,
|
||||||
|
"LaneId": "#P-001",
|
||||||
|
"LaneDescription": "Main Entrance"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user