소스 검색

change to Quartz jobs

Robert 2 년 전
부모
커밋
17f3d8e390

+ 18 - 0
Dockerfile2

@@ -0,0 +1,18 @@
+#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS final
+EXPOSE 80
+EXPOSE 443
+EXPOSE 54088
+RUN sed -i 's/TLSv1.2/TLSv1/g' /etc/ssl/openssl.cnf
+RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
+RUN dotnet tool install --tool-path /bin dotnet-trace
+RUN dotnet tool install --tool-path /bin dotnet-counters
+RUN apt update
+RUN apt install -y linux-perf
+RUN echo 0 > /proc/sys/kernel/kptr_restrict
+WORKDIR /src
+COPY . .
+RUN export DOTNET_PerfMapEnabled=1
+RUN dotnet build ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
+CMD dotnet run --project ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj

+ 6 - 22
EVCB_OCPP.WSServer/Dockerfile

@@ -1,29 +1,13 @@
 #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
 
-FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
-RUN sed -i 's/TLSv1.2/TLSv1/g' /etc/ssl/openssl.cnf
-RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS final
 EXPOSE 80
 EXPOSE 443
 EXPOSE 54088
-WORKDIR /app
-
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+RUN sed -i 's/TLSv1.2/TLSv1/g' /etc/ssl/openssl.cnf
+RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
 WORKDIR /src
-COPY ["EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj", "EVCB_OCPP.WSServer/"]
-COPY ["SuperWebSocket/SuperWebSocket.csproj", "SuperWebSocket/"]
-COPY ["SocketBase/SuperSocket.SocketBase.csproj", "SocketBase/"]
-COPY ["SocketCommon/SuperSocket.Common.csproj", "SocketCommon/"]
-COPY ["SocketEngine/SuperSocket.SocketEngine.csproj", "SocketEngine/"]
-RUN dotnet restore "EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj"
 COPY . .
-WORKDIR "/src/EVCB_OCPP.WSServer"
-RUN dotnet build "EVCB_OCPP.WSServer.csproj" -c Release -o /app/build
-
-FROM build AS publish
-RUN dotnet publish "EVCB_OCPP.WSServer.csproj" -c Release -o /app/publish /p:UseAppHost=false
-
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-ENTRYPOINT ["dotnet", "EVCB_OCPP.WSServer.dll"]
+RUN export DOTNET_PerfMapEnabled=1
+RUN dotnet build ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
+CMD ["dotnet", " run --project ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj"]

+ 25 - 0
EVCB_OCPP.WSServer/Dto/StationInfoDto.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Dto
+{
+    public class StationInfoDto
+    {
+
+        public int Id { set; get; }
+
+        /// <summary>
+        /// LoadingBalance Mode
+        /// 0:None 1: Average
+        /// 2:FCFS 3:Manual
+        /// </summary>
+        public int LBMode { set; get; }
+
+        public int Availability { set; get; }
+
+
+    }
+}

+ 4 - 1
EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj

@@ -20,6 +20,7 @@
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <DockerfileRunArguments>-p 54088:54088 -p 80:80</DockerfileRunArguments>
     <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
+    <UserSecretsId>88f57ec2-60b9-4291-bba3-3c0d312fe6dc</UserSecretsId>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="EVCB_OCPP.Domain">
@@ -59,12 +60,14 @@
     <PackageReference Include="Dapper" Version="2.0.123" />
     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
     <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.2" />
-    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
     <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
     <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
     <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
     <PackageReference Include="NLog" Version="5.1.1" />
     <PackageReference Include="NLog.Web.AspNetCore" Version="5.2.1" />
+    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.6.2" />
     <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
     <PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
     <PackageReference Include="System.ServiceModel.Federation" Version="4.10.0" />

+ 2 - 0
EVCB_OCPP.WSServer/GlobalConfig.cs

@@ -16,6 +16,8 @@ namespace EVCB_OCPP.WSServer
             "OCPP20_WSSUrl"
         };
 
+        public static List<string> DenyModelNames = new List<string>();
+
         public static  string UTC_DATETIMEFORMAT = "yyyy-MM-dd'T'HH':'mm':'ss'Z'";
 
         public static string TCC_API_URL = string.Empty;

+ 146 - 0
EVCB_OCPP.WSServer/HostedProtalServer.cs

@@ -0,0 +1,146 @@
+using EVCB_OCPP.WSServer.Jobs;
+using Microsoft.Extensions.DependencyInjection;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer
+{
+    public static class HostedProtalServer
+    {
+        public static void AddProtalServer(this IServiceCollection services)
+        {
+            services.AddSingleton<ProtalServer>();
+            services.AddHostedService<ProtalServer>(p => p.GetRequiredService<ProtalServer>());
+            services.AddProtalServerJob();
+        }
+
+        public static void AddProtalServerJob(this IServiceCollection services)
+        {
+            services.AddQuartz(q => {
+                q.UseMicrosoftDependencyInjectionJobFactory();
+
+                var ServerUpdateJobKey = new JobKey("job1", "group1");
+                q.AddJob<ServerUpdateJob>(opts => { opts.WithIdentity(ServerUpdateJobKey); });
+
+                q.AddTrigger(opts =>
+                {
+                    opts
+                    .ForJob(ServerUpdateJobKey)
+                    .WithIdentity("trigger1", "group1")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(3)
+                        .RepeatForever());
+                });
+
+                var ServerSetFeeJobKey = new JobKey("job2", "group1");
+                q.AddJob<ServerSetFeeJob>(opts => { opts.WithIdentity(ServerSetFeeJobKey); });
+
+                q.AddTrigger(opts =>
+                {
+                    opts
+                    .ForJob(ServerSetFeeJobKey)
+                    .WithIdentity("trigger2", "group1")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(1)
+                        .RepeatForever());
+                });
+
+                var ServerMessageJobKey = new JobKey("job3", "group1");
+                q.AddJob<ServerMessageJob>(opts => { opts.WithIdentity(ServerMessageJobKey); });
+
+                q.AddTrigger(opts =>
+                {
+                    opts
+                    .ForJob(ServerMessageJobKey)
+                    .WithIdentity("trigger3", "group1")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithInterval(TimeSpan.FromMilliseconds(500))
+                        .RepeatForever());
+                });
+
+                var HeartBeatCheckJobbKey = new JobKey("job4", "group1");
+                q.AddJob<HeartBeatCheckJob>(opts => { opts.WithIdentity(HeartBeatCheckJobbKey); });
+
+                q.AddTrigger(opts =>
+                {
+                    opts
+                    .ForJob(HeartBeatCheckJobbKey)
+                    .WithIdentity("trigger4", "group1")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInSeconds(30)
+                        .RepeatForever());
+                });
+
+                var ServerWeatherNotificationJobKey = new JobKey("job5", "group1");
+                q.AddJob<ServerWeatherNotificationJob>(opts => { opts.WithIdentity(ServerWeatherNotificationJobKey); });
+
+                q.AddTrigger(opts =>
+                {
+                    opts
+                    .ForJob(ServerWeatherNotificationJobKey)
+                    .WithIdentity("trigger5", "group1")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(1)
+                        .RepeatForever());
+                });
+
+                var HealthCheckTriggerJobKey = new JobKey("job6", "group1");
+                q.AddJob<HealthCheckTriggerJob>(opts => { opts.WithIdentity(HealthCheckTriggerJobKey); });
+
+                q.AddTrigger(opts =>
+                {
+                    opts
+                    .ForJob(HealthCheckTriggerJobKey)
+                    .WithIdentity("trigger6", "group1")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(1)
+                        .RepeatForever());
+                });
+
+
+                var SmartChargingJobKey = new JobKey("job7", "group1");
+                q.AddJob<SmartChargingJob>(opts => { opts.WithIdentity(SmartChargingJobKey); });
+
+                q.AddTrigger(opts =>
+                {
+                    opts
+                    .ForJob(SmartChargingJobKey)
+                    .WithIdentity("trigger7", "group1")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(1)
+                        .RepeatForever());
+                });
+
+                //var DenyModelCheckJobKey = new JobKey("job8", "group1");
+                //q.AddJob<DenyModelCheckJob>(opts => { opts.WithIdentity(DenyModelCheckJobKey); });
+
+                //q.AddTrigger(opts =>
+                //{
+                //    opts
+                //    .ForJob(DenyModelCheckJobKey)
+                //    .WithIdentity("trigger8", "group1")
+                //    .StartNow()
+                //    .WithSimpleSchedule(x => x
+                //        .WithIntervalInMinutes(5)
+                //        .RepeatForever());
+                //});
+            });
+
+            services.AddQuartzHostedService(opt =>
+            {
+                opt.WaitForJobsToComplete = true;
+            });
+        }
+    }
+}

