completed the update PVT function

This commit is contained in:
phuongdm 2025-05-15 10:01:56 +07:00
commit 13f8a31251
55 changed files with 2564 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.vs/
.vscode/
RuleEngine/bin/
RuleEngine/obj/
RuleEngine/publish/
SimulatedTrackingDevice/obj/

View File

@ -0,0 +1,16 @@
namespace RuleEngine.Common
{
public class LimitedSizeQueue<T>(int limit) : Queue<T>(limit)
{
public int Limit { get; set; } = limit;
public new void Enqueue(T item)
{
while (Count >= Limit)
{
Dequeue();
}
base.Enqueue(item);
}
}
}

View File

@ -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<string> ConvertRFIDStringToList(string RFIDString)
{
// checksum to get rfid
var rfidList = new List<string>();
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 "";
}
}
}
}

View File

@ -0,0 +1,7 @@
namespace RuleEngine.Constants
{
public class CommandCode
{
public const string UpdatePVT = "UPDATE_PVT";
}
}

View File

@ -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
}
}

View File

@ -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";
}
}

View File

@ -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}";
}
}

View File

@ -0,0 +1,7 @@
namespace RuleEngine.Constants
{
public static class QueueConstant
{
public const int QUEUESIZE = 1024 * 8; // 8MB
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,85 @@
using Microsoft.EntityFrameworkCore;
using RuleEngine.Models;
namespace RuleEngine.Database
{
public partial class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbContext(options)
{
public virtual DbSet<AppVersion> AppVersion { get; set; }
public virtual DbSet<Atmtechnican> Atmtechnicans { get; set; }
public virtual DbSet<Car> Cars { get; set; }
public virtual DbSet<DailyCar> DailyCars { get; set; }
public virtual DbSet<DailyKmCar> DailyKmCars { get; set; }
public virtual DbSet<Device> Devices { get; set; }
public virtual DbSet<Driver> Drivers { get; set; }
public virtual DbSet<Escort> Escorts { get; set; }
public virtual DbSet<History> Histories { get; set; }
public virtual DbSet<Online> Onlines { get; set; }
public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Rfid> Rfids { get; set; }
public virtual DbSet<Scope> Scopes { get; set; }
public virtual DbSet<ScopePermission> ScopePermissions { get; set; }
public virtual DbSet<TransactionPoint> TransactionPoints { get; set; }
public virtual DbSet<Treasurer> Treasurers { get; set; }
public virtual DbSet<Unit> Units { get; set; }
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<UserUnit> UserUnits { get; set; }
public virtual DbSet<Recipient> Recipients { get; set; }
public virtual DbSet<RecipientWarnCfg> RecipientWarnCfgs { get; set; }
public virtual DbSet<WarningEvent> WarningEvents { get; set; }
public virtual DbSet<WarningType> WarningTypes { get; set; }
public virtual DbSet<WarningSms> WarningSms { get; set; }
public virtual DbSet<AppCfg> AppCfgs { get; set; }
public virtual DbSet<Camera> Cameras { get; set; }
public virtual DbSet<ReupLog> ReupLogs { get; set; }
public virtual DbSet<CellInfor> CellInfors { get; set; }
public virtual DbSet<Schedule> Schedules { get; set; }
public virtual DbSet<Owner> Owners { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Schedule>(entity =>
{
entity.ToTable("Schedule");
});
modelBuilder.Entity<Owner>(entity =>
{
entity.ToTable("Owner");
});
modelBuilder.Entity<Device>(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);
}
}

26
RuleEngine/Dockerfile Normal file
View File

@ -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"]

View File

@ -0,0 +1,9 @@
using RuleEngine.DTOs;
namespace RuleEngine.Interfaces
{
public interface IDeviceService
{
Task<bool> UpdatePVT(DeviceMessage deviceMessage);
}
}

View File

@ -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"

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

39
RuleEngine/Models/Car.cs Normal file
View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
#nullable disable
namespace RuleEngine.Models
{
public partial class Car
{
public Car()
{
DailyCars = new HashSet<DailyCar>();
DailyKmCars = new HashSet<DailyKmCar>();
Devices = new HashSet<Device>();
Histories = new HashSet<History>();
Onlines = new HashSet<Online>();
}
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<DailyCar> DailyCars { get; set; }
public virtual ICollection<DailyKmCar> DailyKmCars { get; set; }
public virtual ICollection<Device> Devices { get; set; }
public virtual ICollection<History> Histories { get; set; }
public virtual ICollection<Online> Onlines { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
#nullable disable
namespace RuleEngine.Models
{
public partial class Permission
{
public Permission()
{
ScopePermissions = new HashSet<ScopePermission>();
}
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<ScopePermission> ScopePermissions { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

26
RuleEngine/Models/Rfid.cs Normal file
View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
#nullable disable
namespace RuleEngine.Models
{
public partial class Rfid
{
public Rfid()
{
Treasurers = new HashSet<Treasurer>();
}
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<Treasurer> Treasurers { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
#nullable disable
namespace RuleEngine.Models
{
public partial class Scope
{
public Scope()
{
Users = new HashSet<User>();
}
public int Id { get; set; }
public string Name { get; set; }
public string AllowedRoute { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

24
RuleEngine/Models/Unit.cs Normal file
View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
#nullable disable
namespace RuleEngine.Models
{
public partial class Unit
{
public Unit()
{
UserUnits = new HashSet<UserUnit>();
}
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<UserUnit> UserUnits { get; set; }
}
}

19
RuleEngine/Models/User.cs Normal file
View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

39
RuleEngine/Program.cs Normal file
View File

@ -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<DatabaseContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("RuleEngineConnectStr"));
}
);
// Thêm Rule Engine vào Background Service
builder.Services.AddHostedService<RuleEngineWorker>();
// Thêm các service hỗ trợ Rule Engine
builder.Services.AddScoped<IDeviceService, DeviceService>();
var host = builder.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,12 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"RuleEngine": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-RuleEngine-babf0c43-57b7-40b0-bbae-1a6b4a75f009</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
</ItemGroup>
</Project>

25
RuleEngine/RuleEngine.sln Normal file
View File

@ -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

View File

@ -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<RuleEngineWorker> _logger;
private readonly IConfiguration _config;
private readonly IServiceProvider _serviceProvider;
private readonly IDbContextFactory<DatabaseContext> _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<RuleEngineWorker> logger,
IServiceProvider serviceProvider,
IConfiguration configuration,
IDbContextFactory<DatabaseContext> 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<string>() ?? "localhost";
_mqttPort = _config.GetSection("MqttSettings:Port").Get<int?>() ?? 1883;
_jwtSecret = _config.GetSection("MqttSettings:JwtSecret").Get<string>() ?? "emqx@sivan";
_mqttUsername = _config.GetSection("MqttSettings:Username").Get<string>() ?? string.Empty;
_mqttPassword = _config.GetSection("MqttSettings:Password").Get<string>() ?? 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<DeviceMessage>(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<IDeviceService>();
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();
}
}
}

View File

@ -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<string, LimitedSizeQueue<DateTime>> dictCarHistory = [];
public DeviceService(DatabaseContext dbContext, IConfiguration config)
{
_dbContext = dbContext;
_config = config;
}
public async Task<bool> UpdatePVT(DeviceMessage deviceMessage)
{
DeviceLogRequest? deviceLogRequest = null;
try
{
var enableLogFile = _config.GetValue<bool>("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<DateTime>? queue))
{
queue = new LimitedSizeQueue<DateTime>(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());
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -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"
}

View File

@ -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; }
}
}

View File

@ -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<string, string> deviceTokens = new();
private static readonly ConcurrentDictionary<string, SemaphoreSlim> deviceSemaphores = new();
static async Task Main(string[] args)
{
List<Task> 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<DeviceRegisterResponse>(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)
{
}
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>