Răsfoiți Sursa

private test passed

Robert 1 an în urmă
părinte
comite
a2fc78db27

+ 14 - 0
EVCB_OCPP.WSServer/Dto/StationMachineConfig.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Dto
+{
+    public class StationMachineConfig
+    {
+        public string ConfigureName { get; set; }
+        public string ConfigureSetting { get; set; }
+    }
+}

+ 1 - 1
EVCB_OCPP.WSServer/Helper/MeterValueGroupSingleHandler.cs

@@ -1,7 +1,7 @@
 using Dapper;
 using EVCB_OCPP.Domain;
 using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.WSServer.Service;
+using EVCB_OCPP.WSServer.Service.DbService;
 using Microsoft.Data.SqlClient;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Configuration;

+ 2 - 0
EVCB_OCPP.WSServer/HostedProtalServer.cs

@@ -6,6 +6,7 @@ using EVCB_OCPP.WSServer.Helper;
 using EVCB_OCPP.WSServer.Jobs;
 using EVCB_OCPP.WSServer.Message;
 using EVCB_OCPP.WSServer.Service;
+using EVCB_OCPP.WSServer.Service.DbService;
 using EVCB_OCPP.WSServer.Service.WsService;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Caching.Memory;
@@ -41,6 +42,7 @@ namespace EVCB_OCPP.WSServer
             services.AddSingleton<IMainDbService, MainDbService>();
             services.AddSingleton<IConnectionLogdbService, ConnectionLogdbService>();
 
+            services.AddSingleton<ConfirmWaitingMessageSerevice>();
             services.AddTransient<ProfileHandler>();
             services.AddSingleton<ProtalServer>();
             services.AddHostedService<ProtalServer>(p => p.GetRequiredService<ProtalServer>());

+ 1 - 2
EVCB_OCPP.WSServer/Jobs/DenyModelCheckJob.cs

@@ -1,6 +1,5 @@
 using Dapper;
-
-using EVCB_OCPP.WSServer.Service;
+using EVCB_OCPP.WSServer.Service.DbService;
 using EVCB_OCPP.WSServer.Service.WsService;
 using Microsoft.Data.SqlClient;
 using Microsoft.Extensions.Configuration;

+ 1 - 2
EVCB_OCPP.WSServer/Jobs/HeartBeatCheckJob.cs

@@ -1,6 +1,5 @@
 using EVCB_OCPP.Domain;
-
-using EVCB_OCPP.WSServer.Service;
+using EVCB_OCPP.WSServer.Service.DbService;
 using EVCB_OCPP.WSServer.Service.WsService;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Logging;

+ 12 - 25
EVCB_OCPP.WSServer/Jobs/ServerMessageJob.cs

@@ -3,6 +3,7 @@ using EVCB_OCPP.Domain.Models.MainDb;
 using EVCB_OCPP.Packet.Features;
 using EVCB_OCPP.Packet.Messages;
 using EVCB_OCPP.WSServer.Message;
+using EVCB_OCPP.WSServer.Service;
 using EVCB_OCPP.WSServer.Service.WsService;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Configuration;
@@ -23,16 +24,19 @@ public class ServerMessageJob : IJob
 {
     public ServerMessageJob(
         ProtalServer protalServer,
+        ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice,
         IConfiguration configuration,
         IDbContextFactory<MainDBContext> maindbContextFactory,
         ILogger<ServerMessageJob> logger)
     {
         this.protalServer = protalServer;
+        this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
         this.maindbContextFactory = maindbContextFactory;
         this.logger = logger;
     }
 
     private readonly ProtalServer protalServer;
+    private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
     private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
     private readonly ILogger<ServerMessageJob> logger;
 
@@ -51,7 +55,7 @@ public class ServerMessageJob : IJob
 
     private async Task ExecuteTrigger()
     {
-        protalServer.RemoveConfirmMessage();
+        confirmWaitingMessageSerevice.RemoveExpiredConfirmMessage();
 
         BasicMessageHandler msgAnalyser = new BasicMessageHandler();
         var dateTimeNow = DateTime.UtcNow;
@@ -71,7 +75,8 @@ public class ServerMessageJob : IJob
         {
             // Console.WriteLine(string.Format("Now:{0} commandList Count:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), commandList.Count));
         }
-        var resendList = protalServer.GetResendMessage();
+        //List<NeedConfirmMessage> resendList = protalServer.GetResendMessage();
+        List<NeedConfirmMessage> resendList = confirmWaitingMessageSerevice.GetPendingMessages();
         foreach (var resendItem in resendList)
         {
             WsClientData session;
@@ -79,8 +84,7 @@ public class ServerMessageJob : IJob
             {
                 if (dateTimeNow.Subtract(resendItem.SentOn).TotalSeconds > 1)
                 {
-                    resendItem.SentTimes--;
-                    resendItem.SentOn = dateTimeNow;
+                    confirmWaitingMessageSerevice.SignalMessageSended(resendItem);
                     protalServer.SendMsg(session, resendItem.SentMessage, string.Format("{0} {1}", resendItem.SentAction, "Request"), "");
                 }
 
@@ -99,7 +103,8 @@ public class ServerMessageJob : IJob
 
             //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)
+            //if (session.IsCheckIn && !session.ISOCPP20)
+            if (!session.ISOCPP20)
             {
                 string rawRequest = string.Empty;
 
@@ -154,26 +159,8 @@ public class ServerMessageJob : IJob
                         }
                     }
 
-                    protalServer.AddConfirmMessage(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
-
-                    dateTimeNow = DateTime.UtcNow;
-                    #region 更新資料表單一欄位
-                    var _UpdatedItem = new ServerMessage() { Id = item.Id, UpdatedOn = dateTimeNow };
-                    //db.Configuration.AutoDetectChangesEnabled = false;//自動呼叫DetectChanges()比對所有的entry集合的每一個屬性Properties的新舊值
-                    //db.Configuration.ValidateOnSaveEnabled = false;// 因為Entity有些欄位必填,若不避開會有Validate錯誤
-                    // var _UpdatedItem = db.ServerMessage.Where(x => x.Id == item.Id).FirstOrDefault();
-
-                    using (var db = await maindbContextFactory.CreateDbContextAsync())
-                    {
-                        db.ServerMessage.Attach(_UpdatedItem);
-                        _UpdatedItem.UpdatedOn = dateTimeNow;
-                        db.Entry(_UpdatedItem).Property(x => x.UpdatedOn).IsModified = true;// 可以直接使用這方式強制某欄位要更新,只是查詢集合耗效能而己
-
-                        await db.SaveChangesAsync();
-                        db.ChangeTracker.Clear();
-                    }
-
-                    #endregion
+                    await confirmWaitingMessageSerevice.Add(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
+                    //protalServer.AddConfirmMessage(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
 
                     await Task.Delay(100);
 

+ 6 - 84
EVCB_OCPP.WSServer/Jobs/ServerSetFeeJob.cs

@@ -7,6 +7,7 @@ using EVCB_OCPP.WSServer.Dto;
 using EVCB_OCPP.WSServer.Helper;
 using EVCB_OCPP.WSServer.Message;
 using EVCB_OCPP.WSServer.Service;
+using EVCB_OCPP.WSServer.Service.DbService;
 using EVCB_OCPP.WSServer.Service.WsService;
 using Microsoft.Data.SqlClient;
 using Microsoft.EntityFrameworkCore;
@@ -27,6 +28,7 @@ namespace EVCB_OCPP.WSServer.Jobs;
 public class ServerSetFeeJob : IJob
 {
     private readonly ProtalServer protalServer;
+    private readonly WebDbService webDbService;
     private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
     private readonly ServerMessageService messageService;
     private readonly IMainDbService mainDbService;
@@ -35,12 +37,14 @@ public class ServerSetFeeJob : IJob
 
     public ServerSetFeeJob(
         ProtalServer protalServer,
-		ISqlConnectionFactory<WebDBConetext> sqlConnectionFactory,
+        WebDbService webDbService,
+        ISqlConnectionFactory<WebDBConetext> sqlConnectionFactory,
         ServerMessageService messageService,
         IMainDbService mainDbService,
         ILogger<ServerSetFeeJob> logger)
     {
         this.protalServer = protalServer;
+        this.webDbService = webDbService;
         this.webDbConnectionFactory = sqlConnectionFactory;
         this.messageService = messageService;
         this.mainDbService = mainDbService;
@@ -64,7 +68,7 @@ public class ServerSetFeeJob : IJob
                     continue;
                 }
 
-                string displayPriceText = await SetDefaultFee(session);
+                string displayPriceText = await webDbService.SetDefaultFee(session);
                 if (string.IsNullOrEmpty(displayPriceText) || displayPriceText == session.DisplayPrice)
                 {
                     continue;
@@ -90,86 +94,4 @@ public class ServerSetFeeJob : IJob
             }
         }
     }
-
-    async private Task<string> SetDefaultFee(WsClientData client)
-    {
-        string displayPriceText = string.Empty;
-        string charingPriceText = string.Empty;
-
-        if (string.IsNullOrEmpty(client.ChargeBoxId)) return displayPriceText;
-
-        try
-        {
-            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@MachineId", client.MachineId, DbType.String, ParameterDirection.Input, 36);
-                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;
-            }
-        }
-        catch (Exception ex)
-        {
-            logger.LogError("SetDefaultFee", ex.ToString());
-        }
-
-        return displayPriceText;
-    }
 }

+ 23 - 13
EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs

@@ -21,6 +21,7 @@ using System.Globalization;
 using EVCB_OCPP.WSServer.Service.WsService;
 using EVCB_OCPP.Domain.Models.MainDb;
 using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.WSServer.Service.DbService;
 
 namespace EVCB_OCPP.WSServer.Message;
 
@@ -254,21 +255,30 @@ internal partial class ProfileHandler
 							heartbeat_interval = heartbeat_interval == 0 ? GlobalConfig.GetHEARTBEAT_INTERVAL() : heartbeat_interval;
 						}
 
-						if (session.IsPending == true)
-						{
-							session.IsPending = false;
-						}
-						if (session.IsPending == null)
-						{
-							session.IsPending = true;
-						}
-
-						var confirm = new BootNotificationConfirmation()
+						int toReturnInterval = 5;
+						RegistrationStatus toReturnRegistrationStatus = RegistrationStatus.Rejected ;
+
+                        switch (session.BootStatus)
+                        {
+                            case BootStatus.Startup:
+                            case BootStatus.Initializing:
+                                toReturnInterval = 5;
+                                toReturnRegistrationStatus = RegistrationStatus.Pending;
+                                break;
+                            case BootStatus.Pending:
+                            case BootStatus.Accepted:
+                                toReturnInterval = heartbeat_interval;
+                                toReturnRegistrationStatus = RegistrationStatus.Accepted;
+                                session.BootStatus = BootStatus.Accepted;
+                                break;
+                        }
+
+                        var confirm = new BootNotificationConfirmation()
 						{
 							currentTime = DateTime.UtcNow,
-							interval = session.IsPending.Value ? 5 : heartbeat_interval,
-							status = session.IsPending.Value ? RegistrationStatus.Pending : RegistrationStatus.Accepted
-						};
+                            interval = toReturnInterval,
+                            status = toReturnRegistrationStatus
+                        };
 
 						result.Message = confirm;
 						result.Success = true;