+ 71 - 0
EVCB_OCPP.WSServer/Jobs/DenyModelCheckJob.cs

@@ -0,0 +1,71 @@
+using Dapper;
+using DnsClient.Internal;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using OCPPServer.Protocol;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    public class DenyModelCheckJob : IJob
+    {
+        public DenyModelCheckJob(
+            ProtalServer protalServer,
+            IConfiguration configuration,
+            ILogger<DenyModelCheckJob> logger)
+        {
+            this.webConnectionString = configuration.GetConnectionString("WebDBContext");
+            this.protalServer = protalServer;
+            this.logger = logger;
+        }
+
+        private readonly string webConnectionString;
+        private readonly ProtalServer protalServer;
+        private readonly ILogger<DenyModelCheckJob> logger;
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            logger.LogDebug("{0} Started", nameof(DenyModelCheckJob));
+            try
+            {
+                using (SqlConnection conn = new SqlConnection(webConnectionString))
+                {
+                    string strSql = "SELECT [Value] FROM[StandardOCPP_Web].[dbo].[KernelConfig]" +
+                     "where SystemKey = 'DenyModelNames'; ";
+                    var result = await conn.QueryAsync<string>(strSql);
+
+                    GlobalConfig.DenyModelNames = result.FirstOrDefault().Split(',').ToList();
+                    logger.LogDebug("Current DenyList:[{0}]", string.Join(",", GlobalConfig.DenyModelNames));
+                }
+
+                if (string.IsNullOrEmpty(GlobalConfig.DenyModelNames[0]))
+                {
+                    return;
+                }
+
+                Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
+                foreach (var denyName in GlobalConfig.DenyModelNames)
+                {
+                    var removeClients = _copyClientDic.Where(x => x.Key.StartsWith(denyName)).Select(x => x.Value).ToList();
+                    foreach (var session in removeClients)
+                    {
+                        Console.WriteLine(string.Format("Server forced to shut down ChargeBox ({0}: Reason: DenyModelName-{1}", session.ChargeBoxId, denyName));
+                        protalServer.RemoveClient(session);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.LogError("DenyModelCheckTrigger  Ex:{0}", ex.ToString());
+            }
+        }
+    }
+}

+ 42 - 0
EVCB_OCPP.WSServer/Jobs/HealthCheckTriggerJob.cs

@@ -0,0 +1,42 @@
+using EVCB_OCPP.Domain;
+using Microsoft.Extensions.Logging;
+using OCPPServer.Protocol;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    public class HealthCheckTriggerJob : IJob
+    {
+        public HealthCheckTriggerJob(
+            ProtalServer protalServer,
+            ILogger<HealthCheckTriggerJob> logger)
+        {
+            this.protalServer = protalServer;
+            this.logger = logger;
+        }
+
+        private readonly ProtalServer protalServer;
+        private readonly ILogger<HealthCheckTriggerJob> logger;
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            logger.LogDebug("{0} Started", nameof(HealthCheckTriggerJob));
+
+            Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
+
+            var removeClients = _copyClientDic.Where(x => x.Value.LastActiveTime < DateTime.UtcNow.AddSeconds(-300)).Select(x => x.Value).ToList();
+
+            foreach (var session in removeClients)
+            {
+                Console.WriteLine(string.Format("Server forced to shut down ChargeBox ({0}: LastActiveTime{1})", session.ChargeBoxId, session.LastActiveTime));
+                protalServer.RemoveClient(session);
+            }
+        }
+    }
+}

+ 95 - 0
EVCB_OCPP.WSServer/Jobs/HeartBeatCheckJob.cs

@@ -0,0 +1,95 @@
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using OCPPServer.Protocol;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    public class HeartBeatCheckJob : IJob
+    {
+        public HeartBeatCheckJob(
+            ProtalServer protalServer,
+            IDbContextFactory<MainDBContext> maindbContextFactory,
+            ILogger<HeartBeatCheckJob> logger)
+        {
+            this.protalServer = protalServer;
+            this.maindbContextFactory = maindbContextFactory;
+            this.logger = logger;
+        }
+
+        private readonly ProtalServer protalServer;
+        private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+        private readonly ILogger<HeartBeatCheckJob> logger;
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            logger.LogDebug("{0} Started", nameof(HeartBeatCheckJob));
+            try
+            {
+                Stopwatch watch = new Stopwatch();
+                Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
+
+                var cdt = DateTime.UtcNow;
+                var clients = _copyClientDic.Where(x => x.Value.LastActiveTime > cdt.AddSeconds(-120)).Select(x => x.Value).ToList();
+
+                watch.Start();
+                await Parallel.ForEachAsync(clients, async (session, token ) => {
+                    using (var db = await maindbContextFactory.CreateDbContextAsync())
+                    {
+                        var machine = new Machine() { Id = session.MachineId };
+                        if (machine != null)
+                        {
+                            //db.Configuration.AutoDetectChangesEnabled = false;
+                            //db.Configuration.ValidateOnSaveEnabled = false;
+                            db.Machine.Attach(machine);
+                            machine.HeartbeatUpdatedOn = DateTime.UtcNow;
+                            machine.ConnectionType = session.UriScheme.Equals("wss") ? 2 : 1;
+                            db.Entry(machine).Property(x => x.HeartbeatUpdatedOn).IsModified = true;
+                            db.Entry(machine).Property(x => x.ConnectionType).IsModified = true;
+                            await db.SaveChangesAsync(token);
+                        }
+
+                    }
+                });
+                //foreach (var session in clients)
+                //{
+                //    using (var db = await maindbContextFactory.CreateDbContextAsync())
+                //    {
+                //        var machine = new Machine() { Id = session.MachineId };
+                //        if (machine != null)
+                //        {
+                //            //db.Configuration.AutoDetectChangesEnabled = false;
+                //            //db.Configuration.ValidateOnSaveEnabled = false;
+                //            db.Machine.Attach(machine);
+                //            machine.HeartbeatUpdatedOn = DateTime.UtcNow;
+                //            machine.ConnectionType = session.UriScheme.Equals("wss") ? 2 : 1;
+                //            db.Entry(machine).Property(x => x.HeartbeatUpdatedOn).IsModified = true;
+                //            db.Entry(machine).Property(x => x.ConnectionType).IsModified = true;
+                //            await db.SaveChangesAsync();
+                //        }
+
+                //    }
+                //}
+                watch.Stop();
+                if (watch.ElapsedMilliseconds / 1000 > 5)
+                {
+                    logger.LogCritical("Update HeartBeatCheckTrigger cost " + watch.ElapsedMilliseconds / 1000 + " seconds.");
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine("***********************************************************");
+                logger.LogError(string.Format("HeartBeatCheckTrigger  Ex:{0}", ex.ToString()));
+            }
+        }
+    }
+}

+ 175 - 0
EVCB_OCPP.WSServer/Jobs/ServerMessageJob.cs

@@ -0,0 +1,175 @@
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.Database;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.WSServer.Message;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using OCPPServer.Protocol;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    public class ServerMessageJob : IJob
+    {
+        public ServerMessageJob(
+            ProtalServer protalServer,
+            IConfiguration configuration,
+            IDbContextFactory<MainDBContext> maindbContextFactory,
+            ILogger<ServerMessageJob> logger)
+        {
+            this.protalServer = protalServer;
+            this.maindbContextFactory = maindbContextFactory;
+            this.logger = logger;
+        }
+
+        private readonly ProtalServer protalServer;
+        private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+        private readonly ILogger<ServerMessageJob> logger;
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            try
+            {
+                await ExecuteTrigger();
+            }
+            catch (Exception ex)
+            {
+                logger.LogError("ServerMessageTrigger  Ex:{0}", ex.ToString());
+            }
+        }
+
+        private async Task ExecuteTrigger()
+        {
+            protalServer.RemoveConfirmMessage();
+
+            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+            using (var db = await maindbContextFactory.CreateDbContextAsync())
+            {
+                var dateTimeNoew = DateTime.UtcNow;
+                DateTime startDt = dateTimeNoew.AddSeconds(-30);
+                DateTime dt = new DateTime(1991, 1, 1);
+                DateTime currentTime = dateTimeNoew;
+                var commandList = db.ServerMessage.Where(c => c.ReceivedOn == dt && c.UpdatedOn == dt && c.CreatedOn >= startDt && c.CreatedOn <= currentTime).AsNoTracking().ToList();
+                var clientDic = protalServer.ClientDic;
+
+                //處理主機傳送的有指令
+                var cmdMachineList = commandList.Select(c => c.ChargeBoxId).Distinct().ToList();
+                if (commandList.Count > 0)
+                {
+                    // Console.WriteLine(string.Format("Now:{0} commandList Count:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), commandList.Count));
+                }
+                var resendList = protalServer.ResendMessage;
+                foreach (var resendItem in resendList)
+                {
+                    ClientData session;
+                    if (clientDic.TryGetValue(resendItem.ChargePointSerialNumber, out session))
+                    {
+                        if (dateTimeNoew.Subtract(resendItem.SentOn).TotalSeconds > 1)
+                        {
+                            resendItem.SentTimes--;
+                            resendItem.SentOn = dateTimeNoew;
+                            protalServer.SendMsg(session, resendItem.SentMessage, string.Format("{0} {1}", resendItem.SentAction, "Request"), "");
+                        }
+
+                    }
+
+                }
+                foreach (var charger_SN in cmdMachineList)
+                {
+                    ClientData session;
+                    string uuid = string.Empty;
+                    if (clientDic.TryGetValue(charger_SN, out session))
+                    {
+                        //logger.LogDebug(string.Format("charger_SN:{0} startDt:{1} CreatedOn:{2}", charger_SN, startDt.ToString("yyyy/MM/dd HH:mm:ss"), DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss")));
+
+                        if (session.IsCheckIn && !session.ISOCPP20)
+                        {
+                            string rawRequest = string.Empty;
+
+                            var cmdList = commandList.Where(c => c.ChargeBoxId == charger_SN).ToList();
+
+                            var profiles = protalServer.Profiles;
+
+                            foreach (var item in cmdList)
+                            {
+                                IRequest request = null;
+                                Actions action = Actions.None;
+                                Enum.TryParse(item.OutAction, out action);
+                                Type _RequestType = null;
+
+                                for (int i = 0; i < profiles.Count; i++)
+                                {
+                                    var feature = profiles[i].GetFeaturebyAction(item.OutAction);
+
+                                    if (feature != null)
+                                    {
+                                        _RequestType = feature.GetRequestType();
+                                        break;
+                                    }
+                                }
+
+                                if (_RequestType != null && item.CreatedBy != "Destroyer")
+                                {
+                                    request = JsonConvert.DeserializeObject(item.OutRequest, _RequestType) as IRequest;
+                                    uuid = session.queue.store(request);
+                                    rawRequest = BasicMessageHandler.GenerateRequest(uuid, item.OutAction, request);
+                                    protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
+                                }
+
+
+                                if (item.CreatedBy == "Destroyer")
+                                {
+
+                                    if (_RequestType != null)
+                                    {
+                                        request = Activator.CreateInstance(_RequestType) as IRequest;
+                                        uuid = session.queue.store(request);
+                                        rawRequest = BasicMessageHandler.GenerateDestroyRequest(uuid, item.OutAction, item.OutRequest);
+                                        protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
+
+                                    }
+                                    else
+                                    {
+
+                                        rawRequest = BasicMessageHandler.GenerateDestroyRequest(Guid.NewGuid().ToString(), item.OutAction, item.OutRequest);
+                                        protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
+
+                                    }
+                                }
+
+                                protalServer.AddConfirmMessage(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
+
+                                dateTimeNoew = DateTime.UtcNow;
+                                #region 更新資料表單一欄位
+                                var _UpdatedItem = new ServerMessage() { Id = item.Id, UpdatedOn = dateTimeNoew };
+                                //db.Configuration.AutoDetectChangesEnabled = false;//自動呼叫DetectChanges()比對所有的entry集合的每一個屬性Properties的新舊值
+                                //db.Configuration.ValidateOnSaveEnabled = false;// 因為Entity有些欄位必填,若不避開會有Validate錯誤
+                                // var _UpdatedItem = db.ServerMessage.Where(x => x.Id == item.Id).FirstOrDefault();
+                                db.ServerMessage.Attach(_UpdatedItem);
+                                _UpdatedItem.UpdatedOn = dateTimeNoew;
+                                db.Entry(_UpdatedItem).Property(x => x.UpdatedOn).IsModified = true;// 可以直接使用這方式強制某欄位要更新,只是查詢集合耗效能而己
+
+                                db.SaveChanges();
+
+                                #endregion
+
+                                await Task.Delay(100);
+
+                            }
+
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 200 - 0
EVCB_OCPP.WSServer/Jobs/ServerSetFeeJob.cs

@@ -0,0 +1,200 @@
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.Database;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages.Core;
+using EVCB_OCPP.WSServer.Dto;
+using EVCB_OCPP.WSServer.Message;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using OCPPServer.Protocol;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    public class ServerSetFeeJob : IJob
+    {
+        private readonly ProtalServer protalServer;
+        private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+        private readonly ILogger<ServerSetFeeJob> logger;
+        private readonly string webConnectionString;
+
+        public ServerSetFeeJob(
+            ProtalServer protalServer,
+            IConfiguration configuration,
+            IDbContextFactory<MainDBContext> maindbContextFactory,
+            ILogger<ServerSetFeeJob> logger) 
+        {
+            this.protalServer = protalServer;
+            this.maindbContextFactory = maindbContextFactory;
+            this.logger = logger;
+            this.webConnectionString = configuration.GetConnectionString("WebDBContext");
+        }
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            logger.LogDebug("{0} Started", nameof(ServerSetFeeJob));
+            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+            Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
+            foreach (var item in _copyClientDic)
+            {
+                try
+                {
+                    ClientData session = item.Value;
+                    if (!session.IsCheckIn)
+                    {
+                        continue;
+                    }
+
+                    string displayPriceText = await SetDefaultFee(session);
+                    if (string.IsNullOrEmpty(displayPriceText) || displayPriceText == session.DisplayPrice)
+                    {
+                        continue;
+                    }
+
+                    protalServer.UpdateClientDisplayPrice(item.Key, displayPriceText);
+
+                    var _CheckFeeDt = DateTime.UtcNow;
+                    using (var db = await maindbContextFactory.CreateDbContextAsync())
+                    {
+                        db.ServerMessage.Add(new ServerMessage()
+                        {
+                            ChargeBoxId = session.ChargeBoxId,
+                            CreatedBy = "Server",
+                            CreatedOn = _CheckFeeDt,
+                            OutAction = Actions.ChangeConfiguration.ToString(),
+                            OutRequest = JsonConvert.SerializeObject(
+                                    new ChangeConfigurationRequest()
+                                    {
+                                        key = "DefaultPrice",
+                                        value = displayPriceText
+                                    },
+                                    new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
+                            SerialNo = Guid.NewGuid().ToString(),
+                            InMessage = string.Empty
+
+                        });
+
+                        if (session.CustomerId == new Guid("10C7F5BD-C89A-4E2A-8611-B617E0B41A73"))
+                        {
+                            db.ServerMessage.Add(new ServerMessage()
+                            {
+                                ChargeBoxId = session.ChargeBoxId,
+                                CreatedBy = "Server",
+                                CreatedOn = _CheckFeeDt,
+                                OutAction = Actions.ChangeConfiguration.ToString(),
+                                OutRequest = JsonConvert.SerializeObject(
+                                   new ChangeConfigurationRequest()
+                                   {
+                                       key = "ConnectionTimeOut",
+                                       value = "120"
+                                   },
+                                   new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
+                                SerialNo = Guid.NewGuid().ToString(),
+                                InMessage = string.Empty
+
+                            });
+                            db.ServerMessage.Add(new ServerMessage()
+                            {
+                                ChargeBoxId = session.ChargeBoxId,
+                                CreatedBy = "Server",
+                                CreatedOn = _CheckFeeDt,
+                                OutAction = Actions.ChangeConfiguration.ToString(),
+                                OutRequest = JsonConvert.SerializeObject(
+                                   new ChangeConfigurationRequest()
+                                   {
+                                       key = "MeterValueSampleInterval",
+                                       value = "3"
+                                   },
+                                   new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
+                                SerialNo = Guid.NewGuid().ToString(),
+                                InMessage = string.Empty
+
+                            });
+                        }
+
+                        await db.SaveChangesAsync();
+
+                    }
+
+                }
+                catch (Exception ex)
+                {
+                    logger.LogError("ServerSetFeeTrigger ChargeBoxId:{0}  Ex:{1}", item.Key, ex.ToString());
+                }
+            }
+        }
+
+        async private Task<string> SetDefaultFee(ClientData client)
+        {
+            string displayPriceText = string.Empty;
+            string charingPriceText = string.Empty;
+
+            if (string.IsNullOrEmpty(client.ChargeBoxId)) return displayPriceText;
+
+            using (SqlConnection conn = new SqlConnection(webConnectionString))
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@MachineId", client.MachineId, DbType.String, ParameterDirection.Input);
+                string displayPricestrSql = "";
+                string strSql = "";
+
+                if (client.IsAC)
+                {
+                    displayPricestrSql = "   SELECT  [AC_BillingMethod] as BillingMethod,[AC_FeeName] as FeeName,[AC_Fee] as ChargingFeebyHour" +
+                "  ,[AC_ParkingFee] as ParkingFee, [Currency]  FROM[StationMachine]  left join[dbo].[Station]" +
+                "  on[StationMachine].StationId = Station.[Id]  where StationMachine.MachineId=@MachineId and Station.IsBilling=1; ";
+
+                    strSql = " SELECT CAST( [StartTime] as varchar(5)) StartTime,CAST( [EndTime] as varchar(5)) EndTime,[Fee]  FROM[StationMachine]  left join [dbo].[StationFee]" +
+                   " on[StationMachine].StationId = StationFee.StationId  where StationMachine.MachineId =@MachineId and StationFee.IsAC=1; ";
+                }
+                else
+                {
+                    displayPricestrSql = "   SELECT  [DC_BillingMethod] as BillingMethod,[DC_FeeName] as FeeName,[DC_Fee] as ChargingFeebyHour" +
+               "  ,[DC_ParkingFee] as ParkingFee, [Currency]  FROM[StationMachine]  left join[dbo].[Station]" +
+               "  on[StationMachine].StationId = Station.[Id]  where StationMachine.MachineId=@MachineId and Station.IsBilling=1; ";
+
+                    strSql = " SELECT CAST( [StartTime] as varchar(5)) StartTime,CAST( [EndTime] as varchar(5)) EndTime,[Fee]  FROM[StationMachine]  left join [dbo].[StationFee]" +
+                   " on[StationMachine].StationId = StationFee.StationId  where StationMachine.MachineId =@MachineId and StationFee.IsAC=0; ";
+
+                }
+                var result = await conn.QueryAsync<StationFee>(displayPricestrSql, parameters);
+                if (result.Count() == 0)
+                {
+                    return string.Empty;
+                }
+                var stationPrice = result.First();
+
+                if (stationPrice.BillingMethod == 1)
+                {
+                    var chargingPriceResult = await conn.QueryAsync<ChargingPrice>(strSql, parameters);
+                    client.ChargingPrices = chargingPriceResult.ToList();
+                    if (string.IsNullOrEmpty(client.ChargingPrices[0].StartTime))
+                    {
+                        client.ChargingPrices = new List<ChargingPrice>();
+                    }
+                }
+
+                displayPriceText = stationPrice.FeeName;
+                client.BillingMethod = stationPrice.BillingMethod;
+                client.Currency = stationPrice.Currency;
+                client.ChargingFeebyHour = stationPrice.ChargingFeebyHour;
+                client.ParkingFee = stationPrice.ParkingFee;
+                client.IsBilling = true;
+            }
+
+            return displayPriceText;
+        }
+    }
+}

+ 138 - 0
EVCB_OCPP.WSServer/Jobs/ServerUpdateJob.cs

@@ -0,0 +1,138 @@
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Packet.Messages.RemoteTrigger;
+using EVCB_OCPP.WSServer.Message;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using OCPPServer.Protocol;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    public class ServerUpdateJob : IJob
+    {
+        private readonly ProtalServer protalServer;
+        private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+        private readonly ILogger<ServerUpdateJob> logger;
+
+        public ServerUpdateJob(
+            ProtalServer protalServer, 
+            IDbContextFactory<MainDBContext> maindbContextFactory,
+            ILogger<ServerUpdateJob> logger)
+        {
+            this.protalServer = protalServer;
+            this.maindbContextFactory = maindbContextFactory;
+            this.logger = logger;
+        }
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            logger.LogDebug("{0} Started", nameof(ServerUpdateJob));
+            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+            Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
+            var checkUpdateDt = DateTime.UtcNow;
+            using (var db = await maindbContextFactory.CreateDbContextAsync())
+            {
+                //var needUpdateChargers = db.Machine.Where(x => x.FW_AssignedMachineVersionId.HasValue == true &&
+                //    x.FW_AssignedMachineVersionId != x.FW_VersionReport && x.Online == true)
+                //    .Select(x => new { x.Id, x.ChargeBoxId, x.FW_AssignedMachineVersionId }).ToList();
+
+                var needUpdateChargers = db.Machine.Where(x => x.FW_AssignedVersion.HasValue == true &&
+                  x.FW_AssignedVersion != x.FW_VersionReport && x.Online == true)
+                  .Select(x => x.ChargeBoxId).AsNoTracking().ToList();
+
+                foreach (var chargeBoxId in needUpdateChargers)
+                {
+                    try
+                    {
+                        ClientData session;
+                        if (_copyClientDic.TryGetValue(chargeBoxId, out session))
+                        {
+
+                            string requestId = Guid.NewGuid().ToString();
+                            // using (var db = maindbContextFactory.CreateDbContext())
+
+                            if (session.IsCheckIn && !session.ISOCPP20)
+                            {
+
+                                var _request = new TriggerMessageRequest()
+                                {
+                                    requestedMessage = Packet.Messages.SubTypes.MessageTrigger.FirmwareStatusNotification
+                                };
+
+                                var uuid = session.queue.store(_request);
+                                string rawRequest = BasicMessageHandler.GenerateRequest(uuid, _request.Action, _request);
+                                protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", _request.Action, "Request"), "");
+
+                                #region OCTT   ,測試韌體更新方式
+                                //--------------------> OCTT   ,測試韌體更新方式
+                                //{
+                                //    var machine = db.Machine.Where(x => x.FW_AssignedMachineVersionId.HasValue == true &&
+                                //        x.FW_AssignedMachineVersionId != x.FW_VersionReport && x.ChargeBoxId == session.ChargeBoxId)
+                                //        .Select(x => new { x.Id, x.FW_AssignedMachineVersionId }).FirstOrDefault();
+
+                                //    if (machine != null)
+                                //    {
+                                //        var mv = db.MachineVersion.Include(c => c.PublishVersion)
+                                //         .Include(c => c.PublishVersion.PublishVersionFiles)
+                                //         .Include(c => c.PublishVersion.PublishVersionFiles.Select(z => z.UploadFile))
+                                //         .Where(c => c.Id == machine.FW_AssignedMachineVersionId.Value).First();
+
+                                //        string downloadUrl = mv.PublishVersion.PublishVersionFiles.FirstOrDefault().UploadFile.FileUrl;
+
+                                //        var _updateFWrequest = new UpdateFirmwareRequest()
+                                //        {
+                                //            location = new Uri(downloadUrl),
+                                //            retries = 3,
+                                //            retrieveDate = DateTime.UtcNow,
+                                //            retryInterval = 10
+                                //        };
+                                //        db.MachineOperateRecord.Add(new MachineOperateRecord()
+                                //        {
+                                //            CreatedOn = DateTime.UtcNow,
+                                //            ChargeBoxId = session.ChargeBoxId,
+                                //            SerialNo = requestId,
+                                //            RequestContent = JsonConvert.SerializeObject(_updateFWrequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
+                                //            EVSE_Status = 0,
+                                //            EVSE_Value = "Fw Version:" + machine.FW_AssignedMachineVersionId,
+                                //            Status = 0,
+                                //            RequestType = 0,
+
+                                //        });
+
+                                //        db.ServerMessage.Add(new ServerMessage()
+                                //        {
+                                //            ChargeBoxId = session.ChargeBoxId,
+                                //            CreatedBy = "Server",
+                                //            CreatedOn = DateTime.UtcNow,
+                                //            OutAction = _updateFWrequest.Action.ToString(),
+                                //            OutRequest = JsonConvert.SerializeObject(_updateFWrequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
+                                //            SerialNo = requestId,
+                                //            InMessage = string.Empty
+
+                                //        });
+
+                                //        db.SaveChanges();
+
+                                //    }
+
+                                //}
+                                #endregion
+                            }
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        logger.LogError("serverUpdateTrigger ChargeBoxId:{0}  Ex:{1}", chargeBoxId, ex.ToString());
+                    }
+                }
+            }
+        }
+    }
+}

+ 218 - 0
EVCB_OCPP.WSServer/Jobs/ServerWeatherNotificationJob.cs

@@ -0,0 +1,218 @@
+using EVCB_OCPP.Domain.Models.Database;
+using EVCB_OCPP.WSServer.Dto;
+using EVCB_OCPP.WSServer.Message;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using OCPPServer.Protocol;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using EVCB_OCPP.WSServer.Service;
+using Microsoft.Extensions.Logging;
+using EVCB_OCPP.Domain;
+using Microsoft.EntityFrameworkCore;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages.Core;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    public class ServerWeatherNotificationJob : IJob
+    {
+        public ServerWeatherNotificationJob(
+            ProtalServer protalServer,
+            IDbContextFactory<MainDBContext> maindbContextFactory,
+            ILogger<ServerWeatherNotificationJob> logger)
+        {
+            this.protalServer = protalServer;
+            this.maindbContextFactory = maindbContextFactory;
+            this.logger = logger;
+        }
+
+        private readonly ProtalServer protalServer;
+        private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+        private readonly ILogger<ServerWeatherNotificationJob> logger;
+        private OuterHttpClient httpClient = new OuterHttpClient();
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            logger.LogDebug("{0} Started", nameof(ServerWeatherNotificationJob));
+            // Console.WriteLine("in...............ServerWeatherNotificationTrigger");
+            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+            Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
+            var _CheckWeatherDt = DateTime.UtcNow;
+
+            var locations = _copyClientDic.Where(x => !string.IsNullOrEmpty(x.Value.StationLocation)).Distinct().Select(x => x.Value.StationLocation).ToList();
+
+            // Console.WriteLine("in...............ServerWeatherNotificationTrigger");
+            foreach (var location in locations)
+            {
+                try
+                {   //query weather
+                    var httpResult = await httpClient.GetWeather("https://api.weatherapi.com/v1/current.json?key=874346abc0874e69a9423510222201&q=" + location, null, null);
+
+                    string temp = "17";
+                    string weather_code = "1183";
+                    if (httpResult.Status == System.Net.HttpStatusCode.OK)
+                    {
+                        try
+                        {
+                            var jsonResult = JsonConvert.DeserializeObject(httpResult.Response) as JObject;
+                            temp = jsonResult["current"]["temp_c"].ToString();
+                            weather_code = jsonResult["current"]["condition"]["code"].ToString();
+
+
+                        }
+                        catch (Exception ex)
+                        {
+                            ;
+                        }
+
+                    }
+
+                    #region 台泥氣象Mapping
+                    switch (weather_code)
+                    {
+                        case "1000":
+                            weather_code = "1";
+                            break;
+                        case "1003":
+                        case "1006":
+                        case "1009":
+                            weather_code = "2";
+                            break;
+                        case "1063":
+                        case "1072":
+                        case "1150":
+                        case "1153":
+                        case "1168":
+                        case "1171":
+                        case "1180":
+                        case "1183":
+                        case "1186":
+                        case "1189":
+                        case "1192":
+                        case "1195":
+                        case "1198":
+                        case "1201":
+                        case "1237":
+                        case "1240":
+                        case "1243":
+                        case "1246":
+                        case "1261":
+                        case "1264":
+                            weather_code = "3";
+                            break;
+                        case "1087":
+                        case "1273":
+                        case "1276":
+                        case "1279":
+                        case "1282":
+                            weather_code = "4";
+                            break;
+                        case "1066":
+                        case "1069":
+                        case "1114":
+                        case "1117":
+                        case "1204":
+                        case "1207":
+                        case "1210":
+                        case "1213":
+                        case "1216":
+                        case "1219":
+                        case "1222":
+                        case "1225":
+                        case "1249":
+                        case "1252":
+                        case "1255":
+                        case "1258":
+                            weather_code = "5";
+                            break;
+                        case "1030":
+                        case "1135":
+                        case "1147":
+                            weather_code = "2";
+                            break;
+                        default:
+                            weather_code = "2";
+                            break;
+                    }
+                    #endregion
+
+
+                    if (protalServer.TCCStationDic.ContainsKey(location))
+                    {
+                        protalServer.TCCStationDic[location].Temperature = (int)double.Parse(temp);
+                        protalServer.TCCStationDic[location].WeatherID = int.Parse(weather_code);
+                    }
+                    else
+                    {
+                        protalServer.TCCStationDic.Add(location, new TCCWeatherDto() { WeatherID = int.Parse(weather_code), Temperature = (int)double.Parse(temp) });
+                    }
+
+                }
+                catch (Exception ex)
+                {
+                    logger.LogError("ServerWeatherNotificationTrigger ChargeBoxId:{0}  Ex:{1}", location, ex.ToString());
+                }
+            }
+
+            var clients = _copyClientDic.Where(x => x.Value.CustomerId == new Guid("009E603C-79CD-4620-A2B8-D9349C0E8AD8")).
+            Select(x => new { ChargeBoxId = x.Value.ChargeBoxId, StationLocation = x.Value.StationLocation }).ToList();
+
+            using (var db = await maindbContextFactory.CreateDbContextAsync())
+            {
+
+                foreach (var client in clients)
+                {
+                    try
+                    {
+                        if (string.IsNullOrEmpty(client.StationLocation))
+                        {
+                            Console.WriteLine(client.StationLocation + " is empty");
+                            continue;
+                        }
+
+
+                        if (protalServer.TCCStationDic.ContainsKey(client.StationLocation))
+                        {
+                            db.ServerMessage.Add(new ServerMessage()
+                            {
+                                ChargeBoxId = client.ChargeBoxId,
+                                CreatedBy = "Server",
+                                CreatedOn = DateTime.UtcNow,
+                                OutAction = Actions.DataTransfer.ToString(),
+                                OutRequest = JsonConvert.SerializeObject(
+                                                   new DataTransferRequest()
+                                                   {
+                                                       messageId = "ID_Weather_Info",
+                                                       vendorId = "Phihong Technology",
+                                                       data = JsonConvert.SerializeObject(
+                                                           new
+                                                           {
+                                                               weatherId = protalServer.TCCStationDic[client.StationLocation].WeatherID,
+                                                               Temperature = protalServer.TCCStationDic[client.StationLocation].Temperature
+                                                           })
+                                                   },
+                                                   new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
+                                SerialNo = Guid.NewGuid().ToString(),
+                                InMessage = string.Empty
+
+                            });
+
+                            await db.SaveChangesAsync();
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        logger.LogError("ServerWeatherNotificationTrigger ChargeBoxId:{0}  Ex:{1}", client.ChargeBoxId, ex.ToString());
+                    }
+                }
+            }
+        }
+    }
+}

+ 79 - 0
EVCB_OCPP.WSServer/Jobs/SmartChargingJob.cs

@@ -0,0 +1,79 @@
+using EVCB_OCPP.WSServer.Message;
+using EVCB_OCPP.WSServer.Service;
+using Microsoft.Data.SqlClient;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using EVCB_OCPP.WSServer.Dto;
+using Microsoft.Extensions.Configuration;
+using Dapper;
+using Microsoft.Extensions.Logging;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    public class SmartChargingJob : IJob
+    {
+        public SmartChargingJob(
+            ProtalServer protalServer,
+            IConfiguration configuration,
+            ILogger<SmartChargingJob> logger)
+        {
+            this.webConnectionString = configuration.GetConnectionString("WebDBContext");
+            this.protalServer = protalServer;
+            this.logger = logger;
+        }
+
+        private readonly string webConnectionString;
+        private readonly ProtalServer protalServer;
+        private readonly ILogger<SmartChargingJob> logger;
+        private static List<StationInfoDto> _StationInfo = new List<StationInfoDto>();
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            logger.LogDebug("{0} Started", nameof(SmartChargingJob));
+            List<StationInfoDto> stations = null;
+            using (SqlConnection conn = new SqlConnection(webConnectionString))
+            {
+                string strSql = "SELECT[Id],[LBMode],[LBCurrent] as Availability FROM[StandardOCPP_Web].[dbo].[Station]" +
+                 "where LBMode = 1; ";
+                var result = await conn.QueryAsync<StationInfoDto>(strSql);
+                stations = result.ToList();
+            }
+            foreach (var station in stations)
+            {
+                var compareStation = _StationInfo.Where(x => x.Id == station.Id).FirstOrDefault();
+
+                if (compareStation != null && (station.Id != compareStation.Id || station.Availability == compareStation.Availability))
+                {
+                    continue;
+                }
+                var _powerDic = await protalServer.LoadingBalanceService.GetSettingPower(station.Id);
+                if (_powerDic == null)
+                {
+                    continue;
+                }
+                foreach (var kv in _powerDic)
+                {
+                    try
+                    {
+
+                        if (kv.Value.HasValue)
+                        {
+                            protalServer.ProfileHandler.SetChargingProfile(kv.Key, kv.Value.Value, Packet.Messages.SubTypes.ChargingRateUnitType.W);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        logger.LogError("Set Profile Exception: {0}", ex.ToString());
+                    }
+
+                }
+            }
+            _StationInfo = stations;
+        }
+    }
+}

+ 4 - 2
EVCB_OCPP.WSServer/Program.cs

@@ -64,14 +64,16 @@ namespace EVCB_OCPP.WSServer
                         });
                     });
 
