浏览代码

optimize status notification

Robert 1 年之前
父节点
当前提交
5889bb291a

+ 3 - 22
EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj

@@ -1,25 +1,8 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
     <OutputType>Exe</OutputType>
-    <IsWebBootstrapper>false</IsWebBootstrapper>
-    <PublishUrl>publish\</PublishUrl>
-    <Install>true</Install>
-    <InstallFrom>Disk</InstallFrom>
-    <UpdateEnabled>false</UpdateEnabled>
-    <UpdateMode>Foreground</UpdateMode>
-    <UpdateInterval>7</UpdateInterval>
-    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
-    <UpdatePeriodically>false</UpdatePeriodically>
-    <UpdateRequired>false</UpdateRequired>
-    <MapFileExtensions>true</MapFileExtensions>
-    <ApplicationRevision>0</ApplicationRevision>
-    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
-    <UseApplicationTrust>false</UseApplicationTrust>
-    <BootstrapperEnabled>true</BootstrapperEnabled>
-    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
-    <DockerfileRunArguments>-p 54088:54088 -p 80:80</DockerfileRunArguments>
-    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
     <UserSecretsId>88f57ec2-60b9-4291-bba3-3c0d312fe6dc</UserSecretsId>
   </PropertyGroup>
   <ItemGroup>
@@ -67,13 +50,11 @@
     <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="Polly" Version="7.2.3" />
     <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" />
-    <PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
-      <PrivateAssets>all</PrivateAssets>
-    </PackageReference>
     <PackageReference Include="EntityFramework" Version="6.4.4" />
     <PackageReference Include="log4net" Version="2.0.15" />
   </ItemGroup>

+ 64 - 0
EVCB_OCPP.WSServer/Helper/AddPortalDbContext.cs