+ 117 - 337
EVCB_OCPP.WSServer/ProtalServer.cs

@@ -28,6 +28,10 @@ using Microsoft.Extensions.Logging;
 using EVCB_OCPP.WSServer.Service.WsService;
 using System.Net.WebSockets;
 using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.WSServer.Service.DbService;
+using EVCB_OCPP.Packet.Messages.SubTypes;
+using System.Linq;
+using Azure;
 
 namespace EVCB_OCPP.WSServer
 {
@@ -65,6 +69,7 @@ namespace EVCB_OCPP.WSServer
             , ServerMessageService serverMessageService
             , IServiceProvider serviceProvider
             , OcppWebsocketService websocketService
+            , ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice
             , OuterHttpClient httpClient)
         {
             _ct = _cts.Token;
@@ -79,6 +84,7 @@ namespace EVCB_OCPP.WSServer
             this.webDbService = webDbService;
             this.messageService = serverMessageService;
             this.websocketService = websocketService;
+            this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
             this.httpClient = httpClient;
             isInDocker = !string.IsNullOrEmpty(configuration["DOTNET_RUNNING_IN_CONTAINER"]);
 
@@ -94,7 +100,7 @@ namespace EVCB_OCPP.WSServer
         private DateTime lastcheckdt = DateTime.UtcNow.AddSeconds(-20);
         private ConcurrentDictionary<string, WsClientData> clientDic = new ConcurrentDictionary<string, WsClientData>();
         //private readonly Object _lockClientDic = new object();
-        private readonly Object _lockConfirmPacketList = new object();
+        //private readonly Object _lockConfirmPacketList = new object();
         private readonly ILogger<ProtalServer> logger;
         private readonly IConfiguration configuration;
         //private readonly IServiceProvider serviceProvider;
@@ -108,10 +114,11 @@ namespace EVCB_OCPP.WSServer
         private readonly WebDbService webDbService;
         private readonly ServerMessageService messageService;
         private readonly OcppWebsocketService websocketService;
+        private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
         private readonly ProfileHandler profileHandler;//= new ProfileHandler();
         //private readonly string webConnectionString;// = ConfigurationManager.ConnectionStrings["WebDBContext"].ConnectionString;
         private readonly bool isInDocker;
-        private List<NeedConfirmMessage> needConfirmPacketList = new List<NeedConfirmMessage>();
+        //private List<NeedConfirmMessage> needConfirmPacketList = new List<NeedConfirmMessage>();
         private DateTime checkUpdateDt = DateTime.UtcNow;
         private DateTime _CheckFeeDt = DateTime.UtcNow;
         private DateTime _CheckLBDt = DateTime.UtcNow;
@@ -164,18 +171,6 @@ namespace EVCB_OCPP.WSServer
             return toReturn;
         }
 
-        internal List<NeedConfirmMessage> GetResendMessage()
-        {
-            List<NeedConfirmMessage> sendMessages = new List<NeedConfirmMessage>();
-            lock (_lockConfirmPacketList)
-            {
-                sendMessages = needConfirmPacketList.Where(x => x.SentTimes > 1 && x.CreatedBy == "Server").ToList();
-
-            }
-
-            return sendMessages;
-        }
-
         internal IReadOnlyList<Profile> Profiles => profiles.AsReadOnly();
         internal LoadingBalanceService LoadingBalanceService => _loadingBalanceService;
         internal ProfileHandler ProfileHandler => profileHandler;
@@ -419,201 +414,11 @@ namespace EVCB_OCPP.WSServer
             }
         }
 
-        private void RunHttpConsoleService()
-        {
-            var appBuilder = WebApplication.CreateBuilder();
-            appBuilder.Services.AddSingleton<IHostLifetime, DummyHostLifeTime>();
-            var app = appBuilder.Build();
-
-            var helpFunc = () => {
-                return string.Join("\r\n", new[] { 
-                    "Command help!!",
-                    "lcn : List Customer Name",
-                    "gc : GC Collect",
-                    "lc : List Clients",
-                    "silent : silent",
-                    "show : show log"
-                });
-            };
-            app.MapGet("/", helpFunc);
-            app.MapGet("/help", helpFunc);
-
-            app.MapPost("/stop", () => {
-                Stop();
-                return "Command stop";
-            });
-
-            app.MapPost("/gc", () => {
-                GC.Collect();
-                return "Command GC";
-            });
-
-            app.MapPost("/lc", () => {
-                List<string> toReturn = new List<string>() { "Command List Clients" };
-                Dictionary<string, WsClientData> _copyClientDic = null;
-                _copyClientDic = new Dictionary<string, WsClientData>(clientDic);
-                var list = _copyClientDic.Select(c => c.Value).ToList();
-                int i = 1;
-                foreach (var c in list)
-                {
-                    toReturn.Add(i + ":" + c.ChargeBoxId + " " + c.SessionID);
-                    i++;
-                }
-                return string.Join("\r\n", toReturn);
-            });
-
-            app.MapPost("/lcn", () => {
-                List<string> toReturn = new List<string> { "Command List Customer Name" };
-                Dictionary<string, WsClientData> _copyClientDic = null;
-                _copyClientDic = new Dictionary<string, WsClientData>(clientDic);
-                var lcn = clientDic.Select(c => c.Value.CustomerName).Distinct().ToList();
-                int iLcn = 1;
-                foreach (var c in lcn)
-                {
-                    toReturn.Add(iLcn + ":" + c + ":" + clientDic.Where(z => z.Value.CustomerName == c).Count().ToString());
-                    iLcn++;
-                }
-                return string.Join("\r\n", toReturn);
-            });
-
-            app.MapPost("/silent", () => {
-                foreach (var rule in LogManager.Configuration.LoggingRules)
-                {
-                    if (rule.RuleName != "ConsoleLog")
-                    {
-                        continue;
-                    }
-
-                    var isTargetRule = rule.Targets.Any(x => x.Name.ToLower() == "console");
-
-                    if (isTargetRule)
-                    {
-                        rule.SetLoggingLevels(NLog.LogLevel.Warn, NLog.LogLevel.Off);
-                    }
-                }
-                return "Command silent";
-            });
-
-            app.MapPost("/show", () => {
-                foreach (var rule in LogManager.Configuration.LoggingRules)
-                {
-                    if (rule.RuleName != "ConsoleLog")
-                    {
-                        continue;
-                    }
-
-                    var isTargetRule = rule.Targets.Any(x => x.Name.ToLower() == "console");
-
-                    if (isTargetRule)
-                    {
-                        rule.SetLoggingLevels(NLog.LogLevel.Trace, NLog.LogLevel.Off);
-                    }
-                }
-                return "Command show";
-            });
-
-            app.MapGet("/threads", () => {
-                ThreadPool.GetMaxThreads(out var maxWorkerThread,out var maxCompletionPortThreads);
-                ThreadPool.GetAvailableThreads(out var avaliableWorkerThread, out var avaliableCompletionPortThreads);
-                return $"WorkerThread:{avaliableWorkerThread}/{maxWorkerThread} CompletionPortThreads{avaliableCompletionPortThreads}/{maxCompletionPortThreads}";
-            });
-
-            app.MapPost("/threads", (int min, int max) => {
-                ThreadPool.GetMaxThreads(out var maxWorkerThread, out var maxCompletionPortThreads);
-                ThreadPool.GetAvailableThreads(out var avaliableWorkerThread, out var avaliableCompletionPortThreads); 
-                ThreadPool.SetMinThreads(min, 0);
-                ThreadPool.SetMaxThreads(max, maxCompletionPortThreads);
-                return $"WorkerThread:{avaliableWorkerThread}/{maxWorkerThread} CompletionPortThreads{avaliableCompletionPortThreads}/{maxCompletionPortThreads}";
-            });
-
-            app.Urls.Add("http://*:54088");
-
-            _ = app.RunAsync();
-
-            var builder = WebApplication.CreateBuilder();
-            //builder.Configuration.AddJsonFile("./EVCB_OCPP.WSServer/appsettings.json");
-            //var sec = builder.Configuration.GetSection("ReverseProxy");
-            //Console.WriteLine($"Printing.............");
-            //foreach (var pair in sec.AsEnumerable())
-            //{
-            //    Console.WriteLine($"{pair.Key}:{pair.Value} \n");
-            //}
-
-            builder.Services.AddReverseProxy()
-                .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
-            builder.Services.AddSingleton<IHostLifetime, DummyHostLifeTime>();
-            var yarpApp = builder.Build();
-            yarpApp.Urls.Add("http://*:80");
-            yarpApp.MapReverseProxy();
-            _ = yarpApp.RunAsync();
-        }
-
         internal void Stop()
         {
             _cts?.Cancel();
         }
 
-        //private void OpenNetwork()
-        //{
-
-        //    //載入OCPP Protocol
-        //    OCPPWSServer appServer = ocppWSServerFactory.Create(new List<OCPPSubProtocol>() { new OCPPSubProtocol(), new OCPPSubProtocol(" ocpp1.6"), new OCPPSubProtocol("ocpp2.0") });
-        //    //var appServer = new OCPPWSServer(new List<OCPPSubProtocol>() { new OCPPSubProtocol(), new OCPPSubProtocol(" ocpp1.6"), new OCPPSubProtocol("ocpp2.0") });
-
-        //    List<IListenerConfig> llistener = new List<IListenerConfig>();
-
-        //    if (GlobalConfig.GetWS_Port() != 0)
-        //    {
-        //        llistener.Add(new ListenerConfig { Ip = System.Net.IPAddress.Any.ToString(), Port = GlobalConfig.GetWS_Port(), Backlog = 100, Security = "None" });
-        //    }
-
-        //    foreach (var securityport in GlobalConfig.GetWSS_Ports())
-        //    {
-        //        llistener.Add(new ListenerConfig { Ip = System.Net.IPAddress.Any.ToString(), Port = securityport, Backlog = 100, Security = SslProtocols.Tls12.ToString() });
-        //    }
-
-        //    //var config = ConfigurationManager.GetSection("superSocket") as IConfigurationSource;\
-        //    //var certificate = configuration.GetSection("superSocket").GetSection("Servers:0").GetSection("Certificate").Get<CertificateConfig>();
-        //    var certificate = configuration.GetSection("SuperSocketServerCertificate").Get<CertificateConfig>();
-        //    ICertificateConfig Certificate = certificate;
-        //    IEnumerable<IListenerConfig> listeners = llistener;
-
-        //    //設定server config
-        //    var serverConfig = new ServerConfig
-        //    {
-        //        SendingQueueSize = 10,
-        //        //Port = Convert.ToInt32(2012),
-        //        //Ip = "172.17.40.13",
-        //        MaxRequestLength = 204800,
-        //        //Security = serverSecurity,
-        //        //Certificate = Certificate,
-        //        Listeners = listeners,
-        //        //  LogAllSocketException = true,
-        //        KeepAliveTime = 10,
-        //        // LogBasicSessionActivity = true
-        //        //Security = "None"
-        //    };
-
-        //    //Setup with listening port
-        //    if (!appServer.Setup(serverConfig, logFactory: new NLogLoggerFactory()))
-        //    {
-        //        Console.WriteLine("Failed to setup!");
-        //        return;
-        //    }
-
-        //    appServer.NewSessionConnected += AppServer_NewSessionConnected;
-        //    appServer.SessionClosed += AppServer_SessionClosed;
-
-
-        //    //Try to start the appServer
-        //    if (!appServer.Start())
-        //    {
-        //        Console.WriteLine("Failed to start!");
-        //        //Console.ReadKey();
-        //        return;
-        //    }
-        //}
-
         async private void ReceivedMessageTimeLimited(object sender, string rawdata)
         {
             if (sender is not WsClientData session)
@@ -858,8 +663,6 @@ namespace EVCB_OCPP.WSServer
 
                     }
                 }
-
-                await Task.Delay(10);
             }
             catch (Exception ex)
             {
@@ -876,6 +679,10 @@ namespace EVCB_OCPP.WSServer
                     logger.LogError(string.Format("{0} **Exception :{1} ", session.ChargeBoxId, ex.ToString()));
                 }
             }
+            finally
+            {
+                await Task.Delay(10);
+            }
         }
 
         private async Task ProcessRequestMessage(MessageResult analysisResult, WsClientData session, Actions action)
@@ -901,57 +708,20 @@ namespace EVCB_OCPP.WSServer
                             {
                                 string response = BasicMessageHandler.GenerateConfirmation(analysisResult.UUID, (IConfirmation)replyResult.Message);
 
-
                                 Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Confirmation"), replyResult.Exception == null ? string.Empty : replyResult.Exception.ToString());
 
-                                if (action == Actions.BootNotification && replyResult.Message is BootNotificationConfirmation)
+                                if (action == Actions.BootNotification && replyResult.Message is BootNotificationConfirmation bootNotificationConfirmation)
                                 {
-                                    if (((BootNotificationConfirmation)replyResult.Message).status == Packet.Messages.SubTypes.RegistrationStatus.Accepted)
+                                    if (session.BootStatus == BootStatus.Startup)
                                     {
-                                        session.IsCheckIn = true;
-
-                                        await messageService.SendGetEVSEConfigureRequest(session.ChargeBoxId);
-
-                                     
-
-                                        await messageService.SendDataTransferRequest(
-                                            session.ChargeBoxId,
-                                            messageId: "ID_FirmwareVersion",
-                                            vendorId: "Phihong Technology",
-                                            data: string.Empty);
+                                        session.AddTask(InitializeEVSE(session));
+                                        session.BootStatus = BootStatus.Initializing;
                                     }
-                                    else
-									{
-                                        // Pending mode 下發設定 
-										using (var db = await maindbContextFactory.CreateDbContextAsync())
-                                        {
-                                            var machine = await db.Machine.Where(x => x.ChargeBoxId == session.ChargeBoxId).FirstOrDefaultAsync();
-                                            if (machine != null)
-                                            {
-                                                if (machine.ConnectorType.Contains("6") || machine.ConnectorType.Contains("7") || machine.ConnectorType.Contains("8") || machine.ConnectorType.Contains("9"))
-                                                {
-                                                    session.IsAC = false;
-                                                }
-                                                machine.ConnectionType = session.UriScheme.Contains("wss") ? 2 : 1;
-                                                await db.SaveChangesAsync();
-                                            }
-                                        }									
 
-										await SetDefaultFee(session);
-
-
-										if (session.CustomerId == new Guid("298918C0-6BB5-421A-88CC-4922F918E85E") || session.CustomerId == new Guid("9E6BFDCC-09FB-4DAB-A428-43FE507600A3"))
-										{
-											await messageService.SendChangeConfigurationRequest(
-												session.ChargeBoxId, key: "TimeOffset", value: "+08:00");											
-										}
-
-										if (session.CustomerId == new Guid("D57D5BCC-C5B0-4031-A7AE-7516E00CB028") )
-										{
-											await messageService.SendChangeConfigurationRequest(
-												session.ChargeBoxId, key: "StopTransactionOnInvalidId", value: "True");											
-										}
-									}
+                                    if (bootNotificationConfirmation.status == Packet.Messages.SubTypes.RegistrationStatus.Accepted)
+                                    {
+                                        session.IsCheckIn = true;
+                                    }
                                 }
 
                                 if (action == Actions.Authorize && replyResult.Message is AuthorizeConfirmation)
@@ -1102,7 +872,7 @@ namespace EVCB_OCPP.WSServer
         {
 
             BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-            if (await ReConfirmMessage(analysisResult))
+            if (await confirmWaitingMessageSerevice.TryConfirmMessage(analysisResult))
             {
                 var profileName = profiles.Where(x => x.IsExisted(analysisResult.Action)).Select(x => x.Name).FirstOrDefault();
                 MessageResult confirmResult = null;
@@ -1170,7 +940,7 @@ namespace EVCB_OCPP.WSServer
         private async void ProcessErrorMessage(MessageResult analysisResult, WsClientData session, Actions action)
         {
             BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-            if (await ReConfirmMessage(analysisResult))
+            if (await confirmWaitingMessageSerevice.TryConfirmMessage(analysisResult))
             {
                 var profileName = profiles.Where(x => x.IsExisted(analysisResult.Action)).Select(x => x.Name).FirstOrDefault();
                 switch (profileName)
@@ -1225,6 +995,100 @@ namespace EVCB_OCPP.WSServer
             }
         }
 
+        private async Task InitializeEVSE(WsClientData session)
+        {
+            // Pending mode 下發設定 
+            using (var db = await maindbContextFactory.CreateDbContextAsync())
+            {
+                var machine = await db.Machine.Where(x => x.ChargeBoxId == session.ChargeBoxId).FirstOrDefaultAsync();
+                if (machine != null)
+                {
+                    if (machine.ConnectorType.Contains("6") || machine.ConnectorType.Contains("7") || machine.ConnectorType.Contains("8") || machine.ConnectorType.Contains("9"))
+                    {
+                        session.IsAC = false;
+                    }
+                    machine.ConnectionType = session.UriScheme.Contains("wss") ? 2 : 1;
+                    await db.SaveChangesAsync();
+                }
+            }
+            string requestId = string.Empty;
+            MessageResult response = null;
+
+            var displayPriceText = await webDbService.SetDefaultFee(session);
+            UpdateClientDisplayPrice(session.ChargeBoxId, displayPriceText);
+
+            do
+            {
+                requestId = await messageService.SendChangeConfigurationRequest(
+                    session.ChargeBoxId, key: "DefaultPrice", value: displayPriceText);
+                response = await confirmWaitingMessageSerevice.WaitResultAsync(requestId);
+            }
+            while (response == null);
+            //await SetDefaultFee(session);
+
+            if (session.CustomerId == new Guid("298918C0-6BB5-421A-88CC-4922F918E85E") || session.CustomerId == new Guid("9E6BFDCC-09FB-4DAB-A428-43FE507600A3"))
+            {
+                await messageService.SendChangeConfigurationRequest(
+                    session.ChargeBoxId, key: "TimeOffset", value: "+08:00");
+            }
+
+            if (session.CustomerId == new Guid("D57D5BCC-C5B0-4031-A7AE-7516E00CB028"))
+            {
+                await messageService.SendChangeConfigurationRequest(
+                    session.ChargeBoxId, key: "StopTransactionOnInvalidId", value: "True");
+            }
+
+            do
+            {
+                requestId = await messageService.SendGetEVSEConfigureRequest(session.ChargeBoxId);
+                response = await confirmWaitingMessageSerevice.WaitResultAsync(requestId);
+            }
+            while (response == null);
+            //GetConfigurationConfirmation
+            if (response.Success && response.Message is GetConfigurationConfirmation confirmation)
+            {
+                await CheckandUpdateConfig(session.ChargeBoxId, confirmation.configurationKey.ToDictionary(x => x.key, x => x.value));
+            }
+
+            await messageService.SendDataTransferRequest(
+                session.ChargeBoxId,
+                messageId: "ID_FirmwareVersion",
+                vendorId: "Phihong Technology",
+                data: string.Empty);
+
+            if (session.BootStatus == BootStatus.Initializing)
+            {
+                session.BootStatus = BootStatus.Pending;
+            }
+        }
+
+        private async Task CheckandUpdateConfig(string chargeBoxId, Dictionary<string,string> currentConfigs)
+        {
+            List<KeyValuePair<string, string>> configs = await webDbService.GetCustomerStationEvseConfig(chargeBoxId);
+            if (configs == null)
+            {
+                logger.LogInformation("{0} get station config failed", chargeBoxId);
+                return;
+            }
+
+            foreach(var config in configs)
+            {
+                if (currentConfigs.Keys.Contains(config.Key) &&
+                    currentConfigs[config.Key] == config.Value)
+                {
+                    continue;
+                }
+
+                MessageResult response = null;
+                do
+                {
+                    var requestId = await messageService.SendChangeConfigurationRequest(chargeBoxId, config.Key, config.Value);
+                    response = await confirmWaitingMessageSerevice.WaitResultAsync(requestId);
+                }
+                while (response == null);
+            }
+        }
+
         private void Send(WsClientData session, string msg, string messageType, string errorMsg = "")
         {
             try
@@ -1315,90 +1179,6 @@ namespace EVCB_OCPP.WSServer
             return displayPriceText;
         }
 
-        internal void AddConfirmMessage(string chargePointSerialNumber, int table_id, string requestId, string action, string msg_id, string createdBy, string sendMessage)
-        {
-            NeedConfirmMessage _needConfirmMsg = new NeedConfirmMessage();
-            _needConfirmMsg.Id = table_id;
-            _needConfirmMsg.SentAction = action;
-            _needConfirmMsg.SentOn = DateTime.UtcNow;
-            _needConfirmMsg.SentTimes = 4;
-            _needConfirmMsg.ChargePointSerialNumber = chargePointSerialNumber;
-            _needConfirmMsg.RequestId = requestId;
-            _needConfirmMsg.SentUniqueId = msg_id;
-            _needConfirmMsg.CreatedBy = createdBy;
-            _needConfirmMsg.SentMessage = sendMessage;
-
-            if (needConfirmActions.Contains(action))
-            {
-
-                lock (_lockConfirmPacketList)
-                {
-                    needConfirmPacketList.Add(_needConfirmMsg);
-                }
-            }
-        }
-
-        internal void RemoveConfirmMessage()
-        {
-            var before10Mins = DateTime.UtcNow.AddMinutes(-10);
-            lock (_lockConfirmPacketList)
-            {
-                var removeList = needConfirmPacketList.Where(x => x.SentTimes == 0 || x.SentOn < before10Mins).ToList();
-                foreach (var item in removeList)
-                {
-                    needConfirmPacketList.Remove(item);
-                }
-            }
-        }
-
-        private async Task<bool> ReConfirmMessage(MessageResult analysisResult)
-        {
-            bool confirmed = false;
-            if (needConfirmActions.Contains(analysisResult.Action))
-            {
-
-                NeedConfirmMessage foundRequest = null;
-                lock (_lockConfirmPacketList)
-                {
-                    foundRequest = needConfirmPacketList.Where(x => x.SentUniqueId == analysisResult.UUID).FirstOrDefault();
-                }
-
-                if (foundRequest != null && foundRequest.Id > 0)
-                {
-                    foundRequest.SentTimes = 0;
-                    foundRequest.SentInterval = 0;
-                    analysisResult.RequestId = foundRequest.RequestId;
-
-                    using (var db = await maindbContextFactory.CreateDbContextAsync())
-                    {
-                        var sc = await db.ServerMessage.Where(x => x.Id == foundRequest.Id).FirstOrDefaultAsync();
-                        sc.InMessage = JsonConvert.SerializeObject(analysisResult.Message, Formatting.None);
-                        sc.ReceivedOn = DateTime.UtcNow;
-                        await db.SaveChangesAsync();
-                        //  Console.WriteLine(string.Format("Now:{0} ServerMessage Id:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), foundRequest.Id));
-
-                    }
-                    confirmed = true;
-
-
-
-                }
-                else if (analysisResult.Action == Actions.TriggerMessage.ToString())
-                {
-                    confirmed = true;
-                }
-                else
-                {
-                    logger.LogError(string.Format("Received no record Action:{0} MessageId:{1} ", analysisResult.Action, analysisResult.UUID));
-                }
-            }
-
-            return confirmed;
-        }
-
-
-
-
         internal async void RemoveClient(WsClientData session, string reason)
         {
             if (session == null)

+ 1 - 0
EVCB_OCPP.WSServer/Service/BusinessServiceFactory.cs

@@ -2,6 +2,7 @@
 
 using EVCB_OCPP.Packet.Messages.SubTypes;
 using EVCB_OCPP.WSServer.Dto;
+using EVCB_OCPP.WSServer.Service.DbService;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.DependencyInjection;
 using System;

+ 224 - 0
EVCB_OCPP.WSServer/Service/ConfirmWaitingMessageSerevice.cs

@@ -0,0 +1,224 @@
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.MainDb;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.WSServer.Message;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Query.Internal;
+using Microsoft.Extensions.Logging;
+using Microsoft.Identity.Client;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service
+{
+    public enum ConfirmWaitingMessageRemoveReason
+    {
+        HitRetryLimit,
+        Confirmed,
+    }
+
+    public class ConfirmWaitingMessageSerevice
+    {
+        private static readonly List<string> needConfirmActions = new List<string>()
+        {
+             "GetConfiguration",
+             "ChangeConfiguration",
+             "RemoteStartTransaction",
+             "RemoteStopTransaction",
+             "ChangeAvailability",
+             "ClearCache",
+             "DataTransfer",
+             "Reset",
+             "UnlockConnector",
+             "TriggerMessage",
+             "GetDiagnostics",
+             "UpdateFirmware",
+             "GetLocalListVersion",
+             "SendLocalList",
+             "SetChargingProfile",
+             "ClearChargingProfile",
+             "GetCompositeSchedule",
+             "ReserveNow",
+             "CancelReservation",
+             "ExtendedTriggerMessage"
+        };
+
+        public static bool IsNeedConfirm(string action)
+        {
+            return needConfirmActions.Contains(action);
+        }
+
+        public ConfirmWaitingMessageSerevice(
+            IDbContextFactory<MainDBContext> maindbContextFactory
+            ,ILogger<ConfirmWaitingMessageSerevice> logger)
+        {
+            this.maindbContextFactory = maindbContextFactory;
+            this.logger = logger;
+        }
+
+        public event EventHandler<ConfirmWaitingMessageRemoveReason> OnMessageRemoved;
+
+        private readonly Object _lockConfirmPacketList = new object();
+        private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+        private readonly ILogger<ConfirmWaitingMessageSerevice> logger;
+        private readonly List<NeedConfirmMessage> needConfirmPacketList = new();
+        private readonly Dictionary<string, MessageResultWaitObject> asyncWaitingTasks = new();
+
+        internal async Task Add(string chargePointSerialNumber, int table_id, string requestId, string action, string msg_id, string createdBy, string sendMessage)
+        {
+            NeedConfirmMessage _needConfirmMsg = new NeedConfirmMessage();
+            _needConfirmMsg.Id = table_id;
+            _needConfirmMsg.SentAction = action;
+            _needConfirmMsg.SentOn = DateTime.UtcNow;
+            _needConfirmMsg.SentTimes = 4;
+            _needConfirmMsg.ChargePointSerialNumber = chargePointSerialNumber;
+            _needConfirmMsg.RequestId = requestId;
+            _needConfirmMsg.SentUniqueId = msg_id;
+            _needConfirmMsg.CreatedBy = createdBy;
+            _needConfirmMsg.SentMessage = sendMessage;
+
+            if (needConfirmActions.Contains(action))
+            {
+
+                lock (_lockConfirmPacketList)
+                {
+                    needConfirmPacketList.Add(_needConfirmMsg);
+                }
+            }
+
+            #region 更新資料表單一欄位
+            var dateTimeNow = DateTime.UtcNow;
+            var _UpdatedItem = new ServerMessage() { Id = table_id, UpdatedOn = dateTimeNow };
+            //db.Configuration.AutoDetectChangesEnabled = false;//自動呼叫DetectChanges()比對所有的entry集合的每一個屬性Properties的新舊值
+            //db.Configuration.ValidateOnSaveEnabled = false;// 因為Entity有些欄位必填,若不避開會有Validate錯誤
+            // var _UpdatedItem = db.ServerMessage.Where(x => x.Id == item.Id).FirstOrDefault();
+
+            using (var db = await maindbContextFactory.CreateDbContextAsync())
+            {
+                db.ServerMessage.Attach(_UpdatedItem);
+                _UpdatedItem.UpdatedOn = dateTimeNow;
+                db.Entry(_UpdatedItem).Property(x => x.UpdatedOn).IsModified = true;// 可以直接使用這方式強制某欄位要更新,只是查詢集合耗效能而己
+
+                await db.SaveChangesAsync();
+                db.ChangeTracker.Clear();
+            }
+
+            #endregion
+        }
+
+        internal List<NeedConfirmMessage> GetPendingMessages()
+        {
+            List<NeedConfirmMessage> sendMessages = new List<NeedConfirmMessage>();
+            lock (_lockConfirmPacketList)
+            {
+                sendMessages = needConfirmPacketList.Where(x => x.SentTimes > 1 && x.CreatedBy == "Server").ToList();
+            }
+
+            return sendMessages;
+        }
+
+        internal async Task<bool> TryConfirmMessage(MessageResult analysisResult)
+        {
+            bool confirmed = false;
+            //if (needConfirmActions.Contains(analysisResult.Action))
+            if (!IsNeedConfirm(analysisResult.Action))
+            {
+                return confirmed;
+            }
+
+            NeedConfirmMessage foundRequest = null;
+            lock (_lockConfirmPacketList)
+            {
+                foundRequest = needConfirmPacketList.Where(x => x.SentUniqueId == analysisResult.UUID).FirstOrDefault();
+            }
+
+            if (foundRequest != null && foundRequest.Id > 0)
+            {
+                RemoveWaitingMsg(foundRequest, ConfirmWaitingMessageRemoveReason.Confirmed, analysisResult);
+
+                foundRequest.SentTimes = 0;
+                foundRequest.SentInterval = 0;
+                analysisResult.RequestId = foundRequest.RequestId;
+
+                using (var db = await maindbContextFactory.CreateDbContextAsync())
+                {
+                    var sc = await db.ServerMessage.Where(x => x.Id == foundRequest.Id).FirstOrDefaultAsync();
+                    sc.InMessage = JsonConvert.SerializeObject(analysisResult.Message, Formatting.None);
+                    sc.ReceivedOn = DateTime.UtcNow;
+                    await db.SaveChangesAsync();
+                    //  Console.WriteLine(string.Format("Now:{0} ServerMessage Id:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), foundRequest.Id));
+
+                }
+                confirmed = true;
+
+
+
+            }
+            else if (analysisResult.Action == Actions.TriggerMessage.ToString())
+            {
+                confirmed = true;
+            }
+            else
+            {
+                logger.LogError(string.Format("Received no record Action:{0} MessageId:{1} ", analysisResult.Action, analysisResult.UUID));
+            }
+
+            return confirmed;
+        }
+
+        internal void SignalMessageSended(NeedConfirmMessage resendItem)
+        {
+            var dateTimeNow = DateTime.UtcNow;
+            resendItem.SentTimes--;
+            resendItem.SentOn = dateTimeNow;
+        }
+
+        internal void RemoveExpiredConfirmMessage()
+        {
+            var before10Mins = DateTime.UtcNow.AddMinutes(-10);
+            var removeList = needConfirmPacketList.Where(x => x.SentTimes == 0 || x.SentOn < before10Mins).ToList();
+
+            foreach (var item in removeList)
+            {
+                RemoveWaitingMsg(item, ConfirmWaitingMessageRemoveReason.HitRetryLimit);
+            }
+        }
+
+        internal async Task<MessageResult> WaitResultAsync(string msgId, int maxWaitSec = 65000)
+        {
+            var waiObj = new MessageResultWaitObject();
+            asyncWaitingTasks.Add(msgId, waiObj);
+            var task = waiObj.Lock.WaitAsync();
+            var completedTask = await Task.WhenAny(task, Task.Delay(650_000));
+            return waiObj.Result;
+        }
+
+        private void RemoveWaitingMsg(
+            NeedConfirmMessage msg, 
+            ConfirmWaitingMessageRemoveReason reason,
+            MessageResult response = null)
+        {
+            lock (_lockConfirmPacketList)
+            {
+                needConfirmPacketList.Remove(msg);
+            }
+
+            if (asyncWaitingTasks.Keys.Contains(msg.RequestId))
+            {
+                asyncWaitingTasks[msg.RequestId].Result = response;
+                asyncWaitingTasks[msg.RequestId].Lock.Release();
+            }
+            OnMessageRemoved?.Invoke(this, reason);
+        }
+    }
+
+    internal class MessageResultWaitObject
+    {
+        public MessageResult Result { get; set; } = null;
+        public SemaphoreSlim Lock { get; set; } = new SemaphoreSlim(0);
+    }
+}

+ 6 - 6
EVCB_OCPP.WSServer/Service/ConnectionLogdbService.cs → EVCB_OCPP.WSServer/Service/DbService/ConnectionLogdbService.cs

@@ -19,7 +19,7 @@ using System.Text;
 using System.Threading.Tasks;
 using static System.Runtime.InteropServices.JavaScript.JSType;
 
-namespace EVCB_OCPP.WSServer.Service;
+namespace EVCB_OCPP.WSServer.Service.DbService;
 
 public interface IConnectionLogdbService
 {
@@ -33,7 +33,7 @@ public class ConnectionLogdbService : IConnectionLogdbService
 
     public ConnectionLogdbService(
         IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory,
-		ISqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory,
+        ISqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory,
         ILogger<ConnectionLogdbService> logger,
         IConfiguration configuration)
     {
@@ -43,7 +43,7 @@ public class ConnectionLogdbService : IConnectionLogdbService
         //connectionLogdbConnectionString = configuration.GetConnectionString("MeterValueDBContext");
 
         var opLimit = GetLimit(configuration);
-        this.queueHandler = new(WriteMachineLogEF, opLimit);
+        queueHandler = new(WriteMachineLogEF, opLimit);
 
         InitInsertConnectonLogHandler();
     }
@@ -109,7 +109,7 @@ public class ConnectionLogdbService : IConnectionLogdbService
 
         var parameters = new DynamicParameters();
         parameters.Add("CreatedOn", workTime, DbType.DateTime);
-        parameters.Add("ChargeBoxId", log.clientData.ChargeBoxId == null ? "unknown" : log.clientData.ChargeBoxId.Replace("'", "''"), DbType.String, size:50); ;
+        parameters.Add("ChargeBoxId", log.clientData.ChargeBoxId == null ? "unknown" : log.clientData.ChargeBoxId.Replace("'", "''"), DbType.String, size: 50); ;
         parameters.Add("MessageType", log.messageType.Replace("'", "''"), DbType.String, size: 50);
         parameters.Add("Data", log.data.Replace("'", "''"), DbType.String);
         parameters.Add("Msg", log.errorMsg.Replace("'", "''"), DbType.String, size: 200);
@@ -197,7 +197,7 @@ public class ConnectionLogdbService : IConnectionLogdbService
 
         if (parmsList.Count == 0)
         {
-            return ;
+            return;
         }
 
         var candidate = parmsList[0];
@@ -254,7 +254,7 @@ public class ConnectionLogdbService : IConnectionLogdbService
             logger.LogWarning($"MachineLog Bundle Dapper {string.Join("/", times)} coint:{bundleHandlerData.Datas.Count()}");
         }
 
-        return ;
+        return;
     }
 
     private async Task BulkInsertWithBulkCopy(IEnumerable<MachineLog> parms)

+ 15 - 12
EVCB_OCPP.WSServer/Service/MainDbService.cs → EVCB_OCPP.WSServer/Service/DbService/MainDbService.cs

@@ -13,7 +13,7 @@ using Newtonsoft.Json;
 using OCPPPackage.Profiles;
 using System.Data;
 
-namespace EVCB_OCPP.WSServer.Service;
+namespace EVCB_OCPP.WSServer.Service.DbService;
 
 public interface IMainDbService
 {
@@ -28,8 +28,8 @@ public interface IMainDbService
     Task UpdateConnectorStatus(string Id, ConnectorStatus connectorStatus);
     ValueTask AddConnectorStatus(string ChargeBoxId, byte ConnectorId, DateTime CreatedOn, int Status,
         int ChargePointErrorCodeId, string ErrorInfo, string VendorId, string VendorErrorCode);
-    Task AddServerMessage(ServerMessage message);
-    Task AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy = "", DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "");
+    Task<string> AddServerMessage(ServerMessage message);
+    Task<string> AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy = "", DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "");
     ValueTask AddMachineError(byte ConnectorId, DateTime CreatedOn, int Status, string ChargeBoxId, int ErrorCodeId, string ErrorInfo, int PreStatus, string VendorErrorCode, string VendorId);
     ValueTask<Customer> GetCustomer(string id, CancellationToken token = default);
     ValueTask<Customer> GetCustomer(Guid id, CancellationToken token = default);
@@ -48,7 +48,7 @@ public class MainDbService : IMainDbService
 {
     public MainDbService(
         IDbContextFactory<MainDBContext> contextFactory,
-		ISqlConnectionFactory<MainDBContext> sqlConnectionFactory,
+        ISqlConnectionFactory<MainDBContext> sqlConnectionFactory,
         IMemoryCache memoryCache,
         IConfiguration configuration,
         ILoggerFactory loggerFactory,
@@ -61,10 +61,10 @@ public class MainDbService : IMainDbService
         this.logger = logger;
         var startupLimit = GetStartupLimit(configuration);
         //this.connectionString = configuration.GetConnectionString("MainDBContext");
-        this.startupSemaphore = new(startupLimit);
+        startupSemaphore = new(startupLimit);
 
         var opLimit = GetOpLimit(configuration);
-        this.opSemaphore = new SemaphoreSlim(opLimit);
+        opSemaphore = new SemaphoreSlim(opLimit);
 
         InitUpdateConnectorStatusHandler();
         InitUpdateMachineBasicInfoHandler();
@@ -119,7 +119,7 @@ public class MainDbService : IMainDbService
 
     public Task<string> GetMachineAuthorizationKey(string ChargeBoxId, CancellationToken token = default)
     {
-        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.AuthorizationKey , token);
+        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.AuthorizationKey, token);
     }
 
     public Task<string> GetMachineHeartbeatInterval(string ChargeBoxId)
@@ -213,7 +213,7 @@ public class MainDbService : IMainDbService
         return AddMachineErrorDapper(ConnectorId, CreatedOn, Status, ChargeBoxId, ErrorCodeId, ErrorInfo, PreStatus, VendorErrorCode, VendorId);
     }
 
-    public Task AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy, DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "")
+    public async Task<string> AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy, DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "")
     {
         if (string.IsNullOrEmpty(CreatedBy))
         {
@@ -249,14 +249,17 @@ public class MainDbService : IMainDbService
             InMessage = InMessage
         };
 
-        return AddServerMessage(data);
+        await AddServerMessage(data);
+        return SerialNo;
     }
 