-                    services.AddScoped<ProfileHandler>();
+                    services.AddSingleton<ProfileHandler>();
 
                     services.AddScoped<OuterBusinessService>();
                     services.AddScoped<LocalBusinessService>();
                     services.AddScoped<CPOOuterResponse>();
                     services.AddSingleton<BusinessServiceFactory>();
 
-                    services.AddHostedService<ProtalServer>();
+                    //services.AddHostedService<ProtalServer>();
+
+                    services.AddProtalServer();
                 })
                 .Build();
 

+ 5 - 1
EVCB_OCPP.WSServer/Properties/launchSettings.json

@@ -1,7 +1,11 @@
 {
   "profiles": {
     "EVCB_OCPP.WSServer": {
-      "commandName": "Project"
+      "commandName": "Project",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "DOTNET_ENVIRONMENT": "Development"
+      }
     },
     "Docker": {
       "commandName": "Docker"

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 110 - 944
EVCB_OCPP.WSServer/ProtalServer.cs


+ 95 - 140
EVCB_OCPP.WSServer/Service/LoadingBalanceService.cs

@@ -1,12 +1,13 @@
 using Dapper;
-using Microsoft.Data.SqlClient;
 using Microsoft.Extensions.Configuration;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Configuration;
 using System.Data;
+using System.Data.SqlClient;
 using System.Linq;
+using System.Threading.Tasks;
 
 namespace EVCB_OCPP.WSServer.Service
 {
@@ -22,12 +23,10 @@ namespace EVCB_OCPP.WSServer.Service
 
     public class LoadingBalanceService
     {
+        ConcurrentDictionary<int, object> _lockDic = new ConcurrentDictionary<int, object>();
         private readonly string mainConnectionString;
         private readonly string webConnectionString;
 
-        ConcurrentDictionary<int, object> _lockDic = new ConcurrentDictionary<int, object>();
-
-
         public LoadingBalanceService(IConfiguration configuration)
         {
             mainConnectionString = configuration.GetConnectionString("MainDBContext");
@@ -48,9 +47,9 @@ namespace EVCB_OCPP.WSServer.Service
         }
 
 
-        public bool IsNeedtoCancelSetting(int stationId, string machineId, string chargeBoxId)
+        async public Task<bool> IsNeedtoCancelSetting(int stationId, string machineId, string chargeBoxId)
         {
-            var setting = GetLoadBalance(stationId);
+            var setting = await GetLoadBalance(stationId);
             if (setting == null) return false;
 
             lock (GetLock(stationId))
@@ -58,14 +57,14 @@ namespace EVCB_OCPP.WSServer.Service
                 if (setting.LBMode > 0 && setting.LBMode < 3 && !IsStillInTransactions(chargeBoxId))
                 {
                     // renew table
-                    UpdateLoadbalanceRecord(stationId, machineId, 0, DateTime.UtcNow);
+                    //    UpdateLoadbalanceRecord(stationId, machineId, 0, DateTime.UtcNow);
                     return true;
 
                 }
 
                 if (setting.LBMode >= 3 || setting.LBMode < 1)
                 {
-                    CloseLoadbalanceRecord(stationId);
+                    //   CloseLoadbalanceRecord(stationId);
 
                 }
             }
@@ -176,65 +175,12 @@ namespace EVCB_OCPP.WSServer.Service
 
 
 
-        public Dictionary<string, decimal?> GetRerangeSettingPower(int stationId)
-        {
-            Dictionary<string, decimal?> dic = new Dictionary<string, decimal?>();
-            var setting = GetLoadBalance(stationId);
-            if (setting == null) return null;
-
-            lock (GetLock(stationId))
-            {
-                if (setting != null && setting.LBMode == 2)
-                {
-                    string machineId = string.Empty;
-                    decimal ratedPower = GetRatedPower(machineId);
-                    //找站內最早要充電的交易 下發充電Power & 填寫新給的Power
-                    using (SqlConnection conn = new SqlConnection(mainConnectionString))
-                    {
-                        var parameters = new DynamicParameters();
-                        parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
-                        string strSql = "Select M.Id from [dbo].[LoadingBalance] LB,  [dbo].[Machine] M  where LB.StationId=@StationId and  LB.MachineId=M.Id and LB.Power < M.RatedPower and LB.FinishedOn='1991/01/01' order by LB.Id asc; ";
-                        machineId = conn.ExecuteScalar<string>(strSql, parameters);
-                    }
-
-                    if (!string.IsNullOrEmpty(machineId))
-                    {
-                        decimal estimatedPwerValue = GetFCFSPower(stationId, machineId, setting.LBCurrent);
-
-                        // renew table
-                        UpdateLoadbalanceRecord(stationId, machineId, estimatedPwerValue, null, true);
-                        // UpdateLoadbalanceRecord(stationId, machineId, estimatedPwerValue, null);
-
-                        dic.Add(machineId, estimatedPwerValue);
-                    }
-
-
-                }
-
-                if (setting != null && setting.LBMode == 1)
-                {
-                    dic = GetAveragePower(stationId, setting.LBCurrent);
-                    foreach (var kv in dic)
-                    {
-                        if (kv.Value.HasValue)
-                        {
-                            UpdateLoadbalanceRecord(stationId, kv.Key, 0, DateTime.UtcNow);
-                            UpdateLoadbalanceRecord(stationId, kv.Key, kv.Value.Value, null);
-                        }
-
-                    }
-                }
-            }
-
-
-            return dic;
 
-        }
 
-        public Dictionary<string, decimal?> GetSettingPower(int stationId, string machineId)
+        async public Task<Dictionary<string, decimal?>> GetSettingPower(int stationId)
         {
             Dictionary<string, decimal?> dic = new Dictionary<string, decimal?>();
-            var setting = GetLoadBalance(stationId);
+            var setting = await GetLoadBalance(stationId);
             if (setting == null) return null;
 
             lock (GetLock(stationId))
@@ -244,31 +190,9 @@ namespace EVCB_OCPP.WSServer.Service
                 {
                     if (setting.LBMode == 1)
                     {
-                        dic = GetAveragePower(stationId, setting.LBCurrent, machineId);
-                        foreach (var kv in dic)
-                        {
-                            if (kv.Value.HasValue)
-                            {
-                                UpdateLoadbalanceRecord(stationId, kv.Key, 0, DateTime.UtcNow);
-                                UpdateLoadbalanceRecord(stationId, kv.Key, kv.Value.Value, null);
-                            }
-
-                        }
+                        dic = GetAveragePower(stationId, setting.LBCurrent).Result;
                     }
-                    else if (setting.LBMode == 2)
-                    {
-                        dic.Add(machineId, GetFCFSPower(stationId, machineId, setting.LBCurrent));
-
-                        UpdateLoadbalanceRecord(stationId, machineId, 0, DateTime.UtcNow);
-                        UpdateLoadbalanceRecord(stationId, machineId, dic[machineId].Value, null);
 
-                    }
-                    else
-                    {
-                        // 把LB TABLE 關閉
-                        CloseLoadbalanceRecord(stationId);
-
-                    }
                 }
 
             }
@@ -277,7 +201,7 @@ namespace EVCB_OCPP.WSServer.Service
         }
 
 
-        public LoadBalanceSetting GetLoadBalance(int stationId)
+        async public Task<LoadBalanceSetting> GetLoadBalance(int stationId)
         {
             LoadBalanceSetting setting = null;
             using (SqlConnection conn = new SqlConnection(webConnectionString))
@@ -286,12 +210,13 @@ namespace EVCB_OCPP.WSServer.Service
                 parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
 
                 string strSql = "Select LBMode,LBCurrent from [dbo].[Station] where Id=@StationId ; ";
-                setting = conn.Query<LoadBalanceSetting>(strSql, parameters).FirstOrDefault();
+                var result = await conn.QueryAsync<LoadBalanceSetting>(strSql, parameters);
+                setting = result.FirstOrDefault();
             }
             return setting;
         }
 
-        private List<string> GetIdsbyStationId(int stationId)
+        async private Task<List<string>> GetIdsbyStationId(int stationId)
         {
             List<string> machineIds = new List<string>();
             using (SqlConnection conn = new SqlConnection(webConnectionString))
@@ -299,97 +224,127 @@ namespace EVCB_OCPP.WSServer.Service
                 var parameters = new DynamicParameters();
                 parameters.Add("@StationId", stationId, DbType.Int16, ParameterDirection.Input);
                 string strSql = "Select MachineId from [dbo].[StationMachine] where StationId=@StationId; ";
-                machineIds = conn.Query<String>(strSql, parameters).ToList();
+                var result = await conn.QueryAsync<String>(strSql, parameters);
+                machineIds = result.ToList();
             }
             return machineIds;
         }
 
 
 
-        private Dictionary<string, decimal?> GetAveragePower(int stationId, int availableCapacity, string machineId = "")
+        async private Task<Dictionary<string, decimal?>> GetAveragePower(int stationId, int availableCapacity)
         {
             Dictionary<string, decimal?> dic = new Dictionary<string, decimal?>();
-            //總量 * 該樁的額定功率/該站充電中樁的總額定功率
-            List<string> _MachineIds = new List<string>();
-            int skipCount = 0;
-            int size = 200;
-            int takeCount = 0;
-            int totalRatePower = 0;
-
-            using (SqlConnection conn = new SqlConnection(mainConnectionString))
+            availableCapacity = (int)(availableCapacity * 1000 / 1.05M);
+            int keepPower = 0;
+            //讀取上一次斷線但還沒充完電的分配量
+            var offlineCPs = GetChargeBoxIdbyOfflineCharging(stationId, out keepPower);
+            //扣除Keep充電功率  =  分配充電量
+            var totalPower = availableCapacity - keepPower;
+
+            if (totalPower > 0)
             {
-                var parameters = new DynamicParameters();
-                parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
-                string strSql = "Select MachineId from [dbo].[LoadingBalance] where StationId=@StationId and FinishedOn='1991/01/01'; ";
-                _MachineIds = conn.Query<string>(strSql, parameters).ToList();
-            }
+                //總量 * 該樁的額定功率/該站充電中樁的總額定功率
+                var onlineChargingCPs = await GetOnlineChargerwithCharging(stationId);
+                if (onlineChargingCPs.Count > 0)
+                {
+                    int singlePower = (int)Decimal.Divide(totalPower, onlineChargingCPs.Count);
+
+                    foreach (var id in onlineChargingCPs)
+                    {
+                        dic.Add(id, singlePower);
+                    }
+                }
 
-            if (!string.IsNullOrEmpty(machineId) && !_MachineIds.Contains(machineId))
-            {
-                _MachineIds.Add(machineId);
             }
 
-            while (skipCount < _MachineIds.Count())
-            {
-                takeCount = _MachineIds.Count() - skipCount > size ? size : _MachineIds.Count() - skipCount;
+            return dic;
+
+        }
 
-                using (SqlConnection conn = new SqlConnection(mainConnectionString))
+        async private Task<List<string>> GetOnlineChargerwithCharging(int stationId)
+        {
+            List<string> results = new List<string>();
+            List<string> machineIds = await GetIdsbyStationId(stationId);
+            List<string> chargeboxids = new List<string>();
+            using (SqlConnection conn = new SqlConnection(mainConnectionString))
+            {
+                string onlineChargerSql = "Select ChargeBoxId from [dbo].[Machine] where Id in @machineIds and [Online]=1; ";
+                var onlineResult = await conn.QueryAsync<string>(onlineChargerSql, new { machineIds = machineIds.ToArray() });
+                chargeboxids = onlineResult.ToList();
+                foreach (var chargeboxid in chargeboxids)
                 {
-                    string strSql = "Select Sum(RatedPower) from [dbo].[Machine] where Id in @machineIds and [Online]=1; ";
-                    totalRatePower += conn.ExecuteScalar<Int32>(strSql, new { machineIds = _MachineIds.ToArray() });
-                    skipCount += takeCount;
+                    string txSql = "SELECT TOP(1) [Id] from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime = '1991-01-01 00:00:00.000'; ";
+                    var txId = await conn.ExecuteScalarAsync<Int64>(txSql, new { ChargeBoxId = chargeboxid });
+                    if (txId > 0)
+                    {
+                        results.Add(chargeboxid);
+                    }
                 }
-            }
 
-            foreach (var id in _MachineIds)
-            {
-                int singleRatePower = (int)GetRatedPower(id);
-                var value = totalRatePower == 0 ? 0 : availableCapacity * singleRatePower / totalRatePower;
-                dic.Add(id, value);
             }
+            return results;
+        }
 
-            return dic;
+        /// <summary>
+        /// 取得斷線樁號
+        /// </summary>
+        /// <param name="stationId">站點代號</param>
+        /// <param name="ratedPowers">總額定功率</param>
+        /// <returns></returns>
+        private List<string> GetChargeBoxIdbyOfflineCharging(int stationId, out int ratedPowers)
+        {
+            List<string> machineIds = GetIdsbyStationId(stationId).Result;
+            List<string> result = new List<string>();
+            ratedPowers = 0;
+            using (SqlConnection conn = new SqlConnection(mainConnectionString))
+            {
 
+                string offlineChargerSql = "Select ChargeBoxId from [dbo].[Machine] where Id in @machineIds and [Online]=0; ";
+                result = conn.Query<string>(offlineChargerSql, new { machineIds = machineIds }).ToList();
+                foreach (var charger in result)
+                {
+                    string txSql = "SELECT TOP(1) [Id] from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime = '1991-01-01 00:00:00.000'; ";
+                    var txId = conn.ExecuteScalar<Int64>(txSql, new { ChargeBoxId = charger });
+                    if (txId > 0)
+                    {
+                        string ratedPowerSql = "Select Sum(RatedPower) from [dbo].[Machine] where ChargeBoxId=@ChargeBoxId and [Online]=0; ";
+                        ratedPowers += conn.ExecuteScalar<int>(ratedPowerSql, new { ChargeBoxId = charger });
+                    }
+                }
+            }
+            ratedPowers *= 1000;
+            return result;
         }
 
-        private decimal GetRatedPower(string machineId)
+        private decimal GetRatedPowerbyChargeBoxId(string chargeBoxId)
         {
             decimal ratedPower = 0;
             using (SqlConnection conn = new SqlConnection(mainConnectionString))
             {
                 var parameters = new DynamicParameters();
-                parameters.Add("@machineId", machineId, DbType.String, ParameterDirection.Input);
+                parameters.Add("@machineId", chargeBoxId, DbType.String, ParameterDirection.Input);
                 string strSql = "Select RatedPower from [dbo].[Machine] where Id=@machineId; ";
                 ratedPower = conn.ExecuteScalar<Int32>(strSql, parameters);
             }
             return ratedPower;
         }
 
-        private decimal GetFCFSPower(int stationId, string machineId, int availableCapacity)
+        private decimal GetRatedPowerbyId(string machineId)
         {
-
-            decimal ongoingPower = 0;
-            decimal singleRatePower = GetRatedPower(machineId);
-
-            //先找LB 裡面目前下發的Power
-            decimal? currentPower = GetCurrentSetting(machineId);
-
-            if (!currentPower.HasValue) currentPower = 0;
-
-            //總量 - 所有正在進行的Power
+            decimal ratedPower = 0;
             using (SqlConnection conn = new SqlConnection(mainConnectionString))
             {
                 var parameters = new DynamicParameters();
-                parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
-                string strSql = "Select Sum(Power) from [dbo].[LoadingBalance] where StationId=@StationId and FinishedOn='1991/01/01'; ";
-                ongoingPower = conn.ExecuteScalar<Int32>(strSql, parameters);
+                parameters.Add("@machineId", machineId, DbType.String, ParameterDirection.Input);
+                string strSql = "Select RatedPower from [dbo].[Machine] where Id=@machineId; ";
+                ratedPower = conn.ExecuteScalar<Int32>(strSql, parameters);
             }
-
-            return availableCapacity - (ongoingPower - currentPower.Value) > singleRatePower ? singleRatePower :
-                (availableCapacity - (ongoingPower - currentPower.Value) > 0 ? availableCapacity - (ongoingPower - currentPower.Value) : 0);
+            return ratedPower;
         }
 
 
+
     }
 }
 

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.