@@ -0,0 +1,64 @@
+using EVCB_OCPP.Domain;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Helper;
+
+public static class AddPortalDbContext
+{
+    public const string CommandTimeoutKey = "CommandTimeout";
+    public static IServiceCollection AddMainDbContext(this IServiceCollection services, IConfiguration configuration)
+    { 
+        const string DbUserIdKey = "MainDbUserIdKey";
+        const string DbPassKey = "MainDbPass";
+        const string DbConnectionStringKey = "MainDBContext";
+
+        AddPortalDbContextInternal<MainDBContext>(services,configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
+        return services;
+    }
+
+    public static IServiceCollection AddMeterValueDbContext(this IServiceCollection services, IConfiguration configuration)
+    {
+        const string DbUserIdKey = "MeterValueDbUserId";
+        const string DbPassKey = "MeterValueDbPass";
+        const string DbConnectionStringKey = "MeterValueDBContext";
+
+        AddPortalDbContextInternal<MeterValueDBContext>(services, configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
+        return services;
+    }
+
+    public static IServiceCollection AddConnectionLogDbContext(this IServiceCollection services, IConfiguration configuration)
+    {
+        const string DbUserIdKey = "ConnectionLogDbUserId";
+        const string DbPassKey = "ConnectionLogDbPass";
+        const string DbConnectionStringKey = "ConnectionLogDBContext";
+
+        AddPortalDbContextInternal<ConnectionLogDBContext>(services, configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
+        return services;
+    }
+
+    private static void AddPortalDbContextInternal<T>(
+        IServiceCollection services, IConfiguration configuration,
+        string UserIdKey,string DbPassKey, string ConnectionStringKey) where T : DbContext
+    {
+
+        var commandTimeout = int.TryParse(configuration[CommandTimeoutKey], out var temp) ? temp : 180;
+        string mainDbUserId = string.IsNullOrEmpty(configuration[UserIdKey]) ? string.Empty : $"user id={configuration[UserIdKey]};";
+        string mainDbUserPass = string.IsNullOrEmpty(configuration[DbPassKey]) ? string.Empty : $"password={configuration[DbPassKey]};";
+
+        services.AddPooledDbContextFactory<T>((options) => {
+            var cString = configuration.GetConnectionString(ConnectionStringKey);
+            cString = $"{cString}{mainDbUserId}{mainDbUserPass}";
+            options.UseSqlServer(cString, dbOptions =>
+            {
+                dbOptions.CommandTimeout(commandTimeout);
+            });
+        });
+    }
+}

+ 83 - 0
EVCB_OCPP.WSServer/Helper/GroupSingleHandler.cs

@@ -0,0 +1,83 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Helper;
+
+public class GroupSingleHandler<T>
+{
+    private readonly Func<IEnumerable<T>, Task> handleFunc;
+    private readonly ILogger logger;
+    private readonly ConcurrentQueue<(T param, SemaphoreSlim waitLock)> waitList = new();
+    //private readonly Dictionary<T, SemaphoreSlim> reqLockPaisrs = new();
+    private readonly SemaphoreSlim singleWorkLock = new SemaphoreSlim(1);
+    private Task singleHandleTask;
+
+    public GroupSingleHandler(Func<IEnumerable<T>, Task> handleFunc, ILogger logger)
+    {
+        this.handleFunc = handleFunc;
+        this.logger = logger;
+    }
+
+    public Task HandleAsync(T param)
+    {
+        SemaphoreSlim reqLock = new(0);
+        waitList.Enqueue((param, reqLock));
+        TryStartHandler();
+        return reqLock.WaitAsync();
+    }
+
+    private void TryStartHandler()
+    {
+        if (!singleWorkLock.Wait(0))
+        {
+            return;
+        }
+
+        if (waitList.Count == 0)
+        {
+            singleWorkLock.Release();
+            return;
+        }
+
+        singleHandleTask = StartHandleTask();
+    }
+
+    private async Task StartHandleTask()
+    {
+        var handleList = new List<(T param, SemaphoreSlim waitLock)>();
+
+        while (waitList.TryDequeue(out var handle))
+        {
+            handleList.Add(handle);
+        }
+
+        int cnt = 0;
+        do
+        {
+            cnt++;
+            try
+            {
+                var task = handleFunc(handleList.Select(x => x.param));
+                await task;
+                break;
+            }
+            catch (Exception e)
+            {
+                logger.LogError(e, "Trying Cnt {0}", cnt);
+            }
+        }
+        while (true);
+
+        foreach (var handled in handleList)
+        {
+            handled.waitLock.Release();
+        }
+        singleWorkLock.Release();
+        TryStartHandler();
+    }
+}

+ 16 - 2
EVCB_OCPP.WSServer/HostedProtalServer.cs

@@ -1,7 +1,11 @@
-using EVCB_OCPP.WSServer.Jobs;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Jobs;
 using EVCB_OCPP.WSServer.Message;
 using EVCB_OCPP.WSServer.Service;
 using EVCB_OCPP.WSServer.SuperSocket;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Quartz;
 using System;
@@ -14,13 +18,15 @@ namespace EVCB_OCPP.WSServer
 {
     public static class HostedProtalServer
     {
-        public static void AddProtalServer(this IServiceCollection services)
+        public static void AddProtalServer(this IServiceCollection services, IConfiguration configuration)
         {
+            services.AddPortalServerDatabase(configuration);
             services.AddBusinessServiceFactory();
 
             services.AddTransient<OCPPWSServer>();
             services.AddTransient<IOCPPWSServerFactory, OCPPWSServerFactory>();
 
+            services.AddSingleton<MeterValueDbService>();
             services.AddSingleton<WebDbService>();
             services.AddSingleton<IMainDbService, MainDbService>();
             services.AddSingleton<IConnectionLogdbService, ConnectionLogdbService>();
@@ -32,6 +38,14 @@ namespace EVCB_OCPP.WSServer
             services.AddProtalServerJob();
         }
 
+        internal static void AddPortalServerDatabase(this IServiceCollection services, IConfiguration configuration)
+        {
+            services
+                .AddMainDbContext(configuration)
+                .AddMeterValueDbContext(configuration)
+                .AddConnectionLogDbContext(configuration);
+        }
+
         public static void AddProtalServerJob(this IServiceCollection services)
         {
             services.AddQuartz(q => {

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

@@ -43,8 +43,8 @@ public class HeartBeatCheckJob : IJob
 
             watch.Start();
 
-            using (var db = maindbContextFactory.CreateDbContext())
-            using (var transaction = db.Database.BeginTransaction())
+            using (var db = await maindbContextFactory.CreateDbContextAsync())
+            using (var transaction = await db.Database.BeginTransactionAsync())
             {
                 try
                 {
@@ -70,6 +70,7 @@ public class HeartBeatCheckJob : IJob
                 }
                 catch (Exception ex)
                 {
+                    logger.LogCritical(ex, "HeartBeatCheckTrigger update fail, roll back");
                     transaction.Rollback();
                 }
             }

+ 93 - 127
EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs

@@ -75,7 +75,9 @@ internal partial class ProfileHandler
     private readonly ILogger logger;
     private readonly string webConnectionString;// = ConfigurationManager.ConnectionStrings[].ConnectionString;
     private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
-    private readonly IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory;
+    private readonly MeterValueDbService meterValueDbService;
+
+    //private readonly IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory;
     private readonly IBusinessServiceFactory businessServiceFactory;
     private readonly IMainDbService mainDbService;
     private OuterHttpClient httpClient = new OuterHttpClient();
@@ -83,7 +85,8 @@ internal partial class ProfileHandler
     public ProfileHandler(
         IConfiguration configuration,
         IDbContextFactory<MainDBContext> maindbContextFactory, 
-        IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory,
+        //IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory,
+        MeterValueDbService meterValueDbService,
         IBusinessServiceFactory businessServiceFactory,
         IMainDbService mainDbService,
         ILogger<ProfileHandler> logger)
@@ -92,8 +95,9 @@ internal partial class ProfileHandler
 
         this.logger = logger;
         this.maindbContextFactory = maindbContextFactory;
+        this.meterValueDbService = meterValueDbService;
         this.mainDbService = mainDbService;
-        this.metervaluedbContextFactory = metervaluedbContextFactory;
+        //this.metervaluedbContextFactory = metervaluedbContextFactory;
         this.businessServiceFactory = businessServiceFactory;
     }
 
@@ -366,70 +370,64 @@ internal partial class ProfileHandler
                         if (_request.meterValue.Count > 0)
                         {
 
-
-                            using (var db = await metervaluedbContextFactory.CreateDbContextAsync())
+                            foreach (var item in _request.meterValue)
                             {
-                                foreach (var item in _request.meterValue)
+                                if (_request.transactionId.HasValue)
                                 {
-                                    if (_request.transactionId.HasValue)
-                                    {
-                                        decimal meterStart = 0;
-                                        var energy_Register = item.sampledValue.Where(x => x.measurand == Measurand.Energy_Active_Import_Register).FirstOrDefault();
-
-                                        if (energy_Register != null)
-                                        {
-                                            decimal energyRegister = decimal.Parse(energy_Register.value);
-                                            energyRegister = energy_Register.unit.Value == UnitOfMeasure.kWh ? decimal.Multiply(energyRegister, 1000) : energyRegister;
+                                    decimal meterStart = 0;
+                                    var energy_Register = item.sampledValue.Where(x => x.measurand == Measurand.Energy_Active_Import_Register).FirstOrDefault();
 
+                                    if (energy_Register != null)
+                                    {
+                                        decimal energyRegister = decimal.Parse(energy_Register.value);
+                                        energyRegister = energy_Register.unit.Value == UnitOfMeasure.kWh ? decimal.Multiply(energyRegister, 1000) : energyRegister;
 
-                                            using (var maindb = maindbContextFactory.CreateDbContext())
-                                            {
-                                                meterStart = await maindb.TransactionRecord
-                                                    .Where(x => x.Id == _request.transactionId.Value).Select(x => x.MeterStart)
-                                                    .FirstOrDefaultAsync();
-                                            }
 
-                                            item.sampledValue.Add(new SampledValue()
-                                            {
-                                                context = ReadingContext.Sample_Periodic,
-                                                format = ValueFormat.Raw,
-                                                location = Location.Outlet,
-                                                phase = item.sampledValue.Where(x => x.measurand == Measurand.Energy_Active_Import_Register).Select(x => x.phase).FirstOrDefault(),
-                                                unit = UnitOfMeasure.Wh,
-                                                measurand = Measurand.TotalEnergy,
-                                                value = decimal.Subtract(energyRegister, meterStart).ToString()
-                                            });
+                                        using (var maindb = maindbContextFactory.CreateDbContext())
+                                        {
+                                            meterStart = await maindb.TransactionRecord
+                                                .Where(x => x.Id == _request.transactionId.Value).Select(x => x.MeterStart)
+                                                .FirstOrDefaultAsync();
                                         }
 
+                                        item.sampledValue.Add(new SampledValue()
+                                        {
+                                            context = ReadingContext.Sample_Periodic,
+                                            format = ValueFormat.Raw,
+                                            location = Location.Outlet,
+                                            phase = item.sampledValue.Where(x => x.measurand == Measurand.Energy_Active_Import_Register).Select(x => x.phase).FirstOrDefault(),
+                                            unit = UnitOfMeasure.Wh,
+                                            measurand = Measurand.TotalEnergy,
+                                            value = decimal.Subtract(energyRegister, meterStart).ToString()
+                                        });
                                     }
 
-                                    foreach (var sampleVaule in item.sampledValue)
-                                    {
-
-                                        decimal value = Convert.ToDecimal(sampleVaule.value);
-
-                                        string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId," +
-                     "@ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
+                                }
 
-                                        List<SqlParameter> parameter = new List<SqlParameter>();
-                                        parameter.AddInsertMeterValueRecordSqlParameters(
-                                            chargeBoxId: session.ChargeBoxId
-                                            ,connectorId: (byte)_request.connectorId
-                                            ,value: value
-                                            ,createdOn: item.timestamp
-                                            ,contextId: sampleVaule.context.HasValue ? (int)sampleVaule.context : 0
-                                            ,formatId: sampleVaule.format.HasValue ? (int)sampleVaule.format : 0
-                                            ,measurandId: sampleVaule.measurand.HasValue ? (int)sampleVaule.measurand : 0
-                                            ,phaseId: sampleVaule.phase.HasValue ? (int)sampleVaule.phase : 0
-                                            ,locationId: sampleVaule.location.HasValue ? (int)sampleVaule.location : 0
-                                            ,unitId: sampleVaule.unit.HasValue ? (int)sampleVaule.unit : 0
-                                            ,transactionId: _request.transactionId.HasValue ? _request.transactionId.Value : -1);
-
-                                        await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
-                                    }
+                            }
 
+                            List<Task> insertTasks = new();
+                            foreach (var item in _request.meterValue)
+                            {
+                                foreach (var sampleVaule in item.sampledValue)
+                                {
+                                    decimal value = Convert.ToDecimal(sampleVaule.value);
+                                    var task = meterValueDbService.InsertAsync(
+                                        chargeBoxId: session.ChargeBoxId
+                                        , connectorId: (byte)_request.connectorId
+                                        , value: value
+                                        , createdOn: item.timestamp
+                                        , contextId: sampleVaule.context.HasValue ? (int)sampleVaule.context : 0
+                                        , formatId: sampleVaule.format.HasValue ? (int)sampleVaule.format : 0
+                                        , measurandId: sampleVaule.measurand.HasValue ? (int)sampleVaule.measurand : 0
+                                        , phaseId: sampleVaule.phase.HasValue ? (int)sampleVaule.phase : 0
+                                        , locationId: sampleVaule.location.HasValue ? (int)sampleVaule.location : 0
+                                        , unitId: sampleVaule.unit.HasValue ? (int)sampleVaule.unit : 0
+                                        , transactionId: _request.transactionId.HasValue ? _request.transactionId.Value : -1);
+                                    insertTasks.Add(task);
                                 }
                             }
+                            await Task.WhenAll(insertTasks);
                         }
 
                         //  if (energy_kwh > 0)
@@ -739,20 +737,14 @@ internal partial class ProfileHandler
                             if (_request.transactionData != null &&
                                 _request.transactionData.Count > 0)
                             {
-                                using (var _meterDb = await metervaluedbContextFactory.CreateDbContextAsync())
+                                List<Task> insertTasks = new();
+                                foreach (var item in _request.transactionData)
                                 {
-                                    foreach (var item in _request.transactionData)
-                                        foreach (var sampleVaule in item.sampledValue)
-                                        {
-                                            decimal value = Convert.ToDecimal(sampleVaule.value);
-
-
-                                            string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId," +
-                            "@ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
-
-                                            List<SqlParameter> parameter = new List<SqlParameter>();
-                                            parameter.AddInsertMeterValueRecordSqlParameters(
-                                                chargeBoxId: session.ChargeBoxId
+                                    foreach (var sampleVaule in item.sampledValue)
+                                    {
+                                        decimal value = Convert.ToDecimal(sampleVaule.value);
+                                        var task = meterValueDbService.InsertAsync(
+                                            chargeBoxId: session.ChargeBoxId
                                                 , connectorId: (byte)_ConnectorId
                                                 , value: value
                                                 , createdOn: item.timestamp
@@ -762,15 +754,11 @@ internal partial class ProfileHandler
                                                 , phaseId: sampleVaule.phase.HasValue ? (int)sampleVaule.phase : 0
                                                 , locationId: sampleVaule.location.HasValue ? (int)sampleVaule.location : 0
                                                 , unitId: sampleVaule.unit.HasValue ? (int)sampleVaule.unit : 0
-                                                , transactionId: _request.transactionId
-                                                );
-
-                                            _meterDb.Database.ExecuteSqlRaw(sp, parameter.ToArray());
-                                        }
-
+                                                , transactionId: _request.transactionId);
+                                        insertTasks.Add(task);
+                                    }
                                 }
-
-
+                                await Task.WhenAll(insertTasks);
                             }
 
                             #endregion
@@ -866,11 +854,11 @@ internal partial class ProfileHandler
 
         //if (action == Actions.Heartbeat)
         //{
-            watch.Stop();
-            if (watch.ElapsedMilliseconds / 1000 > 3)
-            {
-            logger.LogError("Processing " + action.ToString() + " costs " + watch.ElapsedMilliseconds / 1000 + " seconds"); ;
-            }
+        watch.Stop();
+        if (watch.ElapsedMilliseconds / 1000 > 3)
+        {
+        logger.LogError("Processing " + action.ToString() + " costs " + watch.ElapsedMilliseconds / 1000 + " seconds"); ;
+        }
         //}
         return result;
     }
@@ -1178,28 +1166,18 @@ internal partial class ProfileHandler
 
                                         db.SaveChanges();
 
-                                        using (var meterdb = await metervaluedbContextFactory.CreateDbContextAsync())
-                                        {
-                                            string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId," +
-                               "@ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
-
-                                            List<SqlParameter> parameter = new List<SqlParameter>();
-                                            parameter.AddInsertMeterValueRecordSqlParameters(
-                                                chargeBoxId: session.ChargeBoxId,
-                                                connectorId: feedto.ConnectorId,
-                                                value: chargingCost,
-                                                createdOn: DateTime.UtcNow,
-                                                contextId: (int)ReadingContext.Sample_Periodic,
-                                                formatId: (int)ValueFormat.Raw,
-                                                measurandId: (int)Measurand.TotalCost,
-                                                phaseId: -1,
-                                                locationId: -1,
-                                                unitId: -1,
-                                                transactionId: feedto.Id
-                                                );
-
-                                            meterdb.Database.ExecuteSqlRaw(sp, parameter.ToArray());
-                                        }
+                                        await meterValueDbService.InsertAsync(
+                                            chargeBoxId: session.ChargeBoxId,
+                                            connectorId: feedto.ConnectorId,
+                                            value: chargingCost,
+                                            createdOn: DateTime.UtcNow,
+                                            contextId: (int)ReadingContext.Sample_Periodic,
+                                            formatId: (int)ValueFormat.Raw,
+                                            measurandId: (int)Measurand.TotalCost,
+                                            phaseId: -1,
+                                            locationId: -1,
+                                            unitId: -1,
+                                            transactionId: feedto.Id);
 
                                         using (SqlConnection conn = new SqlConnection(webConnectionString))
                                         {
@@ -1263,32 +1241,20 @@ internal partial class ProfileHandler
 
                                         db.SaveChanges();
 
-                                        using (var meterdb = await metervaluedbContextFactory.CreateDbContextAsync())
-                                        {
-                                            string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId," +
-                   "@ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
-
-                                            List<SqlParameter> parameter = new List<SqlParameter>();
-                                            parameter.AddInsertMeterValueRecordSqlParameters(
-                                                chargeBoxId: session.ChargeBoxId,
-                                                connectorId: (byte)feedto.ConnectorId,
-                                                value: chargingCost,
-                                                createdOn: DateTime.UtcNow,
-                                                contextId: (int)ReadingContext.Sample_Periodic,
-                                                formatId: (int)ValueFormat.Raw,
-                                                measurandId: (int)Measurand.ChargingCost,
-                                                phaseId: -1,
-                                                locationId: -1,
-                                                unitId: -1,
-                                                transactionId: feedto.Id
-                                                );
-
-                                            meterdb.Database.ExecuteSqlRaw(sp, parameter.ToArray());
-                                        }
-
+                                        await meterValueDbService.InsertAsync(
+                                            chargeBoxId: session.ChargeBoxId,
+                                            connectorId: (byte)feedto.ConnectorId,
+                                            value: chargingCost,
+                                            createdOn: DateTime.UtcNow,
+                                            contextId: (int)ReadingContext.Sample_Periodic,
+                                            formatId: (int)ValueFormat.Raw,
+                                            measurandId: (int)Measurand.ChargingCost,
+                                            phaseId: -1,
+                                            locationId: -1,
+                                            unitId: -1,
+                                            transactionId: feedto.Id
+                                            );
                                     }
-
-
                                 }
 
                             }

+ 4 - 35
EVCB_OCPP.WSServer/Program.cs

@@ -23,7 +23,8 @@ namespace EVCB_OCPP.WSServer
         static void Main(string[] args)
         {
             Console.WriteLine("====================================================================================================");
-            Console.WriteLine("====================================================================================================");
+            Console.WriteLine("=================" +
+                "===================================================================================");
             Console.WriteLine("==                                                                                                ==");
             Console.WriteLine("==       ------------               -----------      -------------         -------------          ==");
             Console.WriteLine("==    ---            ---       ----                  ----------------      ----------------       ==");
@@ -38,8 +39,8 @@ namespace EVCB_OCPP.WSServer
             Console.WriteLine("====================================================================================================");
             Console.WriteLine("====================================================================================================");
 
-
             IHost host = Host.CreateDefaultBuilder(args)
+                //.UseEnvironment("Development")
                 .ConfigureLogging((context, builder) => { 
                     builder.ClearProviders();
                     NLog.LogManager.Configuration = new NLogLoggingConfiguration(context.Configuration.GetSection("NLog"));
@@ -47,43 +48,11 @@ namespace EVCB_OCPP.WSServer
                 .UseNLog()
                 .ConfigureServices((hostContext, services) =>
                 {
-                    var commandTimeout = int.TryParse(hostContext.Configuration["CommandTimeout"], out var temp) ? temp : 180;
-                    var maxRetryCount = int.TryParse(hostContext.Configuration["MaxRetryCount"], out var temp2) ? temp2 : int.MaxValue;
-
-                    services.AddPooledDbContextFactory<MainDBContext>((options) => {
-                        var cString = hostContext.Configuration.GetConnectionString("MainDBContext");
-                        options.UseSqlServer(cString, dbOptions =>
-                        {
-                            //dbOptions.EnableRetryOnFailure(maxRetryCount);
-                            dbOptions.CommandTimeout(commandTimeout);
-                        });
-                    });
-                    services.AddPooledDbContextFactory<MeterValueDBContext>((options) => {
-                        var cString = hostContext.Configuration.GetConnectionString("MeterValueDBContext");
-                        options.UseSqlServer(cString, dbOptions => {
-                            //dbOptions.EnableRetryOnFailure(maxRetryCount);
-                            dbOptions.CommandTimeout(commandTimeout);
-                        });
-                    });
-                    services.AddPooledDbContextFactory<ConnectionLogDBContext>((options) => {
-                        var cString = hostContext.Configuration.GetConnectionString("ConnectionLogDBContext");
-                        options.UseSqlServer(cString, dbOptions => {
-                            //dbOptions.EnableRetryOnFailure(maxRetryCount);
-                            dbOptions.CommandTimeout(commandTimeout);
-                        });
-                    });
-
-                    services.AddProtalServer();
+                    services.AddProtalServer(hostContext.Configuration);
                 })
                 .Build();
 
             host.Run();
-
-            //ProtalServer s = new ProtalServer();
-            //Console.WriteLine("Starting Server...");
-            //s.Start();
-
-            //Console.Read();
         }
 
         public static object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)

+ 30 - 30
EVCB_OCPP.WSServer/Properties/AssemblyInfo.cs

@@ -2,37 +2,37 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
-// 組件的一般資訊是由下列的屬性集控制。
-// 變更這些屬性的值即可修改組件的相關
-// 資訊。
-[assembly: AssemblyTitle("EVCB_OCPP.WSServer")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("EVCB_OCPP.WSServer")]
-[assembly: AssemblyCopyright("Copyright ©  2019")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
+//// 組件的一般資訊是由下列的屬性集控制。
+//// 變更這些屬性的值即可修改組件的相關
+//// 資訊。
+//[assembly: AssemblyTitle("EVCB_OCPP.WSServer")]
+//[assembly: AssemblyDescription("")]
+//[assembly: AssemblyConfiguration("")]
+//[assembly: AssemblyCompany("")]
+//[assembly: AssemblyProduct("EVCB_OCPP.WSServer")]
+//[assembly: AssemblyCopyright("Copyright ©  2019")]
+//[assembly: AssemblyTrademark("")]
+//[assembly: AssemblyCulture("")]
 
-// 將 ComVisible 設為 false 可對 COM 元件隱藏
-// 組件中的類型。若必須從 COM 存取此組件中的類型,
-// 的類型,請在該類型上將 ComVisible 屬性設定為 true。
-[assembly: ComVisible(false)]
+//// 將 ComVisible 設為 false 可對 COM 元件隱藏
+//// 組件中的類型。若必須從 COM 存取此組件中的類型,
+//// 的類型,請在該類型上將 ComVisible 屬性設定為 true。
+//[assembly: ComVisible(false)]
 
-// 下列 GUID 為專案公開 (Expose) 至 COM 時所要使用的 typelib ID
-[assembly: Guid("de0c1e9a-1eee-42cc-8a91-73bf9056a7e7")]
+//// 下列 GUID 為專案公開 (Expose) 至 COM 時所要使用的 typelib ID
+//[assembly: Guid("de0c1e9a-1eee-42cc-8a91-73bf9056a7e7")]
 
-// 組件的版本資訊由下列四個值所組成: 
-//
-//      主要版本
-//      次要版本
-//      組建編號
-//      修訂編號
-//
-// 您可以指定所有的值,或將組建編號或修訂編號設為預設值
-// 指定為預設值: 
-// [assembly: AssemblyVersion("1.2.1.0")]
-[assembly: AssemblyVersion("1.2.1.0")]
-[assembly: AssemblyFileVersion("1.2.1.0")]
+//// 組件的版本資訊由下列四個值所組成: 
+////
+////      主要版本
+////      次要版本
+////      組建編號
+////      修訂編號
+////
+//// 您可以指定所有的值,或將組建編號或修訂編號設為預設值
+//// 指定為預設值: 
+//// [assembly: AssemblyVersion("1.2.1.0")]
+//[assembly: AssemblyVersion("1.2.1.0")]
+//[assembly: AssemblyFileVersion("1.2.1.0")]
 
-[assembly: AssemblyInformationalVersion("0bc54e3")]
+//[assembly: AssemblyInformationalVersion("0bc54e3")]

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

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

+ 3 - 3
EVCB_OCPP.WSServer/ProtalServer.cs

@@ -113,7 +113,7 @@ namespace EVCB_OCPP.WSServer
         private readonly LoadingBalanceService _loadingBalanceService;// = new LoadingBalanceService();
         private List<StationInfoDto> _StationInfo = new List<StationInfoDto>();
 
-        private List<string> needConfirmActions = new List<string>()
+        private readonly List<string> needConfirmActions = new List<string>()
         {
              "GetConfiguration",
              "ChangeConfiguration",
@@ -136,7 +136,7 @@ namespace EVCB_OCPP.WSServer
              "CancelReservation",
              "ExtendedTriggerMessage"
         };
-        private List<Profile> profiles = new List<Profile>()
+        private readonly List<Profile> profiles = new List<Profile>()
         {
              new CoreProfile(),
              new FirmwareManagementProfile(),
@@ -382,7 +382,7 @@ namespace EVCB_OCPP.WSServer
                 List<string> toReturn = new List<string>() { "Command List Clients" };
                 Dictionary<string, ClientData> _copyClientDic = null;
                 _copyClientDic = new Dictionary<string, ClientData>(clientDic);
-                var list = _copyClientDic.Select(c => c.Value).ToList();
+                var list = _copyClientDic.Select(c => c.Value).Where(x=>x.IsPending = false).ToList();
                 int i = 1;
                 foreach (var c in list)
                 {

+ 86 - 24
EVCB_OCPP.WSServer/Service/MainDbService.cs

@@ -4,6 +4,7 @@ using EVCB_OCPP.WSServer.Helper;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
 using OCPPPackage.Profiles;
 using System;
 using System.Collections.Generic;
@@ -29,20 +30,26 @@ public interface IMainDbService
 
 public class MainDbService : IMainDbService
 {
-    public MainDbService(IDbContextFactory<MainDBContext> contextFactory, IConfiguration configuration)
+    public MainDbService(IDbContextFactory<MainDBContext> contextFactory, IConfiguration configuration, ILoggerFactory loggerFactory)
     {
         this.contextFactory = contextFactory;
-
+        this.loggerFactory = loggerFactory;
         var startupLimit = GetStartupLimit(configuration);
         this.startupSemaphore = new (startupLimit);
 
         var opLimit = GetOpLimit(configuration);
         this.opSemaphore = new SemaphoreSlim(opLimit);
+
+        InitUpdateConnectorStatusHandler();
+        //InitUpdateMachineBasicInfoHandler();
     }
 
     private readonly IDbContextFactory<MainDBContext> contextFactory;
+    private readonly ILoggerFactory loggerFactory;
     private readonly QueueSemaphore startupSemaphore;
     private readonly SemaphoreSlim opSemaphore;
+    private GroupSingleHandler<StatusNotificationParam> statusNotificationHandler;
+    //private GroupSingleHandler<UpdateMachineBasicInfoParam> updateMachineBasicInfoHandler;
 
     public async Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId)
     {
@@ -84,22 +91,57 @@ public class MainDbService : IMainDbService
     public async Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine)
     {
         using var semaphoreWrapper = await startupSemaphore.GetToken();
-        using var db = contextFactory.CreateDbContext();
+        using var db = await contextFactory.CreateDbContextAsync();
+
         var _machine = db.Machine.FirstOrDefault(x => x.ChargeBoxId == ChargeBoxId);
         _machine.ChargeBoxSerialNumber = machine.ChargeBoxSerialNumber;
         _machine.ChargePointSerialNumber = machine.ChargePointSerialNumber;
         _machine.ChargePointModel = machine.ChargePointModel;
         _machine.ChargePointVendor = machine.ChargePointVendor;
         _machine.FW_CurrentVersion = machine.FW_CurrentVersion;
-        //_machine.Iccid = string.IsNullOrEmpty(_request.iccid) ? string.Empty : _request.iccid;
         _machine.Iccid = DateTime.UtcNow.ToString("yy-MM-dd HH:mm");
         _machine.Imsi = machine.Imsi;
         _machine.MeterSerialNumber = machine.MeterSerialNumber;
         _machine.MeterType = machine.MeterType;
 
         await db.SaveChangesAsync();
+        //using var semaphoreWrapper = await startupSemaphore.GetToken();
+        //await updateMachineBasicInfoHandler.HandleAsync(new UpdateMachineBasicInfoParam(ChargeBoxId, machine));
     }
 
+    //private void InitUpdateMachineBasicInfoHandler()
+    //{
+    //    if (updateMachineBasicInfoHandler is not null)
+    //    {
+    //        throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once");
+    //    }
+
+    //    updateMachineBasicInfoHandler = new GroupSingleHandler<UpdateMachineBasicInfoParam>(async (pams) => {
+
+    //        using var db = await contextFactory.CreateDbContextAsync();
+    //        using var trans = await db.Database.BeginTransactionAsync();
+
+    //        foreach (var pam in pams)
+    //        {
+    //            var _machine = db.Machine.FirstOrDefault(x => x.ChargeBoxId == pam.ChargeBoxId);
+    //            _machine.ChargeBoxSerialNumber = pam.machine.ChargeBoxSerialNumber;
+    //            _machine.ChargePointSerialNumber = pam.machine.ChargePointSerialNumber;
+    //            _machine.ChargePointModel = pam.machine.ChargePointModel;
+    //            _machine.ChargePointVendor = pam.machine.ChargePointVendor;
+    //            _machine.FW_CurrentVersion = pam.machine.FW_CurrentVersion;
+    //            _machine.Iccid = DateTime.UtcNow.ToString("yy-MM-dd HH:mm");
+    //            _machine.Imsi = pam.machine.Imsi;
+    //            _machine.MeterSerialNumber = pam.machine.MeterSerialNumber;
+    //            _machine.MeterType = pam.machine.MeterType;
+
+    //            await db.SaveChangesAsync();
+    //        }
+
+    //        await trans.CommitAsync();
+    //    },
+    //    loggerFactory.CreateLogger("UpdateMachineBasicInfoHandler"));
+    //}
+
     public async Task AddOCMF(OCMF oCMF)
     {
         using var db = contextFactory.CreateDbContext() ;
@@ -114,33 +156,51 @@ public class MainDbService : IMainDbService
                             && x.ConnectorId == ConnectorId).AsNoTracking().FirstOrDefaultAsync();
     }
 
-    public async Task UpdateConnectorStatus(string Id, ConnectorStatus connectorStatus)
+    public Task UpdateConnectorStatus(string Id, ConnectorStatus connectorStatus)
     {
-        using var db = contextFactory.CreateDbContext();
+        return statusNotificationHandler.HandleAsync(new StatusNotificationParam(Id, connectorStatus));
+    }
 
-        ConnectorStatus status = new() { Id = Id };
+    private void InitUpdateConnectorStatusHandler()
+    {
+        if (statusNotificationHandler is not null)
+        {
+            throw new Exception($"{nameof(InitUpdateConnectorStatusHandler)} should only called once");
+        }
 
-        db.ChangeTracker.AutoDetectChangesEnabled = false;
-        db.ConnectorStatus.Attach(status);
+        statusNotificationHandler = new GroupSingleHandler<StatusNotificationParam>(async (paramCollection) => {
+            using var db = await contextFactory.CreateDbContextAsync();
+            using var trans = await db.Database.BeginTransactionAsync();
 
+            foreach (var param in paramCollection)
+            {
+                ConnectorStatus status = new() { Id = param.Id };
 
-        status.CreatedOn = connectorStatus.CreatedOn;
-        status.Status = connectorStatus.Status;
-        status.ChargePointErrorCodeId = connectorStatus.ChargePointErrorCodeId;
-        status.ErrorInfo = connectorStatus.ErrorInfo;
-        status.VendorId = connectorStatus.VendorId;
-        status.VendorErrorCode = connectorStatus.VendorErrorCode;
+                db.ChangeTracker.AutoDetectChangesEnabled = false;
+                db.ConnectorStatus.Attach(status);
 
 
-        db.Entry(status).Property(x => x.CreatedOn).IsModified = true;
-        db.Entry(status).Property(x => x.Status).IsModified = true;
-        db.Entry(status).Property(x => x.ChargePointErrorCodeId).IsModified = true;
-        db.Entry(status).Property(x => x.ErrorInfo).IsModified = true;
-        db.Entry(status).Property(x => x.VendorId).IsModified = true;
-        db.Entry(status).Property(x => x.VendorErrorCode).IsModified = true;
+                status.CreatedOn = param.Status.CreatedOn;
+                status.Status = param.Status.Status;
+                status.ChargePointErrorCodeId = param.Status.ChargePointErrorCodeId;
+                status.ErrorInfo = param.Status.ErrorInfo;
+                status.VendorId = param.Status.VendorId;
+                status.VendorErrorCode = param.Status.VendorErrorCode;
 
-        //db.SaveChanges();
-        await db.SaveChangesAsync();
+
+                db.Entry(status).Property(x => x.CreatedOn).IsModified = true;
+                db.Entry(status).Property(x => x.Status).IsModified = true;
+                db.Entry(status).Property(x => x.ChargePointErrorCodeId).IsModified = true;
+                db.Entry(status).Property(x => x.ErrorInfo).IsModified = true;
+                db.Entry(status).Property(x => x.VendorId).IsModified = true;
+                db.Entry(status).Property(x => x.VendorErrorCode).IsModified = true;
+
+                //db.SaveChanges();
+                await db.SaveChangesAsync();
+            }
+            await trans.CommitAsync();
+        }
+        , loggerFactory.CreateLogger("StatusNotificationHandler"));
     }
 
 
@@ -166,4 +226,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);

+ 98 - 0
EVCB_OCPP.WSServer/Service/MeterValueDbService.cs

@@ -0,0 +1,98 @@
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Packet.Messages.SubTypes;
+using EVCB_OCPP.WSServer.Helper;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service;
+
+public class MeterValueDbService
+{
+    private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
+    private readonly ILoggerFactory loggerFactory;
+    //private GroupSingleHandler<InsertMeterValueParam> insertMeterValueHandler;
+
+    public MeterValueDbService(IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory, ILoggerFactory loggerFactory)
+    {
+        this.meterValueDbContextFactory = meterValueDbContextFactory;
+        this.loggerFactory = loggerFactory;
+        //InitInsertMeterValueHandler();
+    }
+
+    public async Task InsertAsync(string chargeBoxId, byte connectorId, decimal value, DateTime createdOn
+            , int contextId, int formatId, int measurandId, int phaseId
+            , int locationId, int unitId, int transactionId)
+    {
+        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
+
+        string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId, @ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
+
+        var param = new InsertMeterValueParam(chargeBoxId, connectorId, value, createdOn, contextId, formatId, measurandId, phaseId, locationId, unitId, transactionId);
+
+        List<SqlParameter> parameter = new List<SqlParameter>();
+        parameter.AddInsertMeterValueRecordSqlParameters(
+            chargeBoxId: param.chargeBoxId
+            , connectorId: (byte)param.connectorId
+            , value: param.value
+            , createdOn: param.createdOn
+            , contextId: param.contextId
+            , formatId: param.formatId
+            , measurandId: param.measurandId
+            , phaseId: param.phaseId
+            , locationId: param.locationId
+            , unitId: param.unitId
+            , transactionId: param.transactionId);
+
+        await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
+        //return insertMeterValueHandler.HandleAsync(new InsertMeterValueParam(chargeBoxId, connectorId, value, createdOn, contextId, formatId, measurandId, phaseId, locationId, unitId, transactionId));
+    }
+
+//    private void InitInsertMeterValueHandler()
+//    {
+//        if (insertMeterValueHandler is not null)
+//        {
+//            throw new Exception($"{nameof(InitInsertMeterValueHandler)} should only called once");
+//        }
+
+//        insertMeterValueHandler = new GroupSingleHandler<InsertMeterValueParam>(async (parms) => {
+
+//            using var db = await meterValueDbContextFactory.CreateDbContextAsync();
+//            using var trans = await db.Database.BeginTransactionAsync();
+
+//            string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId," +
+//"@ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
+
+//            foreach (var param in parms)
+//            {
+//                List<SqlParameter> parameter = new List<SqlParameter>();
+//                parameter.AddInsertMeterValueRecordSqlParameters(
+//                    chargeBoxId: param.chargeBoxId
+//                    , connectorId: (byte)param.connectorId
+//                    , value: param.value
+//                    , createdOn: param.createdOn
+//                    , contextId: param.contextId
+//                    , formatId: param.formatId
+//                    , measurandId: param.measurandId
+//                    , phaseId: param.phaseId
+//                    , locationId: param.locationId
+//                    , unitId: param.unitId
+//                    , transactionId: param.transactionId);
+
+//                await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
+//            }
+
+//            await trans.CommitAsync();
+//        }
+//        , loggerFactory.CreateLogger("InsertMeterValueHandler"));
+//    }
+}
+
+public record InsertMeterValueParam(string chargeBoxId, byte connectorId, decimal value, DateTime createdOn
+            , int contextId, int formatId, int measurandId, int phaseId
+            , int locationId, int unitId, int transactionId);