-    public Task AddServerMessage(ServerMessage message)
+    public async Task<string> AddServerMessage(ServerMessage message)
     {
         //return AddServerMessageEF(message);
         //return addServerMessageHandler.HandleAsync(message);
-        return AddServerMessageDapper(message);
+        var id = message.SerialNo;
+        await AddServerMessageDapper(message);
+        return id;
     }
 
     public ValueTask<Customer> GetCustomer(string id, CancellationToken token = default)
@@ -972,6 +975,6 @@ public class MainDbService : IMainDbService
     }
 }
 
-public record MachineAndCustomerInfo (string MachineId, Guid CustomerId, string CustomerName);
+public record MachineAndCustomerInfo(string MachineId, Guid CustomerId, string CustomerName);
 public record StatusNotificationParam(string Id, ConnectorStatus Status);
 public record UpdateMachineBasicInfoParam(string ChargeBoxId, Machine machine);

+ 11 - 11
EVCB_OCPP.WSServer/Service/MeterValueDbService.cs → EVCB_OCPP.WSServer/Service/DbService/MeterValueDbService.cs

@@ -12,7 +12,7 @@ using System.Data;
 using System.Data.Common;
 using System.Diagnostics;
 
-namespace EVCB_OCPP.WSServer.Service;
+namespace EVCB_OCPP.WSServer.Service.DbService;
 
 public class MeterValueDbService
 {
@@ -27,7 +27,7 @@ public class MeterValueDbService
 
     public MeterValueDbService(
         IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
-		ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory,
+        ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory,
         ILogger<MeterValueDbService> logger,
         ILoggerFactory loggerFactory,
         IConfiguration configuration
@@ -114,7 +114,7 @@ public class MeterValueDbService
             BundleInsertWithDapper,
             //loggerFactory.CreateLogger("InsertMeterValueHandler")
             logger,
-            workerCnt:1
+            workerCnt: 1
             );
     }
 
@@ -136,7 +136,7 @@ public class MeterValueDbService
         List<SqlParameter> parameter = new List<SqlParameter>();
         parameter.AddInsertMeterValueRecordSqlParameters(
             chargeBoxId: param.chargeBoxId
-            , connectorId: (byte)param.connectorId
+            , connectorId: param.connectorId
             , value: param.value
             , createdOn: param.createdOn
             , contextId: param.contextId
@@ -175,7 +175,7 @@ public class MeterValueDbService
 
         watch.Stop();
         t1 = watch.ElapsedMilliseconds;
-        if(t1 > 700)
+        if (t1 > 700)
         {
             logger.LogWarning("MeterValue Dapper {0}/{1}", t0, t1);
         }
@@ -188,7 +188,7 @@ public class MeterValueDbService
 
     private async Task BundleInsertWithDapper(BundleHandlerData<InsertMeterValueParam> bundleHandlerData)
     {
-        List<InsertMeterValueParam> completedParams = new ();
+        List<InsertMeterValueParam> completedParams = new();
         var watch = Stopwatch.StartNew();
         long t0, t1, t2, t3, t4;
 
@@ -217,7 +217,7 @@ public class MeterValueDbService
         foreach (var group in gruopParams)
         {
             var tableName = group.Key;
-            foreach(var param in group)
+            foreach (var param in group)
             {
                 await InsertWithNoCheckDapper(tableName, param, sqlConnection
                     //, trans
@@ -233,7 +233,7 @@ public class MeterValueDbService
         t4 = watch.ElapsedMilliseconds;
         if (t4 > 500)
         {
-            logger.LogWarning("MeterValue Dapper {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4,  bundleHandlerData.CompletedDatas.Count());
+            logger.LogWarning("MeterValue Dapper {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, bundleHandlerData.CompletedDatas.Count());
         }
     }
 
@@ -345,7 +345,7 @@ public class MeterValueDbService
 
         if (t4 > 500)
         {
-            logger.LogWarning("MeterValue BulkInsertWithBulkCopy Slow {0}/{1}/{2}/{3}/{4}/{5}", t0,t1,t2,t3,t4, parms.Count());
+            logger.LogWarning("MeterValue BulkInsertWithBulkCopy Slow {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, parms.Count());
         }
     }
 
@@ -385,7 +385,7 @@ public class MeterValueDbService
         List<SqlParameter> parameter = new List<SqlParameter>();
         parameter.AddInsertMeterValueRecordSqlParameters(
             chargeBoxId: param.chargeBoxId
-            , connectorId: (byte)param.connectorId
+            , connectorId: param.connectorId
             , value: param.value
             , createdOn: param.createdOn
             , contextId: param.contextId
@@ -399,7 +399,7 @@ public class MeterValueDbService
         await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
     }
 
-    private static string GetTableName(DateTime dateTime) 
+    private static string GetTableName(DateTime dateTime)
         => $"ConnectorMeterValueRecord{dateTime:yyMMdd}";
 
     private int GetInsertLimit(IConfiguration configuration)

+ 157 - 0
EVCB_OCPP.WSServer/Service/DbService/WebDbService.cs

@@ -0,0 +1,157 @@
+using Dapper;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.WSServer.Dto;
+using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service.DbService;
+
+public class WebDbService
+{
+    private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
+    private readonly ILogger<WebDbService> logger;
+
+    public WebDbService(ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory, ILogger<WebDbService> logger)
+    {
+        this.webDbConnectionFactory = webDbConnectionFactory;
+        this.logger = logger;
+        //this.webConnectionString = configuration.GetConnectionString("WebDBContext");
+    }
+
+    //private readonly string webConnectionString;
+
+    public async Task<List<string>> GetDenyModelNames(CancellationToken token = default)
+    {
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        string strSql = """
+                SELECT [Value] 
+                FROM [dbo].[KernelConfig]
+                where SystemKey = 'DenyModelNames';
+                """;
+
+        var result = await conn.QueryFirstOrDefaultAsync<string>(
+            new CommandDefinition(strSql, cancellationToken: token)
+            );
+
+        return result.Split(',').ToList();
+
+    }
+
+    async public Task<string> SetDefaultFee(WsClientData client)
+    {
+        string displayPriceText = string.Empty;
+        string charingPriceText = string.Empty;
+
+        if (string.IsNullOrEmpty(client.ChargeBoxId)) return displayPriceText;
+
+        try
+        {
+            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@MachineId", client.MachineId, DbType.String, ParameterDirection.Input, 36);
+                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;
+            }
+        }
+        catch (Exception ex)
+        {
+            logger.LogError("SetDefaultFee", ex.ToString());
+        }
+
+        return displayPriceText;
+    }
+
+    internal async Task<List<KeyValuePair<string, string>>> GetCustomerStationEvseConfig(string chargeBoxId)
+    {
+        string getStationStrSql = """
+                SELECT [StationId] 
+                FROM [dbo].[StationMachine]
+                where [ChargeBoxId] = @ChargeBoxId;
+                """;
+        string getConfigStrSql = """
+                SELECT [ConfigureName],[ConfigureSetting]
+                FROM [dbo].[StationMachineConfig]
+                where [StationId] = @StationId;
+                """;
+
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        DynamicParameters parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 25);
+        int stationId = await conn.QueryFirstOrDefaultAsync<int>(getStationStrSql, parameters);
+        if (stationId == default)
+        {
+            return null;
+        }
+        parameters = new DynamicParameters();
+        parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
+        var configs = await conn.QueryAsync<StationMachineConfig>(getConfigStrSql, parameters);
+        return configs.Select(x => new KeyValuePair<string, string>(x.ConfigureName, x.ConfigureSetting)).ToList();
+    }
+}

+ 1 - 0
EVCB_OCPP.WSServer/Service/MeterValueInsertHandler.cs

@@ -2,6 +2,7 @@
 using EVCB_OCPP.Domain;
 using EVCB_OCPP.Domain.ConnectionFactory;
 using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Service.DbService;
 using Microsoft.Data.SqlClient;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Configuration;

+ 1 - 0
EVCB_OCPP.WSServer/Service/OuterBusinessService.cs

@@ -2,6 +2,7 @@
 
 using EVCB_OCPP.Packet.Messages.SubTypes;
 using EVCB_OCPP.WSServer.Dto;
+using EVCB_OCPP.WSServer.Service.DbService;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Logging;
 using Newtonsoft.Json;

+ 7 - 4
EVCB_OCPP.WSServer/Service/ServerMessageService.cs

@@ -1,6 +1,9 @@
 using EVCB_OCPP.Packet.Features;
 using EVCB_OCPP.Packet.Messages.Core;
 using Microsoft.Extensions.Logging;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+using System.ServiceModel.Channels;
+using EVCB_OCPP.WSServer.Service.DbService;
 
 namespace EVCB_OCPP.WSServer.Service;
 
@@ -17,17 +20,17 @@ public class ServerMessageService
     private readonly IMainDbService mainDbService;
     private readonly ILogger<ServerMessageService> logger;
 
-    internal async Task SendGetEVSEConfigureRequest(string chargeBoxId)
+    internal Task<string> SendGetEVSEConfigureRequest(string chargeBoxId)
     {
-        if (string.IsNullOrEmpty(chargeBoxId)) return;
-        await mainDbService.AddServerMessage(
+        if (string.IsNullOrEmpty(chargeBoxId)) return null;
+        return mainDbService.AddServerMessage(
             ChargeBoxId: chargeBoxId,
             OutAction: Actions.GetConfiguration.ToString(),
             OutRequest: new GetConfigurationRequest() { key = new List<string>() }
             );
     }
 
-    internal Task SendChangeConfigurationRequest(string chargeBoxId, string key, string value)
+    internal Task<string> SendChangeConfigurationRequest(string chargeBoxId, string key, string value)
     {
         return mainDbService.AddServerMessage(
             ChargeBoxId: chargeBoxId,

+ 0 - 42
EVCB_OCPP.WSServer/Service/WebDbService.cs

@@ -1,42 +0,0 @@
-using Dapper;
-using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.WSServer.Helper;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Service;
-
-public class WebDbService
-{
-    private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
-
-    public WebDbService(ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory)
-    {
-        this.webDbConnectionFactory = webDbConnectionFactory;
-        //this.webConnectionString = configuration.GetConnectionString("WebDBContext");
-    }
-
-    //private readonly string webConnectionString;
-
-    public async Task<List<string>> GetDenyModelNames(CancellationToken token = default)
-    {
-        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
-        string strSql = """
-                SELECT [Value] 
-                FROM [dbo].[KernelConfig]
-                where SystemKey = 'DenyModelNames';
-                """;
-
-        var result = await conn.QueryFirstOrDefaultAsync<string>(
-            new CommandDefinition(strSql, cancellationToken: token)
-            );
-
-        return result.Split(',').ToList();
-
-    }
-}

+ 1 - 0
EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs

@@ -1,4 +1,5 @@
 using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Service.DbService;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Configuration;

+ 19 - 1
EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs

@@ -6,6 +6,14 @@ using Microsoft.Extensions.Logging;
 
 namespace EVCB_OCPP.WSServer.Service.WsService;
 
+public enum BootStatus
+{
+    Startup = 0,
+    Initializing,
+    Pending,
+    Accepted
+}
+
 public class WsClientData : WsSession
 { /// <summary>
   /// 根據unique id來儲存.取出OCPP Request
@@ -14,7 +22,8 @@ public class WsClientData : WsSession
 
     public EVCB_OCPP20.Packet.Messages.Basic.Queue queue20 = new EVCB_OCPP20.Packet.Messages.Basic.Queue();
 
-    public bool? IsPending { set; get; }
+    public BootStatus BootStatus { get; set; } = BootStatus.Startup;
+    //public bool? IsPending { set; get; }
     public bool IsCheckIn { set; get; } = false;
 
     public string ChargeBoxId { set; get; }
@@ -79,6 +88,7 @@ public class WsClientData : WsSession
 
     private string stringBuffer = string.Empty;
     private readonly ILogger<WsClientData> logger;
+    private List<Task> AttachedTasks = new List<Task>();
 
     public WsClientData(ILogger<WsClientData> logger) : base(logger)
     {
@@ -86,6 +96,14 @@ public class WsClientData : WsSession
         MachineId = SessionID;
         this.logger = logger;
     }
+    
+    public void AddTask(Task toAddTask)
+    {
+        AttachedTasks.Add(toAddTask);
+        toAddTask.ContinueWith(t => {
+            AttachedTasks.Remove(toAddTask);
+        });
+    }
 
     internal override void HandleReceivedData(string data)
     {