From 13f8a3125140605a3d2ceea65a25ac3dc193a19c Mon Sep 17 00:00:00 2001 From: phuongdm Date: Thu, 15 May 2025 10:01:56 +0700 Subject: [PATCH] completed the update PVT function --- .gitignore | 6 + RuleEngine/Common/LimitedSizeQueue.cs | 16 + RuleEngine/Common/StaticResources.cs | 224 +++++++++ RuleEngine/Constants/CommandCode.cs | 7 + RuleEngine/Constants/DeviceConfig.cs | 9 + RuleEngine/Constants/LogConstants.cs | 14 + RuleEngine/Constants/MqttTopic.cs | 8 + RuleEngine/Constants/QueueConstant.cs | 7 + RuleEngine/DTOs/DeviceMessage.cs | 52 +++ RuleEngine/Database/DatabaseContext.cs | 85 ++++ RuleEngine/Dockerfile | 26 ++ RuleEngine/Interfaces/IDeviceService.cs | 9 + RuleEngine/Logs/ruleengine_log_20250514.log | 133 ++++++ RuleEngine/Models/AppCfg.cs | 18 + RuleEngine/Models/AppVersion.cs | 16 + RuleEngine/Models/Atmtechnican.cs | 21 + RuleEngine/Models/Camera.cs | 18 + RuleEngine/Models/Car.cs | 39 ++ RuleEngine/Models/CellInfor.cs | 19 + RuleEngine/Models/DailyCar.cs | 28 ++ RuleEngine/Models/DailyKmCar.cs | 23 + RuleEngine/Models/Device.cs | 25 + RuleEngine/Models/Driver.cs | 21 + RuleEngine/Models/Escort.cs | 21 + RuleEngine/Models/History.cs | 36 ++ RuleEngine/Models/Online.cs | 38 ++ RuleEngine/Models/Owner.cs | 21 + RuleEngine/Models/Permission.cs | 26 ++ RuleEngine/Models/Recipient.cs | 17 + RuleEngine/Models/RecipientWarnCfg.cs | 15 + RuleEngine/Models/ReupLogs.cs | 20 + RuleEngine/Models/Rfid.cs | 26 ++ RuleEngine/Models/Schedule.cs | 44 ++ RuleEngine/Models/Scope.cs | 21 + RuleEngine/Models/ScopePermission.cs | 18 + RuleEngine/Models/TransactionPoint.cs | 24 + RuleEngine/Models/Treasurer.cs | 24 + RuleEngine/Models/Unit.cs | 24 + RuleEngine/Models/User.cs | 19 + RuleEngine/Models/UserUnit.cs | 17 + RuleEngine/Models/WarningEvent.cs | 28 ++ RuleEngine/Models/WarningSms.cs | 18 + RuleEngine/Models/WarningType.cs | 17 + RuleEngine/Program.cs | 39 ++ RuleEngine/Properties/launchSettings.json | 12 + RuleEngine/Requests/DeviceLogRequest.cs | 34 ++ RuleEngine/RuleEngine.csproj | 23 + RuleEngine/RuleEngine.sln | 25 + RuleEngine/RuleEngineWorker.cs | 433 ++++++++++++++++++ RuleEngine/Services/DeviceService.cs | 404 ++++++++++++++++ RuleEngine/appsettings.Development.json | 8 + RuleEngine/appsettings.json | 55 +++ SimulatedTrackingDevice/DTOs/DeviceMessage.cs | 51 +++ SimulatedTrackingDevice/Program.cs | 167 +++++++ .../SimulatedTrackingDevice.csproj | 15 + 55 files changed, 2564 insertions(+) create mode 100644 .gitignore create mode 100644 RuleEngine/Common/LimitedSizeQueue.cs create mode 100644 RuleEngine/Common/StaticResources.cs create mode 100644 RuleEngine/Constants/CommandCode.cs create mode 100644 RuleEngine/Constants/DeviceConfig.cs create mode 100644 RuleEngine/Constants/LogConstants.cs create mode 100644 RuleEngine/Constants/MqttTopic.cs create mode 100644 RuleEngine/Constants/QueueConstant.cs create mode 100644 RuleEngine/DTOs/DeviceMessage.cs create mode 100644 RuleEngine/Database/DatabaseContext.cs create mode 100644 RuleEngine/Dockerfile create mode 100644 RuleEngine/Interfaces/IDeviceService.cs create mode 100644 RuleEngine/Logs/ruleengine_log_20250514.log create mode 100644 RuleEngine/Models/AppCfg.cs create mode 100644 RuleEngine/Models/AppVersion.cs create mode 100644 RuleEngine/Models/Atmtechnican.cs create mode 100644 RuleEngine/Models/Camera.cs create mode 100644 RuleEngine/Models/Car.cs create mode 100644 RuleEngine/Models/CellInfor.cs create mode 100644 RuleEngine/Models/DailyCar.cs create mode 100644 RuleEngine/Models/DailyKmCar.cs create mode 100644 RuleEngine/Models/Device.cs create mode 100644 RuleEngine/Models/Driver.cs create mode 100644 RuleEngine/Models/Escort.cs create mode 100644 RuleEngine/Models/History.cs create mode 100644 RuleEngine/Models/Online.cs create mode 100644 RuleEngine/Models/Owner.cs create mode 100644 RuleEngine/Models/Permission.cs create mode 100644 RuleEngine/Models/Recipient.cs create mode 100644 RuleEngine/Models/RecipientWarnCfg.cs create mode 100644 RuleEngine/Models/ReupLogs.cs create mode 100644 RuleEngine/Models/Rfid.cs create mode 100644 RuleEngine/Models/Schedule.cs create mode 100644 RuleEngine/Models/Scope.cs create mode 100644 RuleEngine/Models/ScopePermission.cs create mode 100644 RuleEngine/Models/TransactionPoint.cs create mode 100644 RuleEngine/Models/Treasurer.cs create mode 100644 RuleEngine/Models/Unit.cs create mode 100644 RuleEngine/Models/User.cs create mode 100644 RuleEngine/Models/UserUnit.cs create mode 100644 RuleEngine/Models/WarningEvent.cs create mode 100644 RuleEngine/Models/WarningSms.cs create mode 100644 RuleEngine/Models/WarningType.cs create mode 100644 RuleEngine/Program.cs create mode 100644 RuleEngine/Properties/launchSettings.json create mode 100644 RuleEngine/Requests/DeviceLogRequest.cs create mode 100644 RuleEngine/RuleEngine.csproj create mode 100644 RuleEngine/RuleEngine.sln create mode 100644 RuleEngine/RuleEngineWorker.cs create mode 100644 RuleEngine/Services/DeviceService.cs create mode 100644 RuleEngine/appsettings.Development.json create mode 100644 RuleEngine/appsettings.json create mode 100644 SimulatedTrackingDevice/DTOs/DeviceMessage.cs create mode 100644 SimulatedTrackingDevice/Program.cs create mode 100644 SimulatedTrackingDevice/SimulatedTrackingDevice.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c456107 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.vs/ +.vscode/ +RuleEngine/bin/ +RuleEngine/obj/ +RuleEngine/publish/ +SimulatedTrackingDevice/obj/ \ No newline at end of file diff --git a/RuleEngine/Common/LimitedSizeQueue.cs b/RuleEngine/Common/LimitedSizeQueue.cs new file mode 100644 index 0000000..d3b905f --- /dev/null +++ b/RuleEngine/Common/LimitedSizeQueue.cs @@ -0,0 +1,16 @@ +namespace RuleEngine.Common +{ + public class LimitedSizeQueue(int limit) : Queue(limit) + { + public int Limit { get; set; } = limit; + + public new void Enqueue(T item) + { + while (Count >= Limit) + { + Dequeue(); + } + base.Enqueue(item); + } + } +} \ No newline at end of file diff --git a/RuleEngine/Common/StaticResources.cs b/RuleEngine/Common/StaticResources.cs new file mode 100644 index 0000000..6d7b082 --- /dev/null +++ b/RuleEngine/Common/StaticResources.cs @@ -0,0 +1,224 @@ +using System.Text; + +namespace RuleEngine.Common +{ + public class StaticResources + { + public static void Log2File(string filePath, string data) + { + try + { + FileInfo info = new FileInfo(filePath); + try + { + if (info.Exists && info.Length > 10000000) // delete the file first if 10 MB + { + File.Delete(filePath); + } + } + catch(Exception ex) + { + Console.WriteLine(ex.Message); + } + using var fs = File.Open(filePath, FileMode.Append); + using var sw = new StreamWriter(fs); + sw.WriteLine(data); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + public static void Log2FileByMonth(string folderPath,string fileName, string data,bool IsAddTime=true) + { + try + { + DateTime dt = DateTime.UtcNow; + TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time"); + + DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(dt, cstZone); + + var folderNameByMonth = localTime.Year.ToString() + "-" + localTime.Month.ToString(); + folderPath = Path.Combine(folderPath, folderNameByMonth); + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + var filePath = Path.Combine(folderPath, fileName); + if(IsAddTime) + { + data = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss ") + data; + } + Log2File(filePath, data); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + + } + public static void LogException2File(string folderPath, string fileName,string exception) + { + try + { + var tmpTime = DateTime.UtcNow.AddHours(7); + folderPath += tmpTime.Year.ToString() + "-" + tmpTime.Month.ToString(); + + string filePath = folderPath + "\\" + fileName + ".txt"; + try + { + FileInfo info = new FileInfo(filePath); + if (info.Exists && info.Length > 10000000) // delete the file first if 10 MB + { + File.Delete(filePath); + } + } + catch(Exception ex) + { + Console.WriteLine(ex.Message); + } + using var fs = File.Open(filePath, FileMode.Append); + using var sw = new StreamWriter(fs); + sw.WriteLine(exception); + } + catch(Exception ex) + { + Console.WriteLine(ex.Message); + } + + } + + public static double DistanceGpsCalculate(double lat1, double lon1, double lat2, double lon2, char unit) + { + try + { + double theta = lon1 - lon2; + double Dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + + Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta)); + Dist = Math.Acos(Dist); + Dist = rad2deg(Dist); + Dist = Dist * 60 * 1.1515; + if (unit == 'K') + { + Dist = Dist * 1.609344; + } + else if (unit == 'N') + { + Dist = Dist * 0.8684; + } + if (double.IsNaN(Dist)) + return 0; + return Dist; + } + catch + { + return 0; + } + } + + public static double deg2rad(double deg) + { + return (deg * Math.PI / 180.0); + } + + public static double rad2deg(double rad) + { + return (rad / Math.PI * 180.0); + } + + public static string ConvertToHexaString(string strRFID) + { + string strResult = ""; + int number = 0; + int.TryParse(strRFID, out number); + if (number != 0) + { + string hexaStr = number.ToString("X6"); + string strNum1 = hexaStr.Substring(0, 2); + string strNum2 = hexaStr.Substring(2, 2); + string strNum3 = hexaStr.Substring(4, 2); + + try + { + int num1 = int.Parse(strNum1, System.Globalization.NumberStyles.HexNumber); + int num2 = int.Parse(strNum2, System.Globalization.NumberStyles.HexNumber); + int num3 = int.Parse(strNum3, System.Globalization.NumberStyles.HexNumber); + + int num4 = num1 ^ num2 ^ num3; + + strResult = hexaStr; // +num4.ToString("X2"); //bo thong tin checksum + } + catch (Exception) + { + strResult = ""; + } + } + return strResult; + } + + public static List ConvertRFIDStringToList(string RFIDString) + { + // checksum to get rfid + var rfidList = new List(); + int checksum = 0; + + for (int k = 0; k < RFIDString.Length - 7; k++) + { + string strNum1 = RFIDString.Substring(k, 2); + string strNum2 = RFIDString.Substring(k + 2, 2); + string strNum3 = RFIDString.Substring(k + 4, 2); + string strNum4 = RFIDString.Substring(k + 6, 2); + + // loai bo cac RFID ffffffff và ffffff01 + if (strNum1 == "ff" && strNum2 == "ff") + { + continue; + } + + try + { + int num1 = int.Parse(strNum1, System.Globalization.NumberStyles.HexNumber); + int num2 = int.Parse(strNum2, System.Globalization.NumberStyles.HexNumber); + int num3 = int.Parse(strNum3, System.Globalization.NumberStyles.HexNumber); + checksum = num1 ^ num2 ^ num3; + } + catch (Exception) + { + } + + int num4 = int.Parse(strNum4, System.Globalization.NumberStyles.HexNumber); + + if (checksum == num4) + { + string rfid_hex = strNum1 + strNum2 + strNum3; + rfidList.Add(rfid_hex); + } + } + + return rfidList; + } + + public static string GetString(byte[] buffer, int index, int count) + { + try + { + var enc = new UTF8Encoding(); + if (index + count > buffer.Length) return ""; + var i = 0; + var temp = ""; + while (i < count) + { + if (buffer[index + i] == '\0') + break; + temp += enc.GetString(buffer, index + i, 1); + i++; + } + return temp.ToLower(); + } + catch + { + return ""; + } + } + } +} \ No newline at end of file diff --git a/RuleEngine/Constants/CommandCode.cs b/RuleEngine/Constants/CommandCode.cs new file mode 100644 index 0000000..1ebf107 --- /dev/null +++ b/RuleEngine/Constants/CommandCode.cs @@ -0,0 +1,7 @@ +namespace RuleEngine.Constants +{ + public class CommandCode + { + public const string UpdatePVT = "UPDATE_PVT"; + } +} diff --git a/RuleEngine/Constants/DeviceConfig.cs b/RuleEngine/Constants/DeviceConfig.cs new file mode 100644 index 0000000..25d60fd --- /dev/null +++ b/RuleEngine/Constants/DeviceConfig.cs @@ -0,0 +1,9 @@ +namespace RuleEngine.Constants +{ + public static class DeviceConfig + { + public const double SPOOFING_THRESH_DISTANCE = 10; + public const double STOP_SPEED_LIMIT = 15; + public const double SECONDS_SET_CAR_STOP = 900; // 15 phút + } +} \ No newline at end of file diff --git a/RuleEngine/Constants/LogConstants.cs b/RuleEngine/Constants/LogConstants.cs new file mode 100644 index 0000000..7a98095 --- /dev/null +++ b/RuleEngine/Constants/LogConstants.cs @@ -0,0 +1,14 @@ +namespace RuleEngine.Constants +{ + public static class LogConstants + { + public const string LOG_FILE_PATH = "../Logs/"; + public const string LOG_EXCEPTION_FILE_PATH = "../Exceptions/"; + public const string LogFileNameError = "RuleEngineErrorLog.txt"; + public const string LogFileNamePVT = "PVTLog.txt"; + public const string LogFileNameCommand = "CommandLog.txt"; + public const string LogFileNameDevice = "DeviceLog.txt"; + public const string LogFileNameCarHistory = "CarHistoryLog.txt"; + public const string LogFileNameCarHistoryError = "CarHistoryErrorLog.txt"; + } +} \ No newline at end of file diff --git a/RuleEngine/Constants/MqttTopic.cs b/RuleEngine/Constants/MqttTopic.cs new file mode 100644 index 0000000..1586213 --- /dev/null +++ b/RuleEngine/Constants/MqttTopic.cs @@ -0,0 +1,8 @@ +namespace RuleEngine.Constants +{ + public class MqttTopic + { + public const string UpdatePVTFormat = "devices/update_pvt/{0}"; + public const string UpdatePVTResponseFormat = "devices/update_pvt_response/{0}"; + } +} \ No newline at end of file diff --git a/RuleEngine/Constants/QueueConstant.cs b/RuleEngine/Constants/QueueConstant.cs new file mode 100644 index 0000000..6876db5 --- /dev/null +++ b/RuleEngine/Constants/QueueConstant.cs @@ -0,0 +1,7 @@ +namespace RuleEngine.Constants +{ + public static class QueueConstant + { + public const int QUEUESIZE = 1024 * 8; // 8MB + } +} \ No newline at end of file diff --git a/RuleEngine/DTOs/DeviceMessage.cs b/RuleEngine/DTOs/DeviceMessage.cs new file mode 100644 index 0000000..0794a15 --- /dev/null +++ b/RuleEngine/DTOs/DeviceMessage.cs @@ -0,0 +1,52 @@ +namespace RuleEngine.DTOs +{ + public class DeviceMessage + { + public string? Imei { get; set; } + public string? ReceivedTime { get; set; } + public double Longitude { get; set; } + public double Latitude { get; set; } + public int GpsSpeed { get; set; } + public string? CelID { get; set; } + public string? LacID { get; set; } + public bool IsSOS { get; set; } + public bool IsStrongBoxOpen { get; set; } + public bool IsEngineOn { get; set; } + public bool IsStopping { get; set; } + public bool IsGPSLost { get; set; } + public int TotalImgCam1 { get; set; } + public int TotalImgCam2 { get; set; } + public string? StrRFID { get; set; } + public string? StrOBD { get; set; } + public string? Provider { get; set; } + public string? Version { get; set; } + public int CpuTime { get; set; } + public int GeoMinDistance { get; set; } + public double NearestLat { get; set; } + public double NearestLon { get; set; } + public int NearestID { get; set; } + public string? NearestName { get; set; } + public int Maxsnr { get; set; } + public string? CpuTemp { get; set; } + public GpsInfor? GpsInfor { get; set; } + public bool Cam1OK { get; set; } + public bool Cam2OK { get; set; } + public bool CamDetach { get; set; } + public bool IsJamming { get; set; } + public bool IsSpoofing { get; set; } + } + + public class GpsInfor + { + public double AgcLevelDb { get; set; } + public double Maxsnr { get; set; } + public int SatelliteUseInFixed { get; set; } + public string? Cellid { get; set; } + public string? Lacid { get; set; } + public string? Mcc { get; set; } + public string? Mnc { get; set; } + public string? NetworkType { get; set; } + public string? SignalStrength { get; set; } + } + +} \ No newline at end of file diff --git a/RuleEngine/Database/DatabaseContext.cs b/RuleEngine/Database/DatabaseContext.cs new file mode 100644 index 0000000..fbc2329 --- /dev/null +++ b/RuleEngine/Database/DatabaseContext.cs @@ -0,0 +1,85 @@ +using Microsoft.EntityFrameworkCore; +using RuleEngine.Models; + +namespace RuleEngine.Database +{ + public partial class DatabaseContext(DbContextOptions options) : DbContext(options) + { + public virtual DbSet AppVersion { get; set; } + public virtual DbSet Atmtechnicans { get; set; } + public virtual DbSet Cars { get; set; } + public virtual DbSet DailyCars { get; set; } + public virtual DbSet DailyKmCars { get; set; } + public virtual DbSet Devices { get; set; } + public virtual DbSet Drivers { get; set; } + public virtual DbSet Escorts { get; set; } + public virtual DbSet Histories { get; set; } + public virtual DbSet Onlines { get; set; } + public virtual DbSet Permissions { get; set; } + public virtual DbSet Rfids { get; set; } + public virtual DbSet Scopes { get; set; } + public virtual DbSet ScopePermissions { get; set; } + public virtual DbSet TransactionPoints { get; set; } + public virtual DbSet Treasurers { get; set; } + public virtual DbSet Units { get; set; } + public virtual DbSet Users { get; set; } + public virtual DbSet UserUnits { get; set; } + public virtual DbSet Recipients { get; set; } + public virtual DbSet RecipientWarnCfgs { get; set; } + public virtual DbSet WarningEvents { get; set; } + public virtual DbSet WarningTypes { get; set; } + public virtual DbSet WarningSms { get; set; } + public virtual DbSet AppCfgs { get; set; } + public virtual DbSet Cameras { get; set; } + public virtual DbSet ReupLogs { get; set; } + public virtual DbSet CellInfors { get; set; } + public virtual DbSet Schedules { get; set; } + public virtual DbSet Owners { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.ToTable("Schedule"); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("Owner"); + }); + + modelBuilder.Entity(entity => + { + entity.Property(e => e.ActivationTime).HasColumnType("date"); + + entity.Property(e => e.AllowUpdate).HasDefaultValueSql("((1))"); + + entity.Property(e => e.CarId).HasColumnName("CarId"); + + entity.Property(e => e.DeviceNumber) + + .HasMaxLength(50) + .IsUnicode(false); + + entity.Property(e => e.Imei) + + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnName("IMEI"); + + entity.Property(e => e.Phone) + + .HasMaxLength(15) + .IsUnicode(false); + + entity.Property(e => e.IsActive).HasDefaultValueSql("((1))"); + + entity.Property(e => e.UnitId).HasColumnName("UnitId"); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + } +} diff --git a/RuleEngine/Dockerfile b/RuleEngine/Dockerfile new file mode 100644 index 0000000..6fe7558 --- /dev/null +++ b/RuleEngine/Dockerfile @@ -0,0 +1,26 @@ +# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +# This stage is used when running from VS in fast mode (Default for Debug configuration) +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER $APP_UID +WORKDIR /app + +# This stage is used to build the service project +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["RuleEngine.csproj", "./"] +RUN dotnet restore "./RuleEngine.csproj" +COPY . . +RUN dotnet build "./RuleEngine.csproj" -c $BUILD_CONFIGURATION -o /app/build + +# This stage is used to publish the service project to be copied to the final stage +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./RuleEngine.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "RuleEngine.dll"] \ No newline at end of file diff --git a/RuleEngine/Interfaces/IDeviceService.cs b/RuleEngine/Interfaces/IDeviceService.cs new file mode 100644 index 0000000..aa416ac --- /dev/null +++ b/RuleEngine/Interfaces/IDeviceService.cs @@ -0,0 +1,9 @@ +using RuleEngine.DTOs; + +namespace RuleEngine.Interfaces +{ + public interface IDeviceService + { + Task UpdatePVT(DeviceMessage deviceMessage); + } +} \ No newline at end of file diff --git a/RuleEngine/Logs/ruleengine_log_20250514.log b/RuleEngine/Logs/ruleengine_log_20250514.log new file mode 100644 index 0000000..1398bd5 --- /dev/null +++ b/RuleEngine/Logs/ruleengine_log_20250514.log @@ -0,0 +1,133 @@ +2025-05-14 09:17:12.740 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:12.7276706+07:00" +2025-05-14 09:17:13.796 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:13.7961081+07:00" +2025-05-14 09:17:14.796 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:14.7962902+07:00" +2025-05-14 09:17:15.811 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:15.8117548+07:00" +2025-05-14 09:17:16.820 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:16.8206737+07:00" +2025-05-14 09:17:17.827 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:17.8269688+07:00" +2025-05-14 09:17:18.828 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:18.8278520+07:00" +2025-05-14 09:17:19.828 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:19.8287620+07:00" +2025-05-14 09:17:20.838 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:20.8383296+07:00" +2025-05-14 09:17:21.841 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:21.8409546+07:00" +2025-05-14 09:17:22.853 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:22.8529941+07:00" +2025-05-14 09:17:23.868 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:23.8684148+07:00" +2025-05-14 09:17:24.882 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:24.8820786+07:00" +2025-05-14 09:17:25.883 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:25.8837286+07:00" +2025-05-14 09:17:26.885 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:26.8851262+07:00" +2025-05-14 09:17:27.892 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:27.8922497+07:00" +2025-05-14 09:17:28.905 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:28.9051632+07:00" +2025-05-14 09:17:29.916 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:29.9163686+07:00" +2025-05-14 09:17:30.922 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:30.9224367+07:00" +2025-05-14 09:17:31.937 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:31.9374909+07:00" +2025-05-14 09:17:32.968 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:32.9685765+07:00" +2025-05-14 09:17:33.970 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:33.9703344+07:00" +2025-05-14 09:17:34.979 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:34.9797880+07:00" +2025-05-14 09:17:35.988 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:35.9885948+07:00" +2025-05-14 09:17:36.995 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:36.9958101+07:00" +2025-05-14 09:17:38.002 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:38.0021677+07:00" +2025-05-14 09:17:39.004 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:39.0046798+07:00" +2025-05-14 09:17:40.005 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:40.0058954+07:00" +2025-05-14 09:17:41.013 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:41.0129768+07:00" +2025-05-14 09:17:42.027 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:42.0270897+07:00" +2025-05-14 09:17:43.030 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:43.0305310+07:00" +2025-05-14 09:17:44.046 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:44.0465228+07:00" +2025-05-14 09:17:45.047 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:45.0477713+07:00" +2025-05-14 09:17:46.057 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:46.0577440+07:00" +2025-05-14 09:17:47.061 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:47.0617174+07:00" +2025-05-14 09:17:48.076 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:48.0765205+07:00" +2025-05-14 09:17:49.089 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:49.0898046+07:00" +2025-05-14 09:17:50.092 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:50.0924799+07:00" +2025-05-14 09:17:51.092 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:51.0926721+07:00" +2025-05-14 09:17:52.095 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:52.0952919+07:00" +2025-05-14 09:17:53.102 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:53.1021814+07:00" +2025-05-14 09:17:54.104 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:54.1046435+07:00" +2025-05-14 09:17:55.119 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:55.1193882+07:00" +2025-05-14 09:17:56.126 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:56.1263659+07:00" +2025-05-14 09:17:57.133 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:57.1331774+07:00" +2025-05-14 09:17:58.141 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:58.1416681+07:00" +2025-05-14 09:17:59.151 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:17:59.1516672+07:00" +2025-05-14 09:18:00.165 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:00.1654727+07:00" +2025-05-14 09:18:01.168 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:01.1683941+07:00" +2025-05-14 09:18:02.177 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:02.1771225+07:00" +2025-05-14 09:18:03.192 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:03.1928842+07:00" +2025-05-14 09:18:04.199 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:04.1998288+07:00" +2025-05-14 09:18:05.202 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:05.2023057+07:00" +2025-05-14 09:18:06.217 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:06.2171677+07:00" +2025-05-14 09:18:07.217 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:07.2179408+07:00" +2025-05-14 09:18:08.227 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:08.2275777+07:00" +2025-05-14 09:18:09.228 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:09.2280441+07:00" +2025-05-14 09:18:10.243 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:10.2436759+07:00" +2025-05-14 09:18:11.248 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:11.2484326+07:00" +2025-05-14 09:18:12.263 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:12.2629629+07:00" +2025-05-14 09:18:13.278 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:13.2783009+07:00" +2025-05-14 09:18:14.293 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:14.2935860+07:00" +2025-05-14 09:18:15.294 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:15.2947761+07:00" +2025-05-14 09:18:16.294 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:16.2948210+07:00" +2025-05-14 09:18:17.296 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:17.2963056+07:00" +2025-05-14 09:18:18.309 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:18.3095020+07:00" +2025-05-14 09:18:19.323 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:19.3236472+07:00" +2025-05-14 09:18:20.335 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:20.3350007+07:00" +2025-05-14 09:18:21.342 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:21.3429593+07:00" +2025-05-14 09:18:22.343 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:22.3437814+07:00" +2025-05-14 09:18:23.355 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:23.3552780+07:00" +2025-05-14 09:18:24.369 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:24.3698567+07:00" +2025-05-14 09:18:25.371 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:25.3715111+07:00" +2025-05-14 09:18:26.372 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:26.3720109+07:00" +2025-05-14 09:18:27.389 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:27.3889656+07:00" +2025-05-14 09:18:28.395 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:28.3952182+07:00" +2025-05-14 09:18:29.404 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:29.4043060+07:00" +2025-05-14 09:18:30.404 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:30.4046716+07:00" +2025-05-14 09:18:31.419 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:31.4198009+07:00" +2025-05-14 09:18:32.426 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:32.4261075+07:00" +2025-05-14 09:18:33.426 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:33.4266313+07:00" +2025-05-14 09:18:34.441 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:34.4415592+07:00" +2025-05-14 09:18:35.449 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:35.4495647+07:00" +2025-05-14 09:18:36.451 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:36.4514174+07:00" +2025-05-14 09:18:37.466 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:37.4666474+07:00" +2025-05-14 09:18:38.467 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:38.4676530+07:00" +2025-05-14 09:18:39.482 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:39.4825603+07:00" +2025-05-14 09:18:40.487 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:40.4869981+07:00" +2025-05-14 09:18:41.489 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:41.4896773+07:00" +2025-05-14 09:18:42.499 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:42.4990012+07:00" +2025-05-14 09:18:43.514 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:43.5148356+07:00" +2025-05-14 09:18:44.515 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:44.5157501+07:00" +2025-05-14 09:18:45.530 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:45.5306296+07:00" +2025-05-14 09:18:46.542 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:46.5426243+07:00" +2025-05-14 09:18:47.557 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:47.5575964+07:00" +2025-05-14 09:18:48.560 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:48.5600813+07:00" +2025-05-14 09:18:49.560 +07:00 [INF] RuleEngineWorker running at: "2025-05-14T09:18:49.5602129+07:00" +2025-05-14 09:48:12.509 +07:00 [INF] RuleEngineWorker initialized with broker: 127.0.0.1:1883 +2025-05-14 09:48:12.565 +07:00 [INF] RuleEngineWorker starting at: "2025-05-14T09:48:12.5648048+07:00" +2025-05-14 09:48:12.572 +07:00 [INF] Connecting to MQTT broker at 127.0.0.1:1883... +2025-05-14 09:48:12.695 +07:00 [INF] Connected to MQTT broker successfully +2025-05-14 09:48:12.696 +07:00 [INF] Subscribing to MQTT topics... +2025-05-14 09:48:12.729 +07:00 [INF] Subscribed to topic 'iot/devices/#' with ResultCode: "GrantedQoS1" +2025-05-14 09:48:12.730 +07:00 [INF] MQTT reconnect timer initialized and started +2025-05-14 09:50:25.389 +07:00 [INF] Stopping RuleEngineWorker +2025-05-14 09:50:25.436 +07:00 [INF] Disconnected from MQTT broker +2025-05-14 09:50:25.439 +07:00 [INF] RuleEngineWorker stopping due to cancellation +2025-05-14 09:50:25.441 +07:00 [WRN] Disconnected from MQTT broker: "NormalDisconnection" +2025-05-14 09:50:25.441 +07:00 [INF] RuleEngineWorker stopped at: "2025-05-14T09:50:25.4392784+07:00" +2025-05-14 09:50:38.767 +07:00 [INF] RuleEngineWorker initialized with broker: 127.0.0.1:1883 +2025-05-14 09:50:38.824 +07:00 [INF] RuleEngineWorker starting at: "2025-05-14T09:50:38.8240853+07:00" +2025-05-14 09:50:38.832 +07:00 [INF] Connecting to MQTT broker at 127.0.0.1:1883... +2025-05-14 09:50:38.908 +07:00 [INF] Connected to MQTT broker successfully +2025-05-14 09:50:38.909 +07:00 [INF] Subscribing to MQTT topics... +2025-05-14 09:50:38.931 +07:00 [INF] Subscribed to topic 'iot/devices/#' with ResultCode: "GrantedQoS1" +2025-05-14 09:50:38.932 +07:00 [INF] MQTT reconnect timer initialized and started +2025-05-14 09:53:57.116 +07:00 [INF] Stopping RuleEngineWorker +2025-05-14 09:53:57.195 +07:00 [INF] Disconnected from MQTT broker +2025-05-14 09:53:57.198 +07:00 [INF] RuleEngineWorker stopping due to cancellation +2025-05-14 09:53:57.201 +07:00 [INF] RuleEngineWorker stopped at: "2025-05-14T09:53:57.1988118+07:00" +2025-05-14 09:53:57.201 +07:00 [WRN] Disconnected from MQTT broker: "NormalDisconnection" +2025-05-14 22:33:34.714 +07:00 [INF] RuleEngineWorker initialized with broker: localhost:1883 +2025-05-14 22:33:34.795 +07:00 [INF] RuleEngineWorker starting at: "2025-05-14T22:33:34.7949053+07:00" +2025-05-14 22:33:34.803 +07:00 [INF] Connecting to MQTT broker at localhost:1883... +2025-05-14 22:33:35.014 +07:00 [INF] Connected to MQTT broker successfully +2025-05-14 22:33:35.015 +07:00 [INF] Subscribing to MQTT topics... +2025-05-14 22:33:35.048 +07:00 [INF] Subscribed to topic 'iot/devices/#' with ResultCode: "GrantedQoS1" +2025-05-14 22:33:35.050 +07:00 [INF] MQTT reconnect timer initialized and started +2025-05-14 22:34:05.777 +07:00 [INF] Stopping RuleEngineWorker +2025-05-14 22:34:05.790 +07:00 [INF] Disconnected from MQTT broker +2025-05-14 22:34:05.791 +07:00 [INF] RuleEngineWorker stopping due to cancellation +2025-05-14 22:34:05.792 +07:00 [INF] RuleEngineWorker stopped at: "2025-05-14T22:34:05.7919355+07:00" +2025-05-14 22:34:05.793 +07:00 [WRN] Disconnected from MQTT broker: "NormalDisconnection" diff --git a/RuleEngine/Models/AppCfg.cs b/RuleEngine/Models/AppCfg.cs new file mode 100644 index 0000000..8bf4802 --- /dev/null +++ b/RuleEngine/Models/AppCfg.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class AppCfg + { + public int Id { get; set; } + public string CfgKey { get; set; } + public string CfgName { get; set; } + public double? CfgValue { get; set; } + public string CfgStrValue { get; set; } + public string Description { get; set; } + public bool? IsActive { get; set; } + } +} diff --git a/RuleEngine/Models/AppVersion.cs b/RuleEngine/Models/AppVersion.cs new file mode 100644 index 0000000..11861cf --- /dev/null +++ b/RuleEngine/Models/AppVersion.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class AppVersion + { + public int Id { get; set; } + public string FileName { get; set; } + public int? Version { get; set; } + public string FolderPath { get; set; } + public DateTime? UpdatedAt { get; set; } + } +} diff --git a/RuleEngine/Models/Atmtechnican.cs b/RuleEngine/Models/Atmtechnican.cs new file mode 100644 index 0000000..27f6d44 --- /dev/null +++ b/RuleEngine/Models/Atmtechnican.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Atmtechnican + { + public int Id { get; set; } + public string Avatar { get; set; } + public string EmployeeCode { get; set; } + public string Name { get; set; } + public string Sex { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public bool? Status { get; set; } + public int? UnitId { get; set; } + public int? Rfidid { get; set; } + } +} diff --git a/RuleEngine/Models/Camera.cs b/RuleEngine/Models/Camera.cs new file mode 100644 index 0000000..97f9fc9 --- /dev/null +++ b/RuleEngine/Models/Camera.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Camera + { + public int Id { get; set; } + public int? CamNo { get; set; } + public string CamImgPath { get; set; } + public DateTime? ReceivedTime { get; set; } + public DateTime? DeviceTime { get; set; } + public int? CarId { get; set; } + public int? DeviceDate { get; set; } + } +} diff --git a/RuleEngine/Models/Car.cs b/RuleEngine/Models/Car.cs new file mode 100644 index 0000000..59491b4 --- /dev/null +++ b/RuleEngine/Models/Car.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Car + { + public Car() + { + DailyCars = new HashSet(); + DailyKmCars = new HashSet(); + Devices = new HashSet(); + Histories = new HashSet(); + Onlines = new HashSet(); + } + + public int Id { get; set; } + public string LicensePlate { get; set; } + public string Type { get; set; } + public int NumberCamera { get; set; } + public int? FirstCamPo { get; set; } + public int? FirstCamRotation { get; set; } + public int? SecondCamRotation { get; set; } + public int? Fuel { get; set; } + public int? LimitedSpeed { get; set; } + public int? UnitId { get; set; } + public int? RfidId { get; set; } + public int? DriverId { get; set; } + public int? DeviceId { get; set; } + + public virtual ICollection DailyCars { get; set; } + public virtual ICollection DailyKmCars { get; set; } + public virtual ICollection Devices { get; set; } + public virtual ICollection Histories { get; set; } + public virtual ICollection Onlines { get; set; } + } +} diff --git a/RuleEngine/Models/CellInfor.cs b/RuleEngine/Models/CellInfor.cs new file mode 100644 index 0000000..5a2a2c5 --- /dev/null +++ b/RuleEngine/Models/CellInfor.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class CellInfor + { + public int Id { get; set; } + public int? MNC { get; set; } + public int? MCC { get; set; } + public int? CellId { get; set; } + public int? LacId { get; set; } + public double? NetworkLat { get; set; } + public double? NetworkLon { get; set; } + public int? NetworkType { get; set; } + } +} diff --git a/RuleEngine/Models/DailyCar.cs b/RuleEngine/Models/DailyCar.cs new file mode 100644 index 0000000..26a28fe --- /dev/null +++ b/RuleEngine/Models/DailyCar.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class DailyCar + { + public int Id { get; set; } + public double? TotalKm { get; set; } + public int? TotalFuel { get; set; } + public int? RunningTime { get; set; } + public int? OpenSafeBox { get; set; } + public int? Conflict { get; set; } + public int? SegmentationId { get; set; } + public int? RouteDeviation { get; set; } + public int? TimeDeviation { get; set; } + public DateTime? ReportTime { get; set; } + public string CarLicensePlate { get; set; } + public string RouteCode { get; set; } + public int? CarId { get; set; } + public int? RouteId { get; set; } + + public virtual Car Car { get; set; } + } +} diff --git a/RuleEngine/Models/DailyKmCar.cs b/RuleEngine/Models/DailyKmCar.cs new file mode 100644 index 0000000..1fefeb2 --- /dev/null +++ b/RuleEngine/Models/DailyKmCar.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class DailyKmCar + { + public int Id { get; set; } + public string Driver { get; set; } + public string Treasure { get; set; } + public double? TotalKm { get; set; } + public DateTime? ReportTime { get; set; } + public string UnitName { get; set; } + public string CarLicensePlate { get; set; } + public int? UnitId { get; set; } + public int? CarId { get; set; } + + public virtual Car Car { get; set; } + } +} diff --git a/RuleEngine/Models/Device.cs b/RuleEngine/Models/Device.cs new file mode 100644 index 0000000..659a0c4 --- /dev/null +++ b/RuleEngine/Models/Device.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Device + { + public int Id { get; set; } + public string DeviceNumber { get; set; } + public string Imei { get; set; } + public string Phone { get; set; } + public int CarrierId { get; set; } + public DateTime? ActivationTime { get; set; } + public bool? IsActive { get; set; } + public bool? AllowUpdate { get; set; } + public int? UnitId { get; set; } + public int? CarId { get; set; } + + public int? FanStatus { get; set; } + public string ServerUrl { get; set; } + // public virtual Car Car { get; set; } + } +} diff --git a/RuleEngine/Models/Driver.cs b/RuleEngine/Models/Driver.cs new file mode 100644 index 0000000..264da14 --- /dev/null +++ b/RuleEngine/Models/Driver.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Driver + { + public int Id { get; set; } + public string Avatar { get; set; } + public string EmployeeCode { get; set; } + public string Name { get; set; } + public string Sex { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public bool? Status { get; set; } + public int? UnitId { get; set; } + public int? Rfidid { get; set; } + } +} diff --git a/RuleEngine/Models/Escort.cs b/RuleEngine/Models/Escort.cs new file mode 100644 index 0000000..50280d9 --- /dev/null +++ b/RuleEngine/Models/Escort.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Escort + { + public int Id { get; set; } + public string Avatar { get; set; } + public string EmployeeCode { get; set; } + public string Name { get; set; } + public string Sex { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public bool Status { get; set; } + public int? UnitId { get; set; } + public int? Rfidid { get; set; } + } +} diff --git a/RuleEngine/Models/History.cs b/RuleEngine/Models/History.cs new file mode 100644 index 0000000..f5fa9d1 --- /dev/null +++ b/RuleEngine/Models/History.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class History + { + public int Id { get; set; } + public string Rfidstring { get; set; } + public DateTime? ReceivedTime { get; set; } + public DateTime? DeviceTime { get; set; } + public int? CarStatus { get; set; } + public bool? EngineOn { get; set; } + public bool? StrongBoxOpen { get; set; } + public bool? IsSos { get; set; } + public bool? IsGPSLost { get; set; } + public double? GpsLat { get; set; } + public double? GpsLon { get; set; } + public double? NetworkLat { get; set; } + public double? NetworkLon { get; set; } + public int? GpsVelocity { get; set; } + public int? CarId { get; set; } + public int? DeviceDate { get; set; } + public int? UnitId { get; set; } + public virtual Car Car { get; set; } + public string GpsInfor { get; internal set; } + public bool IsJamming { get; internal set; } + public bool IsSpoofing { get; internal set; } + public bool Cam1OK { get; internal set; } + public bool Cam2OK { get; internal set; } + public bool CamDetach { get; internal set; } + } +} diff --git a/RuleEngine/Models/Online.cs b/RuleEngine/Models/Online.cs new file mode 100644 index 0000000..26dbd46 --- /dev/null +++ b/RuleEngine/Models/Online.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Online + { + public int Id { get; set; } + public string Rfidstring { get; set; } + public string AppVersion { get; set; } + public DateTime? ReceivedTime { get; set; } + public DateTime? DeviceTime { get; set; } + public int? CarStatus { get; set; } + public bool? EngineOn { get; set; } + public bool? StrongBoxOpen { get; set; } + public bool? IsSos { get; set; } + public bool? IsGPSLost { get; set; } + public double? GpsLat { get; set; } + public double? GpsLon { get; set; } + public string GpsInfor { get; set; } + public double? NetworkLat { get; set; } + public double? NetworkLon { get; set; } + public bool? IsJamming { get; set; } + public bool? IsSpoofing { get; set; } + + public int? GpsVelocity { get; set; } + public int? CarId { get; set; } + public int? CellId { get; set; } + public int? LacId { get; set; } + + public virtual Car Car { get; set; } + public bool IsCam1OK { get; internal set; } + public bool IsCam2OK { get; internal set; } + } +} diff --git a/RuleEngine/Models/Owner.cs b/RuleEngine/Models/Owner.cs new file mode 100644 index 0000000..1f72aa0 --- /dev/null +++ b/RuleEngine/Models/Owner.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Owner + { + public int Id { get; set; } + public string Avatar { get; set; } + public string EmployeeCode { get; set; } + public string Name { get; set; } + public int? Sex { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public bool? Status { get; set; } + public int? UnitId { get; set; } + public int? RfidId { get; set; } + } +} diff --git a/RuleEngine/Models/Permission.cs b/RuleEngine/Models/Permission.cs new file mode 100644 index 0000000..c9fb5d4 --- /dev/null +++ b/RuleEngine/Models/Permission.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Permission + { + public Permission() + { + ScopePermissions = new HashSet(); + } + + public int Id { get; set; } + public string Resource { get; set; } + public string Url { get; set; } + public string Method { get; set; } + public string Action { get; set; } + public string Filter { get; set; } + public string FilterValue { get; set; } + + public virtual ICollection ScopePermissions { get; set; } + } +} diff --git a/RuleEngine/Models/Recipient.cs b/RuleEngine/Models/Recipient.cs new file mode 100644 index 0000000..39d167c --- /dev/null +++ b/RuleEngine/Models/Recipient.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace RuleEngine.Models +{ + public partial class Recipient + { + public int Id { get; set; } + public string StaffCode { get; set; } + public string Name { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public string Position { get; set; } + public int? UnitId { get; set; } + public bool? IsActive { get; set; } + } +} diff --git a/RuleEngine/Models/RecipientWarnCfg.cs b/RuleEngine/Models/RecipientWarnCfg.cs new file mode 100644 index 0000000..b0ee1a8 --- /dev/null +++ b/RuleEngine/Models/RecipientWarnCfg.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace RuleEngine.Models +{ + public partial class RecipientWarnCfg + { + public int Id { get; set; } + public int? RecipientId { get; set; } + public int? WarningTypeId { get; set; } + public bool? IsSendSms { get; set; } + public bool? IsSendEmail { get; set; } + public bool? IsEnable { get; set; } + } +} diff --git a/RuleEngine/Models/ReupLogs.cs b/RuleEngine/Models/ReupLogs.cs new file mode 100644 index 0000000..00d0758 --- /dev/null +++ b/RuleEngine/Models/ReupLogs.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class ReupLog + { + public int Id { get; set; } + public string IMEI { get; set; } + public DateTime? ReceivedTime { get; set; } + public DateTime? FirstRecordTime { get; set; } + public DateTime? LastRecordTime { get; set; } + public int? FirstRecordId { get; set; } + public int? LastRecordId { get; set; } + public DateTime? DeviceDate { get; set; } + public int? RecordType { get; set; } + } +} diff --git a/RuleEngine/Models/Rfid.cs b/RuleEngine/Models/Rfid.cs new file mode 100644 index 0000000..f0231a6 --- /dev/null +++ b/RuleEngine/Models/Rfid.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Rfid + { + public Rfid() + { + Treasurers = new HashSet(); + } + + public int Id { get; set; } + public string CardNumber { get; set; } + public string Description { get; set; } + public DateTime? ActivationTime { get; set; } + public int Type { get; set; } + public bool IsDistributed { get; set; } + public bool? Status { get; set; } + public int? UnitId { get; set; } + + public virtual ICollection Treasurers { get; set; } + } +} diff --git a/RuleEngine/Models/Schedule.cs b/RuleEngine/Models/Schedule.cs new file mode 100644 index 0000000..ad0f234 --- /dev/null +++ b/RuleEngine/Models/Schedule.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Schedule + { + public int Id { get; set; } + public int? SampleId { get; set; } + public int? CarId { get; set; } + public string CarPlateNumber { get; set; } + public int? DriverId { get; set; } + public string DriverName { get; set; } + public int? OwnerId { get; set; } + public string OwnerName { get; set; } + public int? AtmTechnicanId { get; set; } + public string KtvAtmName { get; set; } + public int? EscortId { get; set; } + public string EscortName { get; set; } + public string SecurityName { get; set; } + public DateTime? AssignedDate { get; set; } + public DateTime? RunningDate { get; set; } + public DateTime? StartTime { get; set; } + public DateTime? EndTime { get; set; } + public string SampleCode { get; set; } + public string SampleName { get; set; } + public string ScheduleName { get; set; } + public int? UnitId { get; set; } + public string LstPoint { get; set; } + public string LstTimeLabel { get; set; } + public string SampleDetail { get; set; } + public int? SampleType { get; set; } + public double? MinutesLateArrival { get; set; } + public double? AllowedRangeKm { get; set; } + public double? DistanceTmp { get; set; } + public double? TimeTmp { get; set; } + public bool? UnlimitedTime { get; set; } + public DateTime? AccessedAt { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? DeletedAt { get; set; } + } +} diff --git a/RuleEngine/Models/Scope.cs b/RuleEngine/Models/Scope.cs new file mode 100644 index 0000000..94b6014 --- /dev/null +++ b/RuleEngine/Models/Scope.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Scope + { + public Scope() + { + Users = new HashSet(); + } + + public int Id { get; set; } + public string Name { get; set; } + public string AllowedRoute { get; set; } + + public virtual ICollection Users { get; set; } + } +} diff --git a/RuleEngine/Models/ScopePermission.cs b/RuleEngine/Models/ScopePermission.cs new file mode 100644 index 0000000..7c8a31c --- /dev/null +++ b/RuleEngine/Models/ScopePermission.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class ScopePermission + { + public int Id { get; set; } + public string Allowed { get; set; } + public string Filter { get; set; } + public int? PermissionId { get; set; } + public int? ScopeId { get; set; } + + public virtual Permission Permission { get; set; } + } +} diff --git a/RuleEngine/Models/TransactionPoint.cs b/RuleEngine/Models/TransactionPoint.cs new file mode 100644 index 0000000..d3f07be --- /dev/null +++ b/RuleEngine/Models/TransactionPoint.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class TransactionPoint + { + public int Id { get; set; } + public string PointCode { get; set; } + public string PointName { get; set; } + public int PointType { get; set; } + public string Address { get; set; } + public double Longitude { get; set; } + public double Latitude { get; set; } + public string Branch { get; set; } + public string Contact { get; set; } + public string Phone { get; set; } + public string Fax { get; set; } + public int? UnitId { get; set; } + public double? Distance { get; set; } + } +} diff --git a/RuleEngine/Models/Treasurer.cs b/RuleEngine/Models/Treasurer.cs new file mode 100644 index 0000000..9f77766 --- /dev/null +++ b/RuleEngine/Models/Treasurer.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Treasurer + { + public int Id { get; set; } + public string Avatar { get; set; } + public string EmployeeCode { get; set; } + public string Name { get; set; } + public string Sex { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public bool? Status { get; set; } + public int? UnitId { get; set; } + public int? Rfidid { get; set; } + + public virtual Rfid Rfid { get; set; } + } +} diff --git a/RuleEngine/Models/Unit.cs b/RuleEngine/Models/Unit.cs new file mode 100644 index 0000000..29b72b0 --- /dev/null +++ b/RuleEngine/Models/Unit.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class Unit + { + public Unit() + { + UserUnits = new HashSet(); + } + + public int Id { get; set; } + public string Name { get; set; } + public string Province { get; set; } + public string Description { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + + public virtual ICollection UserUnits { get; set; } + } +} diff --git a/RuleEngine/Models/User.cs b/RuleEngine/Models/User.cs new file mode 100644 index 0000000..2b2153b --- /dev/null +++ b/RuleEngine/Models/User.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class User + { + public int Id { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + public bool? Status { get; set; } + public int? ScopeId { get; set; } + + public virtual Scope Scope { get; set; } + } +} diff --git a/RuleEngine/Models/UserUnit.cs b/RuleEngine/Models/UserUnit.cs new file mode 100644 index 0000000..79bbd9e --- /dev/null +++ b/RuleEngine/Models/UserUnit.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class UserUnit + { + public int Id { get; set; } + public int UserId { get; set; } + public int UnitId { get; set; } + + public virtual Unit Unit { get; set; } + } +} diff --git a/RuleEngine/Models/WarningEvent.cs b/RuleEngine/Models/WarningEvent.cs new file mode 100644 index 0000000..a74736a --- /dev/null +++ b/RuleEngine/Models/WarningEvent.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class WarningEvent + { + public int Id { get; set; } + public int? WarningTypeId { get; set; } + public int? ScheduleId { get; set; } + public int? CarId { get; set; } + public double? StartLatitude { get; set; } + public double? StartLongitude { get; set; } + public double? EndLatitude { get; set; } + public double? EndLongitude { get; set; } + public DateTime? StartTime { get; set; } + public DateTime? EndTime { get; set; } + public string EventContent { get; set; } + public int? NearPointId { get; set; } + public string NearPointName { get; set; } + public double? NearPointDistance { get; set; } + public bool? StopSound { get; set; } + public string Reason { get; set; } + public DateTime? ReasonInputTime { get; set; } + } +} diff --git a/RuleEngine/Models/WarningSms.cs b/RuleEngine/Models/WarningSms.cs new file mode 100644 index 0000000..311db5a --- /dev/null +++ b/RuleEngine/Models/WarningSms.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class WarningSms + { + public int Id { get; set; } + public int? WarningId { get; set; } + public string Phone { get; set; } + public bool? IsSent { get; set; } + public string SmsContent { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? SentAt { get; set; } + } +} diff --git a/RuleEngine/Models/WarningType.cs b/RuleEngine/Models/WarningType.cs new file mode 100644 index 0000000..9964d0e --- /dev/null +++ b/RuleEngine/Models/WarningType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +#nullable disable + +namespace RuleEngine.Models +{ + public partial class WarningType + { + public int Id { get; set; } + public string WarningName { get; set; } + public string Description { get; set; } + public int? WarningLevel { get; set; } + public bool? AllowSendEmail { get; set; } + public bool? AllowSendSms { get; set; } + } +} diff --git a/RuleEngine/Program.cs b/RuleEngine/Program.cs new file mode 100644 index 0000000..6ec59df --- /dev/null +++ b/RuleEngine/Program.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using RuleEngine.Database; +using RuleEngine.Interfaces; +using RuleEngine.Services; +using Serilog; + +namespace RuleEngine +{ + public class Program + { + public static void Main(string[] args) + { + var builder = Host.CreateApplicationBuilder(args); + + // Cấu hình Serilog từ appsettings.json + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(builder.Configuration) + .CreateLogger(); + // Gắn Serilog vào hệ thống logging + builder.Logging.ClearProviders(); + builder.Logging.AddSerilog(Log.Logger); + + //Config cau hinh SqlServerConnection + builder.Services.AddDbContextFactory(options => + { + options.UseSqlServer(builder.Configuration.GetConnectionString("RuleEngineConnectStr")); + } + ); + + // Thêm Rule Engine vào Background Service + builder.Services.AddHostedService(); + // Thêm các service hỗ trợ Rule Engine + builder.Services.AddScoped(); + + var host = builder.Build(); + host.Run(); + } + } +} diff --git a/RuleEngine/Properties/launchSettings.json b/RuleEngine/Properties/launchSettings.json new file mode 100644 index 0000000..55fc095 --- /dev/null +++ b/RuleEngine/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "RuleEngine": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/RuleEngine/Requests/DeviceLogRequest.cs b/RuleEngine/Requests/DeviceLogRequest.cs new file mode 100644 index 0000000..d1c6f22 --- /dev/null +++ b/RuleEngine/Requests/DeviceLogRequest.cs @@ -0,0 +1,34 @@ +namespace RuleEngine.Requests +{ + public class DeviceLogRequest + { + public int CarID { get; set; } + public required string Imei { get; set; } + public int UnitID { get; set; } + public DateTime ReceivedTime { get; set; } + public double? GpsLat { get; set; } + public double? GpsLon { get; set; } + public int? GpsSpeed { get; set; } + public int? CellID { get; set; } + public int? LacID { get; set; } + public bool? IsSos { get; set; } + public bool? IsStrongBoxOpen { get; set; } + public bool? IsEngineOn { get; set; } + public bool? IsGPSLost { get; set; } + public string? StrRFID { get; set; } + public double? OriLati { get; set; } + public double? OriLongi { get; set; } + public double? NetworkLat { get; set; } + public double? NetworkLon { get; set; } + public string? Version { get; set; } + public string? GPSInfor { get; set; } + public bool? IsJamming { get; set; } + public bool? IsSpoofing { get; set; } + public bool? Cam1OK { get; set; } + public bool? Cam2OK { get; set; } + public bool? CamDetach { get; set; } + public int? CarStatus { get; set; } // carStatus: 0 - running; 1 - stopping; 2 - stopping but not fix position + public string? Message { get; set; } + public required string Code { get; set; } + } +} \ No newline at end of file diff --git a/RuleEngine/RuleEngine.csproj b/RuleEngine/RuleEngine.csproj new file mode 100644 index 0000000..93a8aef --- /dev/null +++ b/RuleEngine/RuleEngine.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + dotnet-RuleEngine-babf0c43-57b7-40b0-bbae-1a6b4a75f009 + + + + + + + + + + + + + + + + diff --git a/RuleEngine/RuleEngine.sln b/RuleEngine/RuleEngine.sln new file mode 100644 index 0000000..988ef02 --- /dev/null +++ b/RuleEngine/RuleEngine.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35931.197 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RuleEngine", "RuleEngine.csproj", "{FCC38139-19EB-472C-9D47-01F5960928AA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FCC38139-19EB-472C-9D47-01F5960928AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCC38139-19EB-472C-9D47-01F5960928AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCC38139-19EB-472C-9D47-01F5960928AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCC38139-19EB-472C-9D47-01F5960928AA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A03892B7-12D9-4E88-9EAF-244073E2B432} + EndGlobalSection +EndGlobal diff --git a/RuleEngine/RuleEngineWorker.cs b/RuleEngine/RuleEngineWorker.cs new file mode 100644 index 0000000..288e589 --- /dev/null +++ b/RuleEngine/RuleEngineWorker.cs @@ -0,0 +1,433 @@ +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using MQTTnet; +using Newtonsoft.Json; +using RuleEngine.Constants; +using RuleEngine.Database; +using RuleEngine.DTOs; +using RuleEngine.Interfaces; +using Serilog; + +namespace RuleEngine +{ + public class RuleEngineWorker : BackgroundService + { + // Configuration and dependency injection + private readonly ILogger _logger; + private readonly IConfiguration _config; + private readonly IServiceProvider _serviceProvider; + private readonly IDbContextFactory _dbContextFactory; + + // MQTT client + private IMqttClient? _mqttClient; + private MqttClientOptions _mqttOptions; + private readonly SemaphoreSlim _mqttSemaphore = new SemaphoreSlim(1, 1); + + // MQTT settings + private readonly string _mqttBroker; + private readonly int _mqttPort; + private readonly string _mqttUsername; + private readonly string _mqttPassword; + private readonly string _jwtSecret; + + // Reconnection timer + private System.Timers.Timer _reconnectTimer = null!; + private bool _isDisposed = false; + + public RuleEngineWorker( + ILogger logger, + IServiceProvider serviceProvider, + IConfiguration configuration, + IDbContextFactory dbContextFactory) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); + + // Load MQTT configuration + _mqttBroker = _config.GetSection("MqttSettings:Broker").Get() ?? "localhost"; + _mqttPort = _config.GetSection("MqttSettings:Port").Get() ?? 1883; + _jwtSecret = _config.GetSection("MqttSettings:JwtSecret").Get() ?? "emqx@sivan"; + _mqttUsername = _config.GetSection("MqttSettings:Username").Get() ?? string.Empty; + _mqttPassword = _config.GetSection("MqttSettings:Password").Get() ?? string.Empty; + + // Create MQTT options + _mqttOptions = CreateMqttClientOptions(); + + _logger.LogInformation("RuleEngineWorker initialized with broker: {Broker}:{Port}", _mqttBroker, _mqttPort); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("RuleEngineWorker starting at: {Time}", DateTimeOffset.Now); + + try + { + await InitializeMqttClient(); + InitializeReconnectTimer(); + + // Keep the service running + while (!stoppingToken.IsCancellationRequested) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("RuleEngineWorker running at: {Time}", DateTimeOffset.Now); + } + await Task.Delay(30000, stoppingToken); // Heartbeat every 30 seconds + } + } + catch (OperationCanceledException) + { + _logger.LogInformation("RuleEngineWorker stopping due to cancellation"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception in RuleEngineWorker"); + } + finally + { + await DisposeMqttClient(); + _logger.LogInformation("RuleEngineWorker stopped at: {Time}", DateTimeOffset.Now); + } + } + + private MqttClientOptions CreateMqttClientOptions() + { + return new MqttClientOptionsBuilder() + .WithTcpServer("localhost", 1883) + .WithClientId($"RuleEngineWorker-{Guid.NewGuid()}") + .WithCredentials(_mqttUsername, _mqttPassword) + .WithCleanSession(false) + .WithSessionExpiryInterval(604800) // 7 days in seconds + .WithKeepAlivePeriod(TimeSpan.FromSeconds(60)) + .WithTimeout(TimeSpan.FromSeconds(15)) + .Build(); + } + + private void InitializeReconnectTimer() + { + _reconnectTimer = new System.Timers.Timer(10000); // 10 seconds + _reconnectTimer.Elapsed += async (sender, e) => await TryReconnectMqtt(); + _reconnectTimer.AutoReset = true; + _reconnectTimer.Start(); + + _logger.LogInformation("MQTT reconnect timer initialized and started"); + } + + private async Task InitializeMqttClient() + { + await _mqttSemaphore.WaitAsync(); + try + { + _mqttClient = new MqttClientFactory().CreateMqttClient(); + + // Set up event handlers + _mqttClient.DisconnectedAsync += HandleMqttDisconnection; + _mqttClient.ApplicationMessageReceivedAsync += HandleMqttMessageReceived; + + await ConnectMqttClient(); + } + finally + { + _mqttSemaphore.Release(); + } + } + + private async Task ConnectMqttClient() + { + if (_mqttClient == null || _isDisposed) + return; + + try + { + if (!_mqttClient.IsConnected) + { + _logger.LogInformation("Connecting to MQTT broker at {Broker}:{Port}...", _mqttBroker, _mqttPort); + await _mqttClient.ConnectAsync(_mqttOptions, CancellationToken.None); + _logger.LogInformation("Connected to MQTT broker successfully"); + + await SubscribeToMqttTopics(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to connect to MQTT broker"); + } + } + + private async Task HandleMqttDisconnection(MqttClientDisconnectedEventArgs args) + { + _logger.LogWarning("Disconnected from MQTT broker: {Reason}", args.Reason); + + // ReasonCode 0 means normal disconnect, no need to log as error + if (args.Exception != null) + { + _logger.LogError(args.Exception, "MQTT disconnection error"); + } + + await Task.CompletedTask; + } + + private async Task TryReconnectMqtt() + { + if (_isDisposed) + return; + + await _mqttSemaphore.WaitAsync(); + try + { + if (_mqttClient != null && !_mqttClient.IsConnected) + { + await ConnectMqttClient(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error during MQTT reconnection attempt"); + } + finally + { + _mqttSemaphore.Release(); + } + } + + private async Task SubscribeToMqttTopics() + { + if (_mqttClient == null || !_mqttClient.IsConnected) + return; + + try + { + _logger.LogInformation("Subscribing to MQTT topics..."); + + var topicFilter = new MqttTopicFilterBuilder() + .WithTopic("iot/devices/#") + .WithAtLeastOnceQoS() + .Build(); + + var subscribeResult = await _mqttClient.SubscribeAsync(topicFilter); + + foreach (var result in subscribeResult.Items) + { + _logger.LogInformation("Subscribed to topic '{Topic}' with ResultCode: {ResultCode}", + result.TopicFilter.Topic, result.ResultCode); + + if (result.ResultCode != MqttClientSubscribeResultCode.GrantedQoS0 && + result.ResultCode != MqttClientSubscribeResultCode.GrantedQoS1 && + result.ResultCode != MqttClientSubscribeResultCode.GrantedQoS2) + { + _logger.LogWarning("Subscription to topic {Topic} was rejected: {Reason}", + result.TopicFilter.Topic, result.ResultCode); + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error subscribing to MQTT topics"); + } + } + + private async Task HandleMqttMessageReceived(MqttApplicationMessageReceivedEventArgs e) + { + if (e == null || e.ApplicationMessage == null) + return; + + string topic = e.ApplicationMessage.Topic; + var payloadBytes = e.ApplicationMessage.Payload; + + if (payloadBytes.IsEmpty) + { + _logger.LogWarning("Received empty payload for topic: {Topic}", topic); + return; + } + + string payloadJson = Encoding.UTF8.GetString(payloadBytes); + + _logger.LogInformation("Received MQTT message: {Topic} -> {PayloadLength} bytes", + topic, payloadBytes.Length); + _logger.LogDebug("Message payload: {Payload}", payloadJson); + + try + { + await ProcessMqttMessage(topic, payloadJson); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing MQTT message for topic {Topic}", topic); + } + } + + private async Task ProcessMqttMessage(string topic, string payloadJson) + { + if (!topic.StartsWith("iot/devices/")) + return; + + // Handle device update message + if (topic.EndsWith("/update_PVT")) + { + await ProcessUpdatePvtMessage(topic, payloadJson); + } + + // Add more message handlers here as needed + } + + private async Task ProcessUpdatePvtMessage(string topic, string payloadJson) + { + DeviceMessage? messageData; + + try + { + messageData = JsonConvert.DeserializeObject(payloadJson); + } + catch (JsonException ex) + { + _logger.LogError(ex, "Failed to deserialize message payload"); + return; + } + + if (messageData == null) + { + _logger.LogError("Deserialized message is null for topic {Topic}", topic); + return; + } + + using var scope = _serviceProvider.CreateScope(); + var deviceService = scope.ServiceProvider.GetRequiredService(); + + try + { + bool result = await deviceService.UpdatePVT(messageData); + string imei = messageData.Imei!; + string responseTopic = string.Format(MqttTopic.UpdatePVTResponseFormat, imei); + + object responsePayload; + + if (result) + { + _logger.LogInformation("Update PVT for device {Imei} succeeded", imei); + responsePayload = new + { + Imei = imei, + Status = "success", + Message = "Update PVT success", + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }; + } + else + { + _logger.LogWarning("Update PVT for device {Imei} failed", imei); + responsePayload = new + { + Imei = imei, + Status = "error", + Message = "Update PVT failed", + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }; + } + + await PublishMqttMessage(responseTopic, responsePayload); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing PVT update for device {Imei}", messageData.Imei); + + // Try to send error response + string responseTopic = string.Format(MqttTopic.UpdatePVTResponseFormat, messageData.Imei); + var errorPayload = new + { + Imei = messageData.Imei, + Status = "error", + Message = "Internal server error", + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }; + + await PublishMqttMessage(responseTopic, errorPayload); + } + } + + private async Task PublishMqttMessage(string topic, object payload) + { + if (_mqttClient == null || !_mqttClient.IsConnected) + { + _logger.LogWarning("Cannot publish message: MQTT client is not connected"); + return; + } + + try + { + string payloadJson = JsonConvert.SerializeObject(payload); + + var message = new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(payloadJson) + .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce) + .WithRetainFlag(false) + .Build(); + + await _mqttClient.PublishAsync(message, CancellationToken.None); + _logger.LogInformation("Published message to topic {Topic}", topic); + _logger.LogDebug("Message payload: {Payload}", payloadJson); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error publishing message to topic {Topic}", topic); + } + } + + private async Task DisposeMqttClient() + { + await _mqttSemaphore.WaitAsync(); + try + { + _isDisposed = true; + + if (_reconnectTimer != null) + { + _reconnectTimer.Stop(); + _reconnectTimer.Dispose(); + } + + if (_mqttClient != null) + { + if (_mqttClient.IsConnected) + { + await _mqttClient.DisconnectAsync(); + _logger.LogInformation("Disconnected from MQTT broker"); + } + + _mqttClient.Dispose(); + _mqttClient = null; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error disposing MQTT client"); + } + finally + { + _mqttSemaphore.Release(); + } + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Stopping RuleEngineWorker"); + await DisposeMqttClient(); + await base.StopAsync(cancellationToken); + } + + public override void Dispose() + { + _mqttSemaphore.Dispose(); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/RuleEngine/Services/DeviceService.cs b/RuleEngine/Services/DeviceService.cs new file mode 100644 index 0000000..4fa92f8 --- /dev/null +++ b/RuleEngine/Services/DeviceService.cs @@ -0,0 +1,404 @@ +using System.Globalization; +using Microsoft.EntityFrameworkCore; +using RuleEngine.Common; +using RuleEngine.Constants; +using RuleEngine.Database; +using RuleEngine.DTOs; +using RuleEngine.Interfaces; +using RuleEngine.Models; +using RuleEngine.Requests; + +namespace RuleEngine.Services +{ + public class DeviceService : IDeviceService + { + private readonly DatabaseContext _dbContext; + private readonly IConfiguration _config; + private readonly static string logFolderPath = LogConstants.LOG_FILE_PATH; + private readonly static string exceptionFolder = LogConstants.LOG_EXCEPTION_FILE_PATH; + private static readonly Dictionary> dictCarHistory = []; + + public DeviceService(DatabaseContext dbContext, IConfiguration config) + { + _dbContext = dbContext; + _config = config; + } + public async Task UpdatePVT(DeviceMessage deviceMessage) + { + DeviceLogRequest? deviceLogRequest = null; + try + { + var enableLogFile = _config.GetValue("EnableLogFile"); + if (enableLogFile) + { + string Imei = ""; + try + { + Imei = deviceMessage.Imei ?? string.Empty; + } + catch (Exception ex) + { + // Log the exception + Console.WriteLine(ex.Message); + } + } + + deviceLogRequest = CreatePVTRequest(deviceMessage); + if (deviceLogRequest == null) + { + return false; + } + if (deviceLogRequest.Code == "ERROR") + { + return false; + } + if (deviceLogRequest.GpsLat == 0 && deviceLogRequest.GpsLon == 0) + { + var online = _dbContext.Onlines.Where(x => (x.CarId ?? 0) == deviceLogRequest.CarID).FirstOrDefault(); + if (online == null) + { + await UpdateOnlineProcedure(deviceLogRequest, deviceLogRequest.Imei); + } + else + { + if ((deviceLogRequest.GpsLat == 0) && + (deviceLogRequest.GpsLon == 0)) + { + deviceLogRequest.GpsLat = online.GpsLat; + deviceLogRequest.GpsLon = online.GpsLon; + } + //if ( (deviceLogRequest.NetworkLat == 0) && + // (deviceLogRequest.NetworkLon == 0)) + //{ + // deviceLogRequest.NetworkLat = online.NetworkLat; + // deviceLogRequest.NetworkLon = online.NetworkLon; + //} + await UpdateOnlineProcedure(deviceLogRequest, deviceLogRequest.Imei); + } + } + else + { + await UpdateOnlineProcedure(deviceLogRequest, deviceLogRequest.Imei); + } + + await InsertHistoryProcedure(deviceLogRequest, deviceLogRequest.Imei); + } + catch (Exception ex) + { + // Log the exception + if (Directory.Exists(logFolderPath)) + StaticResources.Log2File(logFolderPath + "PVTException.txt", ex.ToString()); + return false; + } + + return true; + } + + private DeviceLogRequest? CreatePVTRequest(DeviceMessage deviceMessage, bool bReupPVT = false) + { + DeviceLogRequest? requestLog = null; + var strServerTime = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); + string Imei = deviceMessage?.Imei ?? string.Empty; + string strReceivedTime = deviceMessage?.ReceivedTime ?? string.Empty; + DateTime receivedTime = DateTime.UtcNow; + + try + { + receivedTime = DateTime.ParseExact(strReceivedTime, "yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); + if (!string.IsNullOrEmpty(strReceivedTime)) + { + receivedTime = Convert.ToDateTime(strReceivedTime); + } + } + catch (Exception ex) + { + StaticResources.Log2File(logFolderPath + "PVTException.txt", ex.ToString()); + } + + if ((bReupPVT == true) && (Imei != null) && (Imei != "")) + { + if (!dictCarHistory.TryGetValue(Imei, out LimitedSizeQueue? queue)) + { + queue = new LimitedSizeQueue(QueueConstant.QUEUESIZE); + dictCarHistory.Add(Imei, queue); + } + + if (queue != null && !queue.Contains(receivedTime)) + { + queue.Enqueue(receivedTime); + } + else + { + return null; + } + } + + double longtitude = deviceMessage?.Longitude ?? 0; + double latitude = deviceMessage?.Latitude ?? 0; + int gpsSpeed = deviceMessage?.GpsSpeed ?? 0; + bool isSos = deviceMessage?.IsSOS ?? false; + bool isStrongBoxOpen = deviceMessage?.IsStrongBoxOpen ?? false; //co mo ket: dong/mo + bool isEngineOn = deviceMessage?.IsEngineOn ?? false; //co dong co bat/tat + bool isStopping = deviceMessage?.IsStopping ?? false; //co dung do dung/do + bool isGPSLost = deviceMessage?.IsGPSLost ?? true; //co GPS mat/co + int totalImageCam1 = deviceMessage?.TotalImgCam1 ?? 0; + int totalImageCam2 = deviceMessage?.TotalImgCam2 ?? 0; + string strRFID = deviceMessage?.StrRFID ?? string.Empty; + string strOBD = deviceMessage?.StrOBD ?? string.Empty; + string strLac = deviceMessage?.LacID ?? string.Empty; + string version = deviceMessage?.Version ?? string.Empty; + var gpsInfor = deviceMessage?.GpsInfor; + bool isJamming = deviceMessage?.IsJamming ?? false; + bool isSpoofing = deviceMessage?.IsSpoofing ?? false; + bool cam1OK = deviceMessage?.Cam1OK ?? false; + bool cam2OK = deviceMessage?.Cam2OK ?? false; + bool camDetach = deviceMessage?.CamDetach ?? false; + double cell_lat = 0; + double cell_lon = 0; + try + { + if (gpsInfor != null) + { + int cellid = gpsInfor.Cellid != null ? Convert.ToInt32(gpsInfor.Cellid) : 0; + int lacid = gpsInfor.Lacid != null ? Convert.ToInt32(gpsInfor.Lacid) : 0; + int mcc = gpsInfor.Mcc != null ? Convert.ToInt32(gpsInfor.Mcc) : 0; + int mnc = gpsInfor.Mnc != null ? Convert.ToInt32(gpsInfor.Mnc) : 0; + string strCellType = gpsInfor.NetworkType != null ? gpsInfor.NetworkType.Trim() : string.Empty; + if ((cellid > 0) && (strCellType != null)) + { + strCellType = strCellType.ToLower(); + int? CellTypeGoogle = 0; + if (strCellType.Contains("cellsignalstrengthgsm")) + CellTypeGoogle = 3; + else if (strCellType.Contains("cellsignalstrengthcdma")) + CellTypeGoogle = 2; + else if (strCellType.Contains("cellsignalstrengthwcdma")) + CellTypeGoogle = 1; + else if (strCellType.Contains("cellsignalstrengthlte")) + CellTypeGoogle = 0; + var cellinfor = _dbContext.CellInfors.Where(x => (x.LacId == lacid) && + (x.CellId == cellid) && + (x.MCC == mcc) && (x.MNC == mnc) + && (x.NetworkType == CellTypeGoogle) + ).FirstOrDefault(); + + if (cellinfor == null) + { + if (CellTypeGoogle != null) + { + cellinfor = new CellInfor + { + CellId = cellid, + LacId = lacid, + MCC = mcc, + MNC = mnc, + NetworkType = CellTypeGoogle + }; + _dbContext.CellInfors.Add(cellinfor); + _dbContext.SaveChanges(); + } + } + else + { + cell_lat = cellinfor.NetworkLat ?? 0; + cell_lon = cellinfor.NetworkLon ?? 0; + if (cell_lat != 0 && cell_lon != 0 && latitude != 0 && longtitude != 0) + { + var dist = StaticResources.DistanceGpsCalculate(latitude, longtitude, cell_lat, cell_lon, 'K'); + if (dist > DeviceConfig.SPOOFING_THRESH_DISTANCE) + { + isSpoofing = true; + var strLog = string.Format($"{strServerTime} {Imei} {cellinfor.Id} {cellinfor.NetworkLat} {cellinfor.NetworkLon} {latitude} {longtitude} {dist}"); + if (Directory.Exists(logFolderPath)) + StaticResources.Log2File(logFolderPath + "SpoofingDetection.txt", strLog); + } + } + } + } + } + } + catch (Exception ex) + { + if (Directory.Exists(logFolderPath)) + StaticResources.Log2File(logFolderPath + "CELLException.txt", ex.ToString()); + } + return requestLog; + } + + private async Task UpdateOnlineProcedure(DeviceLogRequest request, string imei) + { + try + { + DateTime currentTimeUTC = DateTime.UtcNow; + DateTime localTime = currentTimeUTC.AddHours(7); + + if (request.ReceivedTime.Year <= (localTime.Year - 1)) //lost GPS + { + await UpdateLostGpsOnline(request.CarID, true, localTime); + return; + } + // loc ban tin tuong lai + TimeSpan diff02 = localTime.AddMinutes(5).Subtract(request.ReceivedTime); + if (diff02.TotalSeconds > 0) // TotalSeconds < 0 - ban tin gui lai, ko cap nhat bang truc tuyen + { + await UpdateOnlines(request, localTime); + } + } + catch (Exception ex) + { + StaticResources.LogException2File(exceptionFolder, imei + "_ex_pvt", DateTime.UtcNow.ToString() + " InsertLst_TrucTuyen " + ex.ToString()); + } + } + + private async Task UpdateOnlines(DeviceLogRequest request, DateTime localTime) + { + var isSos = request.IsSos == true; + var isGpsLost = request.IsGPSLost == true; + var strongBoxOpen = request.IsStrongBoxOpen == true; + var engineOn = request.IsEngineOn == true; + + var carId = request.CarID; + var deviceTime = request.ReceivedTime; + var resetStatusMinute = DeviceConfig.SECONDS_SET_CAR_STOP / 60; + + var existingOnline = await _dbContext.Onlines + .FindAsync(carId); + + if (existingOnline != null) + { + var diffSeconds = (deviceTime - existingOnline.DeviceTime)?.TotalSeconds ?? 0; + + if (diffSeconds <= 0) + { + // Device sent an older or duplicate message; do nothing + return; + } + + int carStatus = request.CarStatus ?? 0; + if (diffSeconds > resetStatusMinute) + { + carStatus = 2; // timeout -> set as disconnected + } + + // Update record + existingOnline.GpsVelocity = request.GpsSpeed ?? 0; + existingOnline.IsSos = isSos; + existingOnline.IsGPSLost = isGpsLost; + existingOnline.StrongBoxOpen = strongBoxOpen; + existingOnline.EngineOn = engineOn; + existingOnline.CarStatus = carStatus; + existingOnline.Rfidstring = request.StrRFID; + existingOnline.CellId = request.CellID; + existingOnline.LacId = request.LacID ?? 0; + existingOnline.GpsLat = request.GpsLat ?? 0; + existingOnline.GpsLon = request.GpsLon ?? 0; + existingOnline.NetworkLat = request.NetworkLat ?? 0; + existingOnline.NetworkLon = request.NetworkLon ?? 0; + existingOnline.IsJamming = request.IsJamming == true; + existingOnline.IsSpoofing = request.IsSpoofing == true; + existingOnline.IsCam1OK = request.Cam1OK == true; + existingOnline.IsCam2OK = request.Cam2OK == true; + existingOnline.GpsInfor = request.GPSInfor ?? ""; + existingOnline.ReceivedTime = localTime; + existingOnline.DeviceTime = deviceTime; + existingOnline.AppVersion = request.Version; + + _dbContext.Onlines.Update(existingOnline); + } + else + { + // Insert new record + var newOnline = new Online + { + CarId = carId, + GpsVelocity = request.GpsSpeed ?? 0, + IsSos = isSos, + IsGPSLost = isGpsLost, + StrongBoxOpen = strongBoxOpen, + EngineOn = engineOn, + CarStatus = request.CarStatus ?? 0, + Rfidstring = request.StrRFID, + CellId = request.CellID, + LacId = request.LacID ?? 0, + GpsLat = request.GpsLat ?? 0, + GpsLon = request.GpsLon ?? 0, + NetworkLat = request.NetworkLat ?? 0, + NetworkLon = request.NetworkLon ?? 0, + IsJamming = request.IsJamming == true, + IsSpoofing = request.IsSpoofing == true, + IsCam1OK = request.Cam1OK == true, + IsCam2OK = request.Cam2OK == true, + GpsInfor = request.GPSInfor ?? "", + ReceivedTime = localTime, + DeviceTime = deviceTime, + AppVersion = request.Version + }; + + await _dbContext.Onlines.AddAsync(newOnline); + } + + await _dbContext.SaveChangesAsync(); + } + + private async Task InsertHistoryProcedure(DeviceLogRequest request, string imei) + { + try + { + DateTime currentTimeUTC = DateTime.UtcNow; + DateTime localTime = currentTimeUTC.AddHours(7); + + var history = new History + { + CarId = request.CarID, + UnitId = request.UnitID, + GpsVelocity = request.GpsSpeed ?? 0, + IsSos = request.IsSos == true, + IsGPSLost = request.IsGPSLost == true, + StrongBoxOpen = request.IsStrongBoxOpen == true, + EngineOn = request.IsEngineOn == true, + CarStatus = request.CarStatus ?? 0, + Rfidstring = request.StrRFID, + GpsLat = request.GpsLat ?? 0, + GpsLon = request.GpsLon ?? 0, + GpsInfor = request.GPSInfor ?? string.Empty, + IsJamming = request.IsJamming == true, + IsSpoofing = request.IsSpoofing == true, + Cam1OK = request.Cam1OK == true, + Cam2OK = request.Cam2OK == true, + CamDetach = request.CamDetach == true, + ReceivedTime = localTime, + DeviceTime = request.ReceivedTime, + DeviceDate = int.Parse(request.ReceivedTime.ToString("yyyyMMdd")) + }; + + await _dbContext.Histories.AddAsync(history); + await _dbContext.SaveChangesAsync(); + } + catch (Exception ex) + { + StaticResources.LogException2File(exceptionFolder, imei + "_ex_pvt", DateTime.UtcNow.ToString() + " InsertHistoryProcedure " + ex.ToString()); + } + } + + public async Task UpdateLostGpsOnline(int carId, bool isGpsLost, DateTime receivedTime) + { + try + { + var online = await _dbContext.Onlines.FindAsync(carId); + + if (online != null) + { + online.IsGPSLost = isGpsLost; + online.ReceivedTime = receivedTime; + + await _dbContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + StaticResources.LogException2File(exceptionFolder, carId + "_ex_pvt", DateTime.UtcNow.ToString() + " UpdateLostGpsOnline " + ex.ToString()); + } + } + + } +} \ No newline at end of file diff --git a/RuleEngine/appsettings.Development.json b/RuleEngine/appsettings.Development.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/RuleEngine/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/RuleEngine/appsettings.json b/RuleEngine/appsettings.json new file mode 100644 index 0000000..0cda7c9 --- /dev/null +++ b/RuleEngine/appsettings.json @@ -0,0 +1,55 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Serilog": { + "Using": [ + "Serilog.Sinks.File" + ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "Logs\/ruleengine_log_.log", + "rollingInterval": "Day", + "fileSizeLimitBytes": 10485760, + "rollOnFileSizeLimit": true, + "retainedFileCountLimit": 7 + } + } + ] + }, + "MqttSettings": { + "Broker": "localhost", + "Port": 1883, + "JwtSecret": "emqx@sivan123456789@emqx@sivan123456789@emqx@sivan123456789@emqx@sivan123456789", + "Username": "emqxruleengine", + "Password": "navis@123", + "Topic": { + "DeviceStatus": "devices/status", + "DeviceHealth": "devices/health", + "PushCommand": "devices/command" + } + }, + "ConnectionStrings": { + "RuleEngineConnectStr": "Data Source=203.171.20.94,1434;Database=LPB.CarTracking;User ID=mbbcartracking05;Password=navis@123" + }, + "EnableLogFile": true, + "LogFolderPath": "..\/Logs", + "DefaultServerUrl": "", + "SPOOFING_THRESHOLD_KM": 2000, + "STOP_SPEED_LIMIT": 20, + "STOP_POINT_DISTANCE": 0.2, + "DeviceLogLength": 20000000, + "DevicePasswordSalt": "navis@salt" +} \ No newline at end of file diff --git a/SimulatedTrackingDevice/DTOs/DeviceMessage.cs b/SimulatedTrackingDevice/DTOs/DeviceMessage.cs new file mode 100644 index 0000000..c57fed7 --- /dev/null +++ b/SimulatedTrackingDevice/DTOs/DeviceMessage.cs @@ -0,0 +1,51 @@ +namespace SimulatedTrackingDevice.DTOs +{ + public class DeviceMessage + { + public string? Imei { get; set; } + public string? ReceivedTime { get; set; } + public double Longitude { get; set; } + public double Latitude { get; set; } + public int GpsSpeed { get; set; } + public string? CelID { get; set; } + public string? LacID { get; set; } + public bool IsSOS { get; set; } + public bool IsStrongBoxOpen { get; set; } + public bool IsEngineOn { get; set; } + public bool IsStopping { get; set; } + public bool IsGPSLost { get; set; } + public int TotalImgCam1 { get; set; } + public int TotalImgCam2 { get; set; } + public string? StrRFID { get; set; } + public string? StrOBD { get; set; } + public string? Provider { get; set; } + public string? Version { get; set; } + public int CpuTime { get; set; } + public int GeoMinDistance { get; set; } + public double NearestLat { get; set; } + public double NearestLon { get; set; } + public int NearestID { get; set; } + public string? NearestName { get; set; } + public int Maxsnr { get; set; } + public string? CpuTemp { get; set; } + public GpsInfor? GpsInfor { get; set; } + public bool Cam1OK { get; set; } + public bool Cam2OK { get; set; } + public bool CamDetach { get; set; } + public bool IsJamming { get; set; } + public bool IsSpoofing { get; set; } + } + + public class GpsInfor + { + public double AgcLevelDb { get; set; } + public double Maxsnr { get; set; } + public int SatelliteUseInFixed { get; set; } + public string? Cellid { get; set; } + public string? Lacid { get; set; } + public string? Mcc { get; set; } + public string? Mnc { get; set; } + public string? NetworkType { get; set; } + public string? SignalStrength { get; set; } + } +} \ No newline at end of file diff --git a/SimulatedTrackingDevice/Program.cs b/SimulatedTrackingDevice/Program.cs new file mode 100644 index 0000000..c224605 --- /dev/null +++ b/SimulatedTrackingDevice/Program.cs @@ -0,0 +1,167 @@ +using System.Security.Cryptography; +using System.Text.Json; +using System.Text; +using MQTTnet; +using MQTTnet.Protocol; +using Newtonsoft.Json; +using System.Collections.Concurrent; + +namespace SimulatedTrackingDevice +{ + internal class Program + { + private static readonly string BrokerIp = "127.0.0.1"; + //private static readonly string BrokerIp = "localhost"; + private static readonly int BrokerPort = 1883; + private static readonly string Username = "emqxdevice"; + private static readonly string Password = "sivan@123"; + private static readonly string DevicePasswordSalt = "navis@salt"; + private static readonly int secondsToReconnect = 2000; + private static readonly int secondsToHealthcheck = 10000; + + //private static readonly int DeviceCount = 1000; + + private static readonly string UpdatePVTTopic = "iot/device/update_pvt"; + private static readonly ConcurrentDictionary deviceTokens = new(); + private static readonly ConcurrentDictionary deviceSemaphores = new(); + + static async Task Main(string[] args) + { + List deviceTasks = new(); + for (int i = 1; i <= 1; i++) + { + string localMacAddress = $"DeviceTest{i:0000}"; + deviceSemaphores[localMacAddress] = new SemaphoreSlim(1, 1); // Tạo semaphore riêng cho từng thiết bị + //deviceTasks.Add(AllDevicePublish(macAddress, i)); + deviceTasks.Add(SimulateDevice(localMacAddress)); + + await Task.Delay(50); // Tránh quá tải khi tạo quá nhiều thiết bị cùng lúc + } + await Task.WhenAll(deviceTasks); + } + + private static async Task SimulateDevice(string localMacAddress) + { + try + { + var factory = new MqttClientFactory(); + var mqttClient = factory.CreateMqttClient(); + string token = ""; + + var options = new MqttClientOptionsBuilder() + .WithClientId(localMacAddress) + .WithTcpServer(BrokerIp, BrokerPort) + .WithCredentials(Username, Password) + .WithCleanSession() + .Build(); + + mqttClient.ApplicationMessageReceivedAsync += async e => + { + var macAddressProperty = e.ApplicationMessage.UserProperties + .FirstOrDefault(p => p.Name == "MacAddress")?.Value; + if (macAddressProperty == localMacAddress) + { + var commandTopic = "iot/server/" + localMacAddress + "/command"; + string topic = e.ApplicationMessage.Topic; + string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); + + if (topic == ResponseTopic) + { + if (!string.IsNullOrEmpty(payload)) + { + var response = System.Text.Json.JsonSerializer.Deserialize(payload); + token = response.AccessToken; + + // Unsubscribe khỏi topic response sau khi nhận token + await mqttClient.UnsubscribeAsync(ResponseTopic); + Console.WriteLine(localMacAddress + ": Unsubscribed from response topic"); + + // log file + Log2File("./LogFiles/" + localMacAddress + ".txt", DateTime.UtcNow.AddHours(7).ToString("yyyy/MM/dd HH:mm:ss") + " - Received: " + payload); + } + } + else if (topic == commandTopic) + { + // xu ly lenh gui tu server + Console.WriteLine(localMacAddress + ": " + payload); + // log file + Log2File("./LogFiles/" + localMacAddress + ".txt", DateTime.UtcNow.AddHours(7).ToString("yyyy/MM/dd HH:mm:ss") + " - Received: " + payload); + } + } + }; + + await mqttClient.ConnectAsync(options, CancellationToken.None); + await mqttClient.SubscribeAsync(ResponseTopic, MqttQualityOfServiceLevel.AtLeastOnce); + await SendRegistrationRequest(mqttClient, localMacAddress); + + while (string.IsNullOrEmpty(token)) + { + await Task.Delay(1000); + } + + var _factory = new MqttClientFactory(); + var _mqttClient = _factory.CreateMqttClient(); + var _options = new MqttClientOptionsBuilder() + .WithClientId(localMacAddress) + .WithTcpServer(BrokerIp, BrokerPort) + .WithCredentials(localMacAddress, token) + .WithCleanSession() + .Build(); + + _mqttClient.DisconnectedAsync += async (e) => + { + Console.WriteLine($"MQTT Disconnected. Reason: {e.ToString()}"); + + // Nếu lỗi có thể do token hết hạn, gửi lại request đăng ký + if (e.Reason == MqttClientDisconnectReason.NotAuthorized) + { + Console.WriteLine("Token might be expired. Sending Register..."); + } + }; + + await _mqttClient.ConnectAsync(_options, CancellationToken.None); + + while (true) + { + await SendHealthData(_mqttClient, localMacAddress, token); + await Task.Delay(10000); + } + } + catch (Exception ex) + { + // log file + Log2File("./LogFiles/Exceptions.txt", DateTime.UtcNow.AddHours(7).ToString("yyyy/MM/dd HH:mm:ss") + "- User: " + Username + " - " + localMacAddress + ": " + ex.ToString()); + } + } + + public static void Log2File(string filePath, string data) + { + try + { + FileInfo info = new FileInfo(filePath); + try + { + if (info.Exists && info.Length > 10000000) // delete the file first if 10 MB + { + File.Delete(filePath); + } + } + catch (Exception ex) + { + + } + using (var fs = File.Open(filePath, FileMode.Append)) + { + using (var sw = new StreamWriter(fs)) + { + sw.WriteLine(data); + } + } + } + catch (Exception ex) + { + } + } + } + +} diff --git a/SimulatedTrackingDevice/SimulatedTrackingDevice.csproj b/SimulatedTrackingDevice/SimulatedTrackingDevice.csproj new file mode 100644 index 0000000..8c69ab0 --- /dev/null +++ b/SimulatedTrackingDevice/SimulatedTrackingDevice.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + +