Robert 9 ヶ月 前
コミット
97be2ccbdd
58 ファイル変更11811 行追加11811 行削除
  1. 63 63
      .gitattributes
  2. 66 66
      DEV_Build.ps1
  3. 1 1
      Dev_Build.bat
  4. 52 52
      Dockerfile
  5. 55 55
      EVCB_OCPP.Server.sln
  6. 25 25
      EVCB_OCPP.WSServer/Dto/ErrorDetails.cs
  7. 18 18
      EVCB_OCPP.WSServer/Dto/TransactionEnergy.cs
  8. 69 69
      EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
  9. 71 71
      EVCB_OCPP.WSServer/Helper/AddPortalDbContext.cs
  10. 151 151
      EVCB_OCPP.WSServer/Helper/GroupHandlerIO.cs
  11. 246 246
      EVCB_OCPP.WSServer/Helper/MeterValueGroupSingleHandler.cs
  12. 159 159
      EVCB_OCPP.WSServer/HostedProtalServer.cs
  13. 67 67
      EVCB_OCPP.WSServer/Jobs/DenyModelCheckJob.cs
  14. 43 43
      EVCB_OCPP.WSServer/Jobs/HealthCheckTriggerJob.cs
  15. 71 71
      EVCB_OCPP.WSServer/Jobs/HeartBeatCheckJob.cs
  16. 217 217
      EVCB_OCPP.WSServer/Jobs/ServerMessageJob.cs
  17. 97 97
      EVCB_OCPP.WSServer/Jobs/ServerSetFeeJob.cs
  18. 138 138
      EVCB_OCPP.WSServer/Jobs/ServerUpdateJob.cs
  19. 85 85
      EVCB_OCPP.WSServer/Jobs/SmartChargingJob.cs
  20. 27 27
      EVCB_OCPP.WSServer/Jobs/StationConfigPollingJob.cs
  21. 158 158
      EVCB_OCPP.WSServer/Message/BasicMessageHandler.cs
  22. 1814 1814
      EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs
  23. 233 233
      EVCB_OCPP.WSServer/Message/FirmwareManagementProfileHandler.cs
  24. 108 108
      EVCB_OCPP.WSServer/Message/LocalAuthListManagementProfileHandler.cs
  25. 349 349
      EVCB_OCPP.WSServer/Message/OCPP16MessageHandler.cs
  26. 298 298
      EVCB_OCPP.WSServer/Message/OCPP20MessageHandler.cs
  27. 90 90
      EVCB_OCPP.WSServer/Message/RemoteTriggerHandler.cs
  28. 129 129
      EVCB_OCPP.WSServer/Message/ReservationProfileHandler.cs
  29. 75 75
      EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs
  30. 190 190
      EVCB_OCPP.WSServer/Message/SmartChargingProfileHandler.cs
  31. 96 96
      EVCB_OCPP.WSServer/Program.cs
  32. 12 12
      EVCB_OCPP.WSServer/Properties/PublishProfiles/FolderProfile1.pubxml
  33. 1194 1194
      EVCB_OCPP.WSServer/ProtalServer.cs
  34. 95 95
      EVCB_OCPP.WSServer/Service/BusinessServiceFactory.cs
  35. 291 291
      EVCB_OCPP.WSServer/Service/ConfirmWaitingMessageSerevice.cs
  36. 383 383
      EVCB_OCPP.WSServer/Service/DbService/ConnectionLogdbService.cs
  37. 1315 1315
      EVCB_OCPP.WSServer/Service/DbService/MainDbService.cs
  38. 418 418
      EVCB_OCPP.WSServer/Service/DbService/MeterValueDbService.cs
  39. 236 236
      EVCB_OCPP.WSServer/Service/DbService/WebDbService.cs
  40. 31 31
      EVCB_OCPP.WSServer/Service/GoogleGetTimePrintService.cs
  41. 68 68
      EVCB_OCPP.WSServer/Service/HeaderRecordService.cs
  42. 372 372
      EVCB_OCPP.WSServer/Service/LoadingBalanceService.cs
  43. 93 93
      EVCB_OCPP.WSServer/Service/LocalBusinessService.cs
  44. 150 150
      EVCB_OCPP.WSServer/Service/MapApiServce.cs
  45. 166 166
      EVCB_OCPP.WSServer/Service/MeterValueInsertHandler.cs
  46. 316 316
      EVCB_OCPP.WSServer/Service/OuterBusinessService.cs
  47. 64 64
      EVCB_OCPP.WSServer/Service/ServerMessageService.cs
  48. 247 247
      EVCB_OCPP.WSServer/Service/StationConfigService.cs
  49. 271 271
      EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs
  50. 167 167
      EVCB_OCPP.WSServer/Service/WsService/WebsocketService.cs
  51. 158 158
      EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs
  52. 146 146
      EVCB_OCPP.WSServer/appsettings.json
  53. 109 109
      Prerelease_Build.ps1
  54. 9 9
      Prod_Build_podman.bat
  55. 101 101
      Release_Build.ps1
  56. 86 86
      SocketEngine/SuperSocket.SocketEngine.csproj
  57. 48 48
      SuperWebSocket/SuperWebSocket.csproj
  58. 4 4
      build.bat

+ 63 - 63
.gitattributes

@@ -1,63 +1,63 @@
-###############################################################################
-# Set default behavior to automatically normalize line endings.
-###############################################################################
-* text=auto
-
-###############################################################################
-# Set default behavior for command prompt diff.
-#
-# This is need for earlier builds of msysgit that does not have it on by
-# default for csharp files.
-# Note: This is only used by command line
-###############################################################################
-#*.cs     diff=csharp
-
-###############################################################################
-# Set the merge driver for project and solution files
-#
-# Merging from the command prompt will add diff markers to the files if there
-# are conflicts (Merging from VS is not affected by the settings below, in VS
-# the diff markers are never inserted). Diff markers may cause the following 
-# file extensions to fail to load in VS. An alternative would be to treat
-# these files as binary and thus will always conflict and require user
-# intervention with every merge. To do so, just uncomment the entries below
-###############################################################################
-#*.sln       merge=binary
-#*.csproj    merge=binary
-#*.vbproj    merge=binary
-#*.vcxproj   merge=binary
-#*.vcproj    merge=binary
-#*.dbproj    merge=binary
-#*.fsproj    merge=binary
-#*.lsproj    merge=binary
-#*.wixproj   merge=binary
-#*.modelproj merge=binary
-#*.sqlproj   merge=binary
-#*.wwaproj   merge=binary
-
-###############################################################################
-# behavior for image files
-#
-# image files are treated as binary by default.
-###############################################################################
-#*.jpg   binary
-#*.png   binary
-#*.gif   binary
-
-###############################################################################
-# diff behavior for common document formats
-# 
-# Convert binary document formats to text before diffing them. This feature
-# is only available from the command line. Turn it on by uncommenting the 
-# entries below.
-###############################################################################
-#*.doc   diff=astextplain
-#*.DOC   diff=astextplain
-#*.docx  diff=astextplain
-#*.DOCX  diff=astextplain
-#*.dot   diff=astextplain
-#*.DOT   diff=astextplain
-#*.pdf   diff=astextplain
-#*.PDF   diff=astextplain
-#*.rtf   diff=astextplain
-#*.RTF   diff=astextplain
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=false
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain

+ 66 - 66
DEV_Build.ps1

@@ -1,66 +1,66 @@
-# 設定 ASCII 藝術字的內容
-$asciiArt = @"
-  _____  ________      ________ _      ____  _____  __  __ ______ _   _ _______ 
- |  __ \|  ____\ \    / /  ____| |    / __ \|  __ \|  \/  |  ____| \ | |__   __|
- | |  | | |__   \ \  / /| |__  | |   | |  | | |__) | \  / | |__  |  \| |  | |   
- | |  | |  __|   \ \/ / |  __| | |   | |  | |  ___/| |\/| |  __| | . ` |  | |   
- | |__| | |____   \  /  | |____| |___| |__| | |    | |  | | |____| |\  |  | |   
- |_____/|______|   \/   |______|______\____/|_|    |_|  |_|______|_| \_|  |_|   
-                                                                                
-                                                                                
-"@
-
-# 顯示 ASCII 藝術字
-Write-Host $asciiArt
-
-#第一次建立專案請先設定ACR Name
-$registryname="evdevcontainerregistry"
-$fullregistryname="evdevcontainerregistry.azurecr.io"
-#第一次建立專案請先設定專案名稱
-$imagerepositoryname="server"
-$dev_prefix = "Docker_test_"
-
-$username = az account show --query user.name
-$username = $username.TrimStart("""").Split('@')[0]
-
-$tagname= $dev_prefix + $username
-
-$fulltag=$fullregistryname+"/"+$imagerepositoryname+":"+$tagname
-$imagename = $imagerepositoryname+":"+$tagname
-
-
-$response = read-host  "please confirm that what you are currently uploading is a test version[ $fulltag ]. (y/n)"
-
-
-if ($response -eq "y") {
- write-host "upload processing....."
-
-
- #解除image鎖定
- az acr repository update --name $registryname --image $imagename --delete-enabled true --write-enabled true
-
- $ssha = git rev-parse --short head
-
- Write-Host "ACR Login....."
- $token = az acr login --name $registryname --expose-token --output tsv --query accessToken
- $user = "00000000-0000-0000-0000-000000000000"
- podman login $fullregistryname -u $user -p $token
- 
- #wite ssha to file
- $ssha | Out-File ssha
- 
- podman build ./ -t  $fulltag --label [gitcommit=$ssha,author=$username]
- podman push $fulltag
- 
- #remove ssha file
- Remove-Item ssha
-
- #鎖定image
- az acr repository update --name $registryname --image $imagename --delete-enabled false --write-enabled false
-} else {
- write-host "please modify the parameters with scripts."
-}
-
-
-
-
+# 設定 ASCII 藝術字的內容
+$asciiArt = @"
+  _____  ________      ________ _      ____  _____  __  __ ______ _   _ _______ 
+ |  __ \|  ____\ \    / /  ____| |    / __ \|  __ \|  \/  |  ____| \ | |__   __|
+ | |  | | |__   \ \  / /| |__  | |   | |  | | |__) | \  / | |__  |  \| |  | |   
+ | |  | |  __|   \ \/ / |  __| | |   | |  | |  ___/| |\/| |  __| | . ` |  | |   
+ | |__| | |____   \  /  | |____| |___| |__| | |    | |  | | |____| |\  |  | |   
+ |_____/|______|   \/   |______|______\____/|_|    |_|  |_|______|_| \_|  |_|   
+                                                                                
+                                                                                
+"@
+
+# 顯示 ASCII 藝術字
+Write-Host $asciiArt
+
+#第一次建立專案請先設定ACR Name
+$registryname="evdevcontainerregistry"
+$fullregistryname="evdevcontainerregistry.azurecr.io"
+#第一次建立專案請先設定專案名稱
+$imagerepositoryname="server"
+$dev_prefix = "Docker_test_"
+
+$username = az account show --query user.name
+$username = $username.TrimStart("""").Split('@')[0]
+
+$tagname= $dev_prefix + $username
+
+$fulltag=$fullregistryname+"/"+$imagerepositoryname+":"+$tagname
+$imagename = $imagerepositoryname+":"+$tagname
+
+
+$response = read-host  "please confirm that what you are currently uploading is a test version[ $fulltag ]. (y/n)"
+
+
+if ($response -eq "y") {
+ write-host "upload processing....."
+
+
+ #解除image鎖定
+ az acr repository update --name $registryname --image $imagename --delete-enabled true --write-enabled true
+
+ $ssha = git rev-parse --short head
+
+ Write-Host "ACR Login....."
+ $token = az acr login --name $registryname --expose-token --output tsv --query accessToken
+ $user = "00000000-0000-0000-0000-000000000000"
+ podman login $fullregistryname -u $user -p $token
+ 
+ #wite ssha to file
+ $ssha | Out-File ssha
+ 
+ podman build ./ -t  $fulltag --label [gitcommit=$ssha,author=$username]
+ podman push $fulltag
+ 
+ #remove ssha file
+ Remove-Item ssha
+
+ #鎖定image
+ az acr repository update --name $registryname --image $imagename --delete-enabled false --write-enabled false
+} else {
+ write-host "please modify the parameters with scripts."
+}
+
+
+
+

+ 1 - 1
Dev_Build.bat

@@ -1,2 +1,2 @@
-docker build ./ -t evdevcontainerregistry.azurecr.io/server:test
+docker build ./ -t evdevcontainerregistry.azurecr.io/server:test
 docker push evdevcontainerregistry.azurecr.io/server:test

+ 52 - 52
Dockerfile

@@ -1,53 +1,53 @@
-#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
-EXPOSE 80
-EXPOSE 443
-EXPOSE 54088
-EXPOSE 2222
-WORKDIR /app
-
-RUN apt-get update \
-    && apt-get install -y --no-install-recommends dialog \
-    && apt-get install -y --no-install-recommends openssh-server \
-	&& apt-get install -y tcpdump\
-	&& mkdir -p /run/sshd \
-    && echo "root:Docker!" | chpasswd 
-	
-COPY sshd_config /etc/ssh/sshd_config
-
-RUN apt-get update \
-	&& apt-get install -y cron \
-	&& apt-get install -y vim \
-	&& apt-get install -y zip 
-	
-COPY schedule .
-RUN crontab schedule
-
-RUN echo 'net.ipv4.tcp_rmem= 10240 87380 12582912' >> /etc/sysctl.conf
-RUN echo 'net.ipv4.tcp_wmem= 10240 87380 12582912' >> /etc/sysctl.conf
-RUN echo 'net.ipv4.tcp_window_scaling = 1' >> /etc/sysctl.conf
-
-
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
-WORKDIR /src
-COPY ["EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj", "EVCB_OCPP.WSServer/"]
-COPY ["SuperWebSocket/SuperWebSocket.csproj", "SuperWebSocket/"]
-COPY ["SocketBase/SuperSocket.SocketBase.csproj", "SocketBase/"]
-COPY ["SocketCommon/SuperSocket.Common.csproj", "SocketCommon/"]
-COPY ["SocketEngine/SuperSocket.SocketEngine.csproj", "SocketEngine/"]
-RUN dotnet restore "EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj"
-COPY . .
-WORKDIR "/src/EVCB_OCPP.WSServer"
-RUN dotnet build "EVCB_OCPP.WSServer.csproj" -c Release -o /app/build
-
-FROM build AS publish
-RUN dotnet publish "EVCB_OCPP.WSServer.csproj" -c Release -o /app/publish /p:UseAppHost=false
-
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-COPY entrypoint.sh /app/entrypoint.sh
-COPY ssha /app/ssha
-RUN chmod +x /app/entrypoint.sh
+#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
+EXPOSE 80
+EXPOSE 443
+EXPOSE 54088
+EXPOSE 2222
+WORKDIR /app
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends dialog \
+    && apt-get install -y --no-install-recommends openssh-server \
+	&& apt-get install -y tcpdump\
+	&& mkdir -p /run/sshd \
+    && echo "root:Docker!" | chpasswd 
+	
+COPY sshd_config /etc/ssh/sshd_config
+
+RUN apt-get update \
+	&& apt-get install -y cron \
+	&& apt-get install -y vim \
+	&& apt-get install -y zip 
+	
+COPY schedule .
+RUN crontab schedule
+
+RUN echo 'net.ipv4.tcp_rmem= 10240 87380 12582912' >> /etc/sysctl.conf
+RUN echo 'net.ipv4.tcp_wmem= 10240 87380 12582912' >> /etc/sysctl.conf
+RUN echo 'net.ipv4.tcp_window_scaling = 1' >> /etc/sysctl.conf
+
+
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+WORKDIR /src
+COPY ["EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj", "EVCB_OCPP.WSServer/"]
+COPY ["SuperWebSocket/SuperWebSocket.csproj", "SuperWebSocket/"]
+COPY ["SocketBase/SuperSocket.SocketBase.csproj", "SocketBase/"]
+COPY ["SocketCommon/SuperSocket.Common.csproj", "SocketCommon/"]
+COPY ["SocketEngine/SuperSocket.SocketEngine.csproj", "SocketEngine/"]
+RUN dotnet restore "EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj"
+COPY . .
+WORKDIR "/src/EVCB_OCPP.WSServer"
+RUN dotnet build "EVCB_OCPP.WSServer.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "EVCB_OCPP.WSServer.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+COPY entrypoint.sh /app/entrypoint.sh
+COPY ssha /app/ssha
+RUN chmod +x /app/entrypoint.sh
 CMD ["/app/entrypoint.sh"]

+ 55 - 55
EVCB_OCPP.Server.sln

@@ -1,55 +1,55 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.4.33103.184
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EVCB_OCPP.WSServer", "EVCB_OCPP.WSServer\EVCB_OCPP.WSServer.csproj", "{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestTool.RemoteTriggerAPP", "TestTool.RemoteTriggerAPP\TestTool.RemoteTriggerAPP.csproj", "{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperWebSocket", "SuperWebSocket\SuperWebSocket.csproj", "{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperSocket.SocketBase", "SocketBase\SuperSocket.SocketBase.csproj", "{743510BD-A370-47A9-8264-0F30161EA9D0}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperSocket.SocketEngine", "SocketEngine\SuperSocket.SocketEngine.csproj", "{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperSocket.Common", "SocketCommon\SuperSocket.Common.csproj", "{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}.Release|Any CPU.Build.0 = Release|Any CPU
-		{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}.Release|Any CPU.Build.0 = Release|Any CPU
-		{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}.Release|Any CPU.Build.0 = Release|Any CPU
-		{743510BD-A370-47A9-8264-0F30161EA9D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{743510BD-A370-47A9-8264-0F30161EA9D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{743510BD-A370-47A9-8264-0F30161EA9D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{743510BD-A370-47A9-8264-0F30161EA9D0}.Release|Any CPU.Build.0 = Release|Any CPU
-		{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}.Release|Any CPU.Build.0 = Release|Any CPU
-		{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}.Release|Any CPU.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(ExtensibilityGlobals) = postSolution
-		SolutionGuid = {79198208-2DBA-46E9-B361-18617E64EAAE}
-	EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33103.184
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EVCB_OCPP.WSServer", "EVCB_OCPP.WSServer\EVCB_OCPP.WSServer.csproj", "{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestTool.RemoteTriggerAPP", "TestTool.RemoteTriggerAPP\TestTool.RemoteTriggerAPP.csproj", "{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperWebSocket", "SuperWebSocket\SuperWebSocket.csproj", "{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperSocket.SocketBase", "SocketBase\SuperSocket.SocketBase.csproj", "{743510BD-A370-47A9-8264-0F30161EA9D0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperSocket.SocketEngine", "SocketEngine\SuperSocket.SocketEngine.csproj", "{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperSocket.Common", "SocketCommon\SuperSocket.Common.csproj", "{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DE0C1E9A-1EEE-42CC-8A91-73BF9056A7E7}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F39A3B1E-2B93-40E1-9C7B-8CEE2529BF52}.Release|Any CPU.Build.0 = Release|Any CPU
+		{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{43C5BC98-FA2C-45D1-BF96-A299C05A72AE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{743510BD-A370-47A9-8264-0F30161EA9D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{743510BD-A370-47A9-8264-0F30161EA9D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{743510BD-A370-47A9-8264-0F30161EA9D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{743510BD-A370-47A9-8264-0F30161EA9D0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D4A0E22B-8EAF-4CA5-AE1B-414508D71B62}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8241B98B-A7BF-4FBA-BD0B-B1536DDD1A72}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {79198208-2DBA-46E9-B361-18617E64EAAE}
+	EndGlobalSection
+EndGlobal

+ 25 - 25
EVCB_OCPP.WSServer/Dto/ErrorDetails.cs

@@ -1,25 +1,25 @@
-using EVCB_OCPP.Packet.Messages.SubTypes;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Converters;
-using System;
-
-namespace EVCB_OCPP.WSServer.Dto
-{
-    public class ErrorDetails
-    {
-        public string ChargeBoxId { set; get; }
-
-        public int ConnectorId { set; get; }
-
-        [JsonConverter(typeof(StringEnumConverter))]
-        public ChargePointErrorCode ErrorCode { set; get; }
-
-        public string Info { set; get; }
-
-        public string VendorId { set; get; }
-
-        public string VendorErrorCode { set; get; }
-
-        public DateTime OCcuredOn { set; get; }
-    }
-}
+using EVCB_OCPP.Packet.Messages.SubTypes;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System;
+
+namespace EVCB_OCPP.WSServer.Dto
+{
+    public class ErrorDetails
+    {
+        public string ChargeBoxId { set; get; }
+
+        public int ConnectorId { set; get; }
+
+        [JsonConverter(typeof(StringEnumConverter))]
+        public ChargePointErrorCode ErrorCode { set; get; }
+
+        public string Info { set; get; }
+
+        public string VendorId { set; get; }
+
+        public string VendorErrorCode { set; get; }
+
+        public DateTime OCcuredOn { set; get; }
+    }
+}

+ 18 - 18
EVCB_OCPP.WSServer/Dto/TransactionEnergy.cs

@@ -1,18 +1,18 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Dto
-{
-    public class TransactionEnergy
-    {       
-
-        public int TxId { set; get; }
-
-		public string EVCCID { set; get; }
-
-		public Dictionary<string,decimal> PeriodEnergy { set; get; }
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Dto
+{
+    public class TransactionEnergy
+    {       
+
+        public int TxId { set; get; }
+
+		public string EVCCID { set; get; }
+
+		public Dictionary<string,decimal> PeriodEnergy { set; get; }
+    }
+}

+ 69 - 69
EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj

@@ -1,70 +1,70 @@
-<Project Sdk="Microsoft.NET.Sdk">
-  <PropertyGroup>
-    <OutputType>Exe</OutputType>
-    <TargetFramework>net7.0</TargetFramework>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <UserSecretsId>88f57ec2-60b9-4291-bba3-3c0d312fe6dc</UserSecretsId>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="EVCB_OCPP.Domain">
-      <HintPath>DLL\EVCB_OCPP.Domain.dll</HintPath>
-    </Reference>
-    <Reference Include="EVCB_OCPP.Packet, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>DLL\EVCB_OCPP.Packet.dll</HintPath>
-    </Reference>
-    <Reference Include="EVCB_OCPP20.Packet, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>DLL\EVCB_OCPP20.Packet.dll</HintPath>
-    </Reference>
-  </ItemGroup>
-  <ItemGroup>
-    <Content Include="appsettings.json">
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
-    </Content>
-    <Content Include="DLL\EVCB_OCPP20.Packet.dll" />
-  </ItemGroup>
-  <ItemGroup>
-    <Content Include="DLL\EVCB_OCPP.Packet.dll" />
-  </ItemGroup>
-  <ItemGroup>
-    <BootstrapperPackage Include=".NETFramework,Version=v4.7.1">
-      <Visible>False</Visible>
-      <ProductName>Microsoft .NET Framework 4.7.1 %28x86 和 x64%29</ProductName>
-      <Install>true</Install>
-    </BootstrapperPackage>
-    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
-      <Visible>False</Visible>
-      <ProductName>.NET Framework 3.5 SP1</ProductName>
-      <Install>false</Install>
-    </BootstrapperPackage>
-  </ItemGroup>
-  <ItemGroup>
-    <PackageReference Include="Azure.Identity" Version="1.11.2" />
-    <PackageReference Include="Dapper" Version="2.0.143" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.12" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.12" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.10" />
-    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
-    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
-    <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
-    <PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="7.0.10" />
-    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
-    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
-    <PackageReference Include="NLog" Version="5.2.3" />
-    <PackageReference Include="NLog.Web.AspNetCore" Version="5.3.3" />
-    <PackageReference Include="Polly" Version="7.2.4" />
-    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.7.0" />
-    <PackageReference Include="RestSharp" Version="110.2.0" />
-    <PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
-    <PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
-    <PackageReference Include="System.Security.Cryptography.Pkcs" Version="6.0.3" />
-    <PackageReference Include="System.Security.Permissions" Version="8.0.0" />
-    <PackageReference Include="System.ServiceModel.Federation" Version="6.0.0" />
-    <PackageReference Include="EntityFramework" Version="6.4.4" />
-    <PackageReference Include="log4net" Version="2.0.15" />
-    <PackageReference Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />
-    <PackageReference Include="Yarp.ReverseProxy" Version="2.0.1" />
-  </ItemGroup>
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UserSecretsId>88f57ec2-60b9-4291-bba3-3c0d312fe6dc</UserSecretsId>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="EVCB_OCPP.Domain">
+      <HintPath>DLL\EVCB_OCPP.Domain.dll</HintPath>
+    </Reference>
+    <Reference Include="EVCB_OCPP.Packet, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>DLL\EVCB_OCPP.Packet.dll</HintPath>
+    </Reference>
+    <Reference Include="EVCB_OCPP20.Packet, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>DLL\EVCB_OCPP20.Packet.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="appsettings.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="DLL\EVCB_OCPP20.Packet.dll" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="DLL\EVCB_OCPP.Packet.dll" />
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.7.1">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4.7.1 %28x86 和 x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="Azure.Identity" Version="1.11.2" />
+    <PackageReference Include="Dapper" Version="2.0.143" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.12" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.12" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.10" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="7.0.10" />
+    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+    <PackageReference Include="NLog" Version="5.2.3" />
+    <PackageReference Include="NLog.Web.AspNetCore" Version="5.3.3" />
+    <PackageReference Include="Polly" Version="7.2.4" />
+    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.7.0" />
+    <PackageReference Include="RestSharp" Version="110.2.0" />
+    <PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
+    <PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
+    <PackageReference Include="System.Security.Cryptography.Pkcs" Version="6.0.3" />
+    <PackageReference Include="System.Security.Permissions" Version="8.0.0" />
+    <PackageReference Include="System.ServiceModel.Federation" Version="6.0.0" />
+    <PackageReference Include="EntityFramework" Version="6.4.4" />
+    <PackageReference Include="log4net" Version="2.0.15" />
+    <PackageReference Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />
+    <PackageReference Include="Yarp.ReverseProxy" Version="2.0.1" />
+  </ItemGroup>
 </Project>

+ 71 - 71
EVCB_OCPP.WSServer/Helper/AddPortalDbContext.cs

@@ -1,72 +1,72 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.Domain.Extensions;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using NLog.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data.Entity.Infrastructure;
-using System.Diagnostics;
-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 AddWebDBConetext(this IServiceCollection services, IConfiguration configuration)
-    {
-        const string DbUserIdKey = "WebDbUserId";
-        const string DbPassKey = "WebDbPass";
-        const string DbConnectionStringKey = "WebDBContext";
-
-        var conneciotnString = configuration.GetConnectionString(DbUserIdKey, DbPassKey, DbConnectionStringKey);
-        services.AddSqlConnectionFactory<WebDBConetext>(conneciotnString);
-        return services;
-    }
-
-    public static IServiceCollection AddOnlineLogDBContext(this IServiceCollection services, IConfiguration configuration)
-    {
-        const string DbUserIdKey = "OnlineLogDbUserId";
-        const string DbPassKey = "OnlineLogDbPass";
-        const string DbConnectionStringKey = "OnlineLogDBContext";
-
-        var conneciotnString = configuration.GetConnectionString(DbUserIdKey, DbPassKey, DbConnectionStringKey);
-        services.AddSqlConnectionFactory<OnlineLogDBContext>(conneciotnString);
-        return services;
-    }
-
-    private static void AddPortalDbContextInternal<T>(
-        IServiceCollection services, IConfiguration configuration,
-        string connectionString,bool logToConsole = false) where T : DbContext
-    {
-
-        var commandTimeout = int.TryParse(configuration[CommandTimeoutKey], out var temp) ? temp : 180;
-        
-        services.AddPooledDbContextFactory<T>((serviceProvider, options) => {
-            options.UseSqlServer(connectionString, dbOptions =>
-            {
-                dbOptions.CommandTimeout(commandTimeout);
-            });
-            options.UseLoggerFactory(serviceProvider.GetRequiredService<ILoggerFactory>());
-        });
-    }
-}
-
-/// <summary>
-/// Dummy
-/// </summary>
-public class WebDBConetext : DbContext {}
-
-/// <summary>
-/// Dummy
-/// </summary>
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.Domain.Extensions;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using NLog.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data.Entity.Infrastructure;
+using System.Diagnostics;
+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 AddWebDBConetext(this IServiceCollection services, IConfiguration configuration)
+    {
+        const string DbUserIdKey = "WebDbUserId";
+        const string DbPassKey = "WebDbPass";
+        const string DbConnectionStringKey = "WebDBContext";
+
+        var conneciotnString = configuration.GetConnectionString(DbUserIdKey, DbPassKey, DbConnectionStringKey);
+        services.AddSqlConnectionFactory<WebDBConetext>(conneciotnString);
+        return services;
+    }
+
+    public static IServiceCollection AddOnlineLogDBContext(this IServiceCollection services, IConfiguration configuration)
+    {
+        const string DbUserIdKey = "OnlineLogDbUserId";
+        const string DbPassKey = "OnlineLogDbPass";
+        const string DbConnectionStringKey = "OnlineLogDBContext";
+
+        var conneciotnString = configuration.GetConnectionString(DbUserIdKey, DbPassKey, DbConnectionStringKey);
+        services.AddSqlConnectionFactory<OnlineLogDBContext>(conneciotnString);
+        return services;
+    }
+
+    private static void AddPortalDbContextInternal<T>(
+        IServiceCollection services, IConfiguration configuration,
+        string connectionString,bool logToConsole = false) where T : DbContext
+    {
+
+        var commandTimeout = int.TryParse(configuration[CommandTimeoutKey], out var temp) ? temp : 180;
+        
+        services.AddPooledDbContextFactory<T>((serviceProvider, options) => {
+            options.UseSqlServer(connectionString, dbOptions =>
+            {
+                dbOptions.CommandTimeout(commandTimeout);
+            });
+            options.UseLoggerFactory(serviceProvider.GetRequiredService<ILoggerFactory>());
+        });
+    }
+}
+
+/// <summary>
+/// Dummy
+/// </summary>
+public class WebDBConetext : DbContext {}
+
+/// <summary>
+/// Dummy
+/// </summary>
 public class OnlineLogDBContext : DbContext { }

+ 151 - 151
EVCB_OCPP.WSServer/Helper/GroupHandlerIO.cs

@@ -1,152 +1,152 @@
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Helper;
-
-public class GroupHandler<TI,TO> where TI : class
-{
-    public GroupHandler(
-        Func<BundleHandlerData<TI,TO>, Task> handleFunc,
-        ILogger logger, int workerCnt = 1, int maxRetry = 10)
-    {
-        this.handleFunc = handleFunc;
-        this.logger = logger;
-
-        this.maxRetry = maxRetry;
-        workersLock = new SemaphoreSlim(workerCnt);
-        //singleWorkLock = new(_WorkerCnt);
-    }
-
-    private readonly Func<BundleHandlerData<TI, TO>, Task> handleFunc;
-    private readonly ILogger logger;
-    private readonly int maxRetry;
-    private readonly ConcurrentQueue<WaitParam<TI, TO>> waitList = new();
-
-    private SemaphoreSlim workersLock;// = new SemaphoreSlim(1);
-
-    public async Task<TO> HandleAsync(TI param)
-    {
-        var waitData = new WaitParam<TI,TO>() { Data = param, Waiter = new SemaphoreSlim(0), Exception = null };
-        waitList.Enqueue(waitData);
-        TryStartHandler();
-        await waitData.Waiter.WaitAsync();
-        if (waitData.Exception is not null)
-        {
-            throw waitData.Exception;
-        }
-        return waitData.Result;
-    }
-
-    private void TryStartHandler()
-    {
-        if (!workersLock.Wait(0))
-        {
-            return;
-        }
-
-        if (waitList.Count == 0)
-        {
-            workersLock.Release();
-            return;
-        }
-
-        _ = StartHandleTask();
-    }
-
-    private async Task StartHandleTask()
-    {
-        var timer = Stopwatch.StartNew();
-        List<long> times = new();
-
-        var requests = new List<WaitParam<TI,TO>>();
-
-        while (waitList.TryDequeue(out var handle))
-        {
-            requests.Add(handle);
-        }
-        times.Add(timer.ElapsedMilliseconds);
-
-        int cnt = 0;
-        Exception lastException = null;
-        var datas = requests.Select(x => x.Data).ToList();
-
-        for (; cnt < maxRetry; cnt++)
-        {
-            var bundleHandledata = new BundleHandlerData<TI, TO>(datas);
-            try
-            {
-                await handleFunc(bundleHandledata);
-            }
-            catch (Exception e)
-            {
-                lastException = e;
-            }
-
-            var completedKeys = bundleHandledata.CompletedDatas.Select(x => x.Key).ToList();
-            var completedRequests = requests.Where(x => completedKeys.Any(y=> y == x.Data)).ToList();
-            foreach (var request in completedRequests)
-            {
-                var result = bundleHandledata.CompletedDatas.FirstOrDefault(x => x.Key == request.Data);
-                request.Result = result.Value;
-                request.Waiter.Release();
-            }
-
-            //datas = datas.Except(bundleHandledata.CompletedDatas).ToList();
-            datas = datas.Where(x => !completedKeys.Contains(x)).ToList();
-
-            if (datas == null || datas.Count == 0)
-            {
-                break;
-            }
-            logger.LogError(lastException?.Message);
-            times.Add(timer.ElapsedMilliseconds);
-        }
-
-        var uncompletedRequests = requests.Where(x => datas.Contains(x.Data)).ToList();
-        foreach (var request in uncompletedRequests)
-        {
-            request.Exception = lastException;
-            request.Waiter.Release();
-        }
-        workersLock.Release();
-
-        timer.Stop();
-        if (timer.ElapsedMilliseconds > 1000)
-        {
-            logger.LogWarning($"StartHandleTask {string.Join("/", times)}");
-        }
-
-        TryStartHandler();
-    }
-}
-
-public class BundleHandlerData<TI,TO>
-{
-    public List<TI> Datas { get; set; }
-    public List<KeyValuePair<TI,TO>> CompletedDatas { get; set; }
-
-    public BundleHandlerData(List<TI> datas)
-    {
-        Datas = datas;
-        CompletedDatas = new();
-    }
-
-    public void AddCompletedData(TI competedData, TO result)
-    {
-        CompletedDatas.Add(new KeyValuePair<TI, TO>(competedData, result)); ;
-    }
-}
-
-internal class WaitParam<TI,TO>
-{
-    public TI Data { get; init; }
-    public TO Result { get; set; }
-    public SemaphoreSlim Waiter { get; init; }
-    public Exception Exception { get; set; }
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Helper;
+
+public class GroupHandler<TI,TO> where TI : class
+{
+    public GroupHandler(
+        Func<BundleHandlerData<TI,TO>, Task> handleFunc,
+        ILogger logger, int workerCnt = 1, int maxRetry = 10)
+    {
+        this.handleFunc = handleFunc;
+        this.logger = logger;
+
+        this.maxRetry = maxRetry;
+        workersLock = new SemaphoreSlim(workerCnt);
+        //singleWorkLock = new(_WorkerCnt);
+    }
+
+    private readonly Func<BundleHandlerData<TI, TO>, Task> handleFunc;
+    private readonly ILogger logger;
+    private readonly int maxRetry;
+    private readonly ConcurrentQueue<WaitParam<TI, TO>> waitList = new();
+
+    private SemaphoreSlim workersLock;// = new SemaphoreSlim(1);
+
+    public async Task<TO> HandleAsync(TI param)
+    {
+        var waitData = new WaitParam<TI,TO>() { Data = param, Waiter = new SemaphoreSlim(0), Exception = null };
+        waitList.Enqueue(waitData);
+        TryStartHandler();
+        await waitData.Waiter.WaitAsync();
+        if (waitData.Exception is not null)
+        {
+            throw waitData.Exception;
+        }
+        return waitData.Result;
+    }
+
+    private void TryStartHandler()
+    {
+        if (!workersLock.Wait(0))
+        {
+            return;
+        }
+
+        if (waitList.Count == 0)
+        {
+            workersLock.Release();
+            return;
+        }
+
+        _ = StartHandleTask();
+    }
+
+    private async Task StartHandleTask()
+    {
+        var timer = Stopwatch.StartNew();
+        List<long> times = new();
+
+        var requests = new List<WaitParam<TI,TO>>();
+
+        while (waitList.TryDequeue(out var handle))
+        {
+            requests.Add(handle);
+        }
+        times.Add(timer.ElapsedMilliseconds);
+
+        int cnt = 0;
+        Exception lastException = null;
+        var datas = requests.Select(x => x.Data).ToList();
+
+        for (; cnt < maxRetry; cnt++)
+        {
+            var bundleHandledata = new BundleHandlerData<TI, TO>(datas);
+            try
+            {
+                await handleFunc(bundleHandledata);
+            }
+            catch (Exception e)
+            {
+                lastException = e;
+            }
+
+            var completedKeys = bundleHandledata.CompletedDatas.Select(x => x.Key).ToList();
+            var completedRequests = requests.Where(x => completedKeys.Any(y=> y == x.Data)).ToList();
+            foreach (var request in completedRequests)
+            {
+                var result = bundleHandledata.CompletedDatas.FirstOrDefault(x => x.Key == request.Data);
+                request.Result = result.Value;
+                request.Waiter.Release();
+            }
+
+            //datas = datas.Except(bundleHandledata.CompletedDatas).ToList();
+            datas = datas.Where(x => !completedKeys.Contains(x)).ToList();
+
+            if (datas == null || datas.Count == 0)
+            {
+                break;
+            }
+            logger.LogError(lastException?.Message);
+            times.Add(timer.ElapsedMilliseconds);
+        }
+
+        var uncompletedRequests = requests.Where(x => datas.Contains(x.Data)).ToList();
+        foreach (var request in uncompletedRequests)
+        {
+            request.Exception = lastException;
+            request.Waiter.Release();
+        }
+        workersLock.Release();
+
+        timer.Stop();
+        if (timer.ElapsedMilliseconds > 1000)
+        {
+            logger.LogWarning($"StartHandleTask {string.Join("/", times)}");
+        }
+
+        TryStartHandler();
+    }
+}
+
+public class BundleHandlerData<TI,TO>
+{
+    public List<TI> Datas { get; set; }
+    public List<KeyValuePair<TI,TO>> CompletedDatas { get; set; }
+
+    public BundleHandlerData(List<TI> datas)
+    {
+        Datas = datas;
+        CompletedDatas = new();
+    }
+
+    public void AddCompletedData(TI competedData, TO result)
+    {
+        CompletedDatas.Add(new KeyValuePair<TI, TO>(competedData, result)); ;
+    }
+}
+
+internal class WaitParam<TI,TO>
+{
+    public TI Data { get; init; }
+    public TO Result { get; set; }
+    public SemaphoreSlim Waiter { get; init; }
+    public Exception Exception { get; set; }
 }

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

@@ -1,246 +1,246 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.WSServer.Service.DbService;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Data;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Helper;
-
-public class MeterValueGroupSingleHandler
-{
-    public MeterValueGroupSingleHandler(
-        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
-		ISqlConnectionFactory<MeterValueDBContext> connectionFactory,
-        IConfiguration configuration,
-        ILogger<MeterValueGroupSingleHandler> logger)
-    {
-        this.meterValueDbContextFactory = meterValueDbContextFactory;
-        this.connectionFactory = connectionFactory;
-        this.configuration = configuration;
-        this.logger = logger;
-
-        //singleWorkLock = new (_WorkerCnt);
-        //singleHandleTask = StartHandleTask();
-        //this.meterValueConnectionString = configuration.GetConnectionString("MeterValueDBContext");
-
-        var workerCnt = 20;
-        _handleTasks = new Task[workerCnt];
-        for (int cnt = 0; cnt < workerCnt; cnt++)
-        {
-            _handleTasks[cnt] = StartHandleTask();
-        }
-    }
-
-    private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
-    private readonly ISqlConnectionFactory<MeterValueDBContext> connectionFactory;
-    private readonly IConfiguration configuration;
-
-    //private readonly Func<IEnumerable<T>, Task> handleFunc;
-    private readonly ILogger logger;
-    //private readonly string meterValueConnectionString;
-    private readonly BlockingCollection<(InsertMeterValueParam param, SemaphoreSlim waitLock)> blockQueue = new();
-    private static Queue<string> _existTables = new();
-    //private SemaphoreSlim singleWorkLock;// = new SemaphoreSlim(1);
-    private bool IsStarted = false;
-    private Task singleHandleTask;
-    private Task[] _handleTasks;
-
-    public Task HandleAsync(InsertMeterValueParam param)
-    {
-        IsStarted = true;
-
-        SemaphoreSlim reqLock = new(0);
-        blockQueue.Add((param, reqLock));
-        //TryStartHandler();
-        return reqLock.WaitAsync();
-    }
-
-    //private void TryStartHandler()
-    //{
-    //    if (!singleWorkLock.Wait(0))
-    //    {
-    //        return;
-    //    }
-
-    //    if (waitList.Count == 0)
-    //    {
-    //        singleWorkLock.Release();
-    //        return;
-    //    }
-
-    //    singleHandleTask = StartHandleTask();
-    //}
-
-    private Task StartHandleTask()
-    {
-        return Task.Run(async () => {
-            while (true)
-            {
-                var handleList = new List<(InsertMeterValueParam param, SemaphoreSlim waitLock)>();
-                try
-                {
-                    var startData = blockQueue.Take();
-                    handleList.Add(startData);
-                }
-                catch (InvalidOperationException e)
-                {
-                    logger.LogError(e, "blockQueue.Take Error");
-                    break;
-                }
-
-                var watch = Stopwatch.StartNew();
-                long t0, t1, t2, t3;
-
-                while (blockQueue.TryTake(out var data))
-                {
-                    handleList.Add(data);
-                }
-                t0 = watch.ElapsedMilliseconds;
-                var parms = handleList.Select(x => x.param).ToList();
-                t1 = watch.ElapsedMilliseconds;
-                await BundleInsertWithDapper(parms).ConfigureAwait(false);
-                t2 = watch.ElapsedMilliseconds;
-
-                foreach (var handled in handleList)
-                {
-                    handled.waitLock.Release();
-                }
-                watch.Stop();
-                t3 = watch.ElapsedMilliseconds;
-                if (t3 > 1000)
-                {
-                    logger.LogWarning("MeterValue StartHandleTask {0}/{1}/{2}/{3}", t0, t1, t2, t3);
-                }
-            }
-        });
-    }
-
-    private async Task BundleInsertWithDapper(IEnumerable<InsertMeterValueParam> parms)
-    {
-        var watch = Stopwatch.StartNew();
-        long t0, t1, t2, t3;
-
-        var parmsList = parms.ToList();
-        t0 = watch.ElapsedMilliseconds;
-        foreach (var param in parms)
-        {
-            if (!await GetTableExist(param.createdOn))
-            {
-                await InsertWithStoredProcedure(param);
-                parmsList.Remove(param);
-            }
-            t1 = watch.ElapsedMilliseconds;
-            watch.Stop();
-            if (t1 > 500)
-            {
-                logger.LogWarning("MeterValue InsertWithStoredProcedure {0}/{1}", t0, t1);
-            }
-        }
-
-        t1 = watch.ElapsedMilliseconds;
-        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
-        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
-
-        t2 = watch.ElapsedMilliseconds;
-        foreach (var group in gruopParams)
-        {
-            using SqlConnection sqlConnection = await connectionFactory.CreateAsync();
-            using var tans = sqlConnection.BeginTransaction();
-
-            var tableName = group.Key;
-            string command = $"""
-                INSERT INTO {tableName} (ConnectorId, Value, CreatedOn, ContextId, FormatId, MeasurandId, PhaseId, LocationId, UnitId, ChargeBoxId, TransactionId)
-                VALUES (@ConnectorId, @Value, @CreatedOn, @ContextId, @FormatId, @MeasurandId, @PhaseId, @LocationId, @UnitId, @ChargeBoxId, @TransactionId);
-                """;
-            foreach (var param in group)
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("ConnectorId", param.connectorId, DbType.Int16);
-                parameters.Add("Value", param.value, DbType.Decimal, precision: 18, scale: 8);
-                parameters.Add("CreatedOn", param.createdOn, DbType.DateTime);
-                parameters.Add("ContextId", param.contextId, DbType.Int32);
-                parameters.Add("FormatId", param.formatId, DbType.Int32);
-                parameters.Add("MeasurandId", param.measurandId, DbType.Int32);
-                parameters.Add("PhaseId", param.phaseId, DbType.Int32);
-                parameters.Add("LocationId", param.locationId, DbType.Int32);
-                parameters.Add("UnitId", param.unitId, DbType.Int32);
-                parameters.Add("ChargeBoxId", param.chargeBoxId, DbType.String, size: 50);
-                parameters.Add("TransactionId", param.transactionId, DbType.Int32);
-                await sqlConnection.ExecuteAsync(command, parameters, tans);
-            }
-
-            await tans.CommitAsync();
-        }
-
-        watch.Stop();
-        t3 = watch.ElapsedMilliseconds;
-        if (t3 > 400)
-        {
-            logger.LogWarning("MeterValue Dapper {0}/{1}/{2}/{3}", t0, t1, t2, t3);
-        }
-    }
-
-    private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
-    {
-        var tableName = GetTableName(tableDateTime);
-        if (_existTables.Contains(tableName))
-        {
-            return true;
-        }
-
-        FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
-
-        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
-        var resultList = await db.Database.SqlQuery<int>(checkTableSql)?.ToListAsync();
-
-        if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
-        {
-            _existTables.Enqueue(tableName);
-            if (_existTables.Count > 30)
-            {
-                _existTables.TryDequeue(out _);
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    private async Task InsertWithStoredProcedure(InsertMeterValueParam param)
-    {
-        using var db = 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: 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);
-
-        db.Database.ExecuteSqlRaw(sp, parameter.ToArray());
-    }
-
-    private static string GetTableName(DateTime dateTime)
-        => $"ConnectorMeterValueRecord{dateTime:yyMMdd}";
-}
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.WSServer.Service.DbService;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Helper;
+
+public class MeterValueGroupSingleHandler
+{
+    public MeterValueGroupSingleHandler(
+        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
+		ISqlConnectionFactory<MeterValueDBContext> connectionFactory,
+        IConfiguration configuration,
+        ILogger<MeterValueGroupSingleHandler> logger)
+    {
+        this.meterValueDbContextFactory = meterValueDbContextFactory;
+        this.connectionFactory = connectionFactory;
+        this.configuration = configuration;
+        this.logger = logger;
+
+        //singleWorkLock = new (_WorkerCnt);
+        //singleHandleTask = StartHandleTask();
+        //this.meterValueConnectionString = configuration.GetConnectionString("MeterValueDBContext");
+
+        var workerCnt = 20;
+        _handleTasks = new Task[workerCnt];
+        for (int cnt = 0; cnt < workerCnt; cnt++)
+        {
+            _handleTasks[cnt] = StartHandleTask();
+        }
+    }
+
+    private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
+    private readonly ISqlConnectionFactory<MeterValueDBContext> connectionFactory;
+    private readonly IConfiguration configuration;
+
+    //private readonly Func<IEnumerable<T>, Task> handleFunc;
+    private readonly ILogger logger;
+    //private readonly string meterValueConnectionString;
+    private readonly BlockingCollection<(InsertMeterValueParam param, SemaphoreSlim waitLock)> blockQueue = new();
+    private static Queue<string> _existTables = new();
+    //private SemaphoreSlim singleWorkLock;// = new SemaphoreSlim(1);
+    private bool IsStarted = false;
+    private Task singleHandleTask;
+    private Task[] _handleTasks;
+
+    public Task HandleAsync(InsertMeterValueParam param)
+    {
+        IsStarted = true;
+
+        SemaphoreSlim reqLock = new(0);
+        blockQueue.Add((param, reqLock));
+        //TryStartHandler();
+        return reqLock.WaitAsync();
+    }
+
+    //private void TryStartHandler()
+    //{
+    //    if (!singleWorkLock.Wait(0))
+    //    {
+    //        return;
+    //    }
+
+    //    if (waitList.Count == 0)
+    //    {
+    //        singleWorkLock.Release();
+    //        return;
+    //    }
+
+    //    singleHandleTask = StartHandleTask();
+    //}
+
+    private Task StartHandleTask()
+    {
+        return Task.Run(async () => {
+            while (true)
+            {
+                var handleList = new List<(InsertMeterValueParam param, SemaphoreSlim waitLock)>();
+                try
+                {
+                    var startData = blockQueue.Take();
+                    handleList.Add(startData);
+                }
+                catch (InvalidOperationException e)
+                {
+                    logger.LogError(e, "blockQueue.Take Error");
+                    break;
+                }
+
+                var watch = Stopwatch.StartNew();
+                long t0, t1, t2, t3;
+
+                while (blockQueue.TryTake(out var data))
+                {
+                    handleList.Add(data);
+                }
+                t0 = watch.ElapsedMilliseconds;
+                var parms = handleList.Select(x => x.param).ToList();
+                t1 = watch.ElapsedMilliseconds;
+                await BundleInsertWithDapper(parms).ConfigureAwait(false);
+                t2 = watch.ElapsedMilliseconds;
+
+                foreach (var handled in handleList)
+                {
+                    handled.waitLock.Release();
+                }
+                watch.Stop();
+                t3 = watch.ElapsedMilliseconds;
+                if (t3 > 1000)
+                {
+                    logger.LogWarning("MeterValue StartHandleTask {0}/{1}/{2}/{3}", t0, t1, t2, t3);
+                }
+            }
+        });
+    }
+
+    private async Task BundleInsertWithDapper(IEnumerable<InsertMeterValueParam> parms)
+    {
+        var watch = Stopwatch.StartNew();
+        long t0, t1, t2, t3;
+
+        var parmsList = parms.ToList();
+        t0 = watch.ElapsedMilliseconds;
+        foreach (var param in parms)
+        {
+            if (!await GetTableExist(param.createdOn))
+            {
+                await InsertWithStoredProcedure(param);
+                parmsList.Remove(param);
+            }
+            t1 = watch.ElapsedMilliseconds;
+            watch.Stop();
+            if (t1 > 500)
+            {
+                logger.LogWarning("MeterValue InsertWithStoredProcedure {0}/{1}", t0, t1);
+            }
+        }
+
+        t1 = watch.ElapsedMilliseconds;
+        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
+        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
+
+        t2 = watch.ElapsedMilliseconds;
+        foreach (var group in gruopParams)
+        {
+            using SqlConnection sqlConnection = await connectionFactory.CreateAsync();
+            using var tans = sqlConnection.BeginTransaction();
+
+            var tableName = group.Key;
+            string command = $"""
+                INSERT INTO {tableName} (ConnectorId, Value, CreatedOn, ContextId, FormatId, MeasurandId, PhaseId, LocationId, UnitId, ChargeBoxId, TransactionId)
+                VALUES (@ConnectorId, @Value, @CreatedOn, @ContextId, @FormatId, @MeasurandId, @PhaseId, @LocationId, @UnitId, @ChargeBoxId, @TransactionId);
+                """;
+            foreach (var param in group)
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("ConnectorId", param.connectorId, DbType.Int16);
+                parameters.Add("Value", param.value, DbType.Decimal, precision: 18, scale: 8);
+                parameters.Add("CreatedOn", param.createdOn, DbType.DateTime);
+                parameters.Add("ContextId", param.contextId, DbType.Int32);
+                parameters.Add("FormatId", param.formatId, DbType.Int32);
+                parameters.Add("MeasurandId", param.measurandId, DbType.Int32);
+                parameters.Add("PhaseId", param.phaseId, DbType.Int32);
+                parameters.Add("LocationId", param.locationId, DbType.Int32);
+                parameters.Add("UnitId", param.unitId, DbType.Int32);
+                parameters.Add("ChargeBoxId", param.chargeBoxId, DbType.String, size: 50);
+                parameters.Add("TransactionId", param.transactionId, DbType.Int32);
+                await sqlConnection.ExecuteAsync(command, parameters, tans);
+            }
+
+            await tans.CommitAsync();
+        }
+
+        watch.Stop();
+        t3 = watch.ElapsedMilliseconds;
+        if (t3 > 400)
+        {
+            logger.LogWarning("MeterValue Dapper {0}/{1}/{2}/{3}", t0, t1, t2, t3);
+        }
+    }
+
+    private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
+    {
+        var tableName = GetTableName(tableDateTime);
+        if (_existTables.Contains(tableName))
+        {
+            return true;
+        }
+
+        FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
+
+        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
+        var resultList = await db.Database.SqlQuery<int>(checkTableSql)?.ToListAsync();
+
+        if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
+        {
+            _existTables.Enqueue(tableName);
+            if (_existTables.Count > 30)
+            {
+                _existTables.TryDequeue(out _);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    private async Task InsertWithStoredProcedure(InsertMeterValueParam param)
+    {
+        using var db = 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: 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);
+
+        db.Database.ExecuteSqlRaw(sp, parameter.ToArray());
+    }
+
+    private static string GetTableName(DateTime dateTime)
+        => $"ConnectorMeterValueRecord{dateTime:yyMMdd}";
+}

+ 159 - 159
EVCB_OCPP.WSServer/HostedProtalServer.cs

@@ -1,159 +1,159 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Extensions;
-using EVCB_OCPP.Service;
-using EVCB_OCPP.WSServer.Fake;
-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;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer
-{
-    public static class HostedProtalServer
-    {
-        public static void AddProtalServer(this IServiceCollection services, IConfiguration configuration)
-        {
-            services.AddMemoryCache();
-            //services.AddSingleton<IMemoryCache, DummyMemoryCache>();
-
-            services.AddPortalServerDatabase(configuration);
-            services.AddBusinessServiceFactory();
-
-            //services.AddTransient<OCPPWSServer>();
-            //services.AddTransient<IOCPPWSServerFactory, OCPPWSServerFactory>();
-            services.AddHeaderRecordService();
-
-            services.AddSingleton<MeterValueDbService>();
-            services.AddSingleton<WebDbService>();
-            services.AddSingleton<IMainDbService, MainDbService>();
-            services.AddSingleton<IConnectionLogdbService, ConnectionLogdbService>();
-
-            services.AddOcppWsServer();
-
-            services.AddSingleton<ServerMessageService>();
-            services.AddSingleton<StationConfigService>();
-
-            services.AddSingleton<ConfirmWaitingMessageSerevice>();
-            services.AddTransient<ProfileHandler>();
-
-            services.AddSingleton<ProtalServer>();
-            services.AddHostedService<ProtalServer>(p => p.GetRequiredService<ProtalServer>());
-
-            services.AddProtalServerJob();
-        }
-
-        internal static void AddPortalServerDatabase(this IServiceCollection services, IConfiguration configuration)
-        {
-            services
-                .AddMainDbContext(configuration)
-                .AddMeterValueDbContext(configuration)
-                .AddConnectionLogDbContext(configuration)
-                .AddWebDBConetext(configuration);
-        }
-
-        public static void AddProtalServerJob(this IServiceCollection services)
-        {
-            services.AddQuartz(q => {
-                q.UseMicrosoftDependencyInjectionJobFactory();
-
-                q.ScheduleJob<ServerUpdateJob>(trigger =>
-                    trigger
-                    .WithIdentity("ServerUpdateJobTrigger")
-                    .StartNow()
-                    .WithSimpleSchedule(x => x
-                        .WithIntervalInMinutes(3)
-                        .RepeatForever())
-                );
-
-                q.ScheduleJob<ServerSetFeeJob>(trigger =>
-                    trigger
-                    .WithIdentity("ServerSetFeeJobTrigger")
-                    .StartNow()
-                    .WithSimpleSchedule(x => x
-                        .WithIntervalInMinutes(1)
-                        .RepeatForever())
-                );
-
-                q.ScheduleJob<ServerMessageJob>(trigger =>
-                    trigger
-                    .WithIdentity("ServerMessageJobTrigger")
-                    .StartNow()
-                    .WithSimpleSchedule(x => x
-                        .WithInterval(TimeSpan.FromMilliseconds(500))
-                        .RepeatForever())
-                );
-
-                q.ScheduleJob<HeartBeatCheckJob>(trigger => 
-                    trigger
-                    .WithIdentity("HeartBeatCheckJobTrigger")
-                    .StartNow()
-                    .WithSimpleSchedule(x => x
-                        .WithIntervalInSeconds(30)
-                        .RepeatForever())
-                );
-
-                q.ScheduleJob<HealthCheckTriggerJob>(trigger =>
-                    trigger
-                    .WithIdentity("HealthCheckTriggerJobTrigger")
-                    .StartNow()
-                    .WithSimpleSchedule(x => x
-                        .WithIntervalInMinutes(1)
-                        .RepeatForever())
-                );
-
-                q.ScheduleJob<SmartChargingJob>(trigger =>
-                    trigger
-                    .WithIdentity("SmartChargingJobTrigger")
-                    .StartNow()
-                    .WithSimpleSchedule(x => x
-                        .WithIntervalInMinutes(1)
-                        .RepeatForever())
-                );
-
-                q.ScheduleJob<DenyModelCheckJob>(trigger =>
-                    trigger
-                    .WithIdentity("DenyModelCheckJobTrigger")
-                    .StartNow()
-                    .WithSimpleSchedule(x => x
-                        .WithIntervalInMinutes(5)
-                        .RepeatForever())
-                );
-
-                q.ScheduleJob<StationConfigPollingJob>(trigger =>
-                    trigger
-                    .WithIdentity("StationConfigPollingJobTrigger")
-                    .StartNow()
-                    .WithSimpleSchedule(x => x
-                        .WithIntervalInSeconds(30)
-                        .RepeatForever())
-                );
-
-                //q.ScheduleJob<GoogleCheckJob>(trigger =>
-                //    trigger
-                //    .WithIdentity("GoogleCheckJobTrigger")
-                //    .StartNow()
-                //    .WithSimpleSchedule(x => x
-                //        .WithIntervalInSeconds(5)
-                //        .RepeatForever())
-                //);
-            });
-
-            services.AddQuartzHostedService(opt =>
-            {
-                opt.WaitForJobsToComplete = true;
-            });
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Extensions;
+using EVCB_OCPP.Service;
+using EVCB_OCPP.WSServer.Fake;
+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;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer
+{
+    public static class HostedProtalServer
+    {
+        public static void AddProtalServer(this IServiceCollection services, IConfiguration configuration)
+        {
+            services.AddMemoryCache();
+            //services.AddSingleton<IMemoryCache, DummyMemoryCache>();
+
+            services.AddPortalServerDatabase(configuration);
+            services.AddBusinessServiceFactory();
+
+            //services.AddTransient<OCPPWSServer>();
+            //services.AddTransient<IOCPPWSServerFactory, OCPPWSServerFactory>();
+            services.AddHeaderRecordService();
+
+            services.AddSingleton<MeterValueDbService>();
+            services.AddSingleton<WebDbService>();
+            services.AddSingleton<IMainDbService, MainDbService>();
+            services.AddSingleton<IConnectionLogdbService, ConnectionLogdbService>();
+
+            services.AddOcppWsServer();
+
+            services.AddSingleton<ServerMessageService>();
+            services.AddSingleton<StationConfigService>();
+
+            services.AddSingleton<ConfirmWaitingMessageSerevice>();
+            services.AddTransient<ProfileHandler>();
+
+            services.AddSingleton<ProtalServer>();
+            services.AddHostedService<ProtalServer>(p => p.GetRequiredService<ProtalServer>());
+
+            services.AddProtalServerJob();
+        }
+
+        internal static void AddPortalServerDatabase(this IServiceCollection services, IConfiguration configuration)
+        {
+            services
+                .AddMainDbContext(configuration)
+                .AddMeterValueDbContext(configuration)
+                .AddConnectionLogDbContext(configuration)
+                .AddWebDBConetext(configuration);
+        }
+
+        public static void AddProtalServerJob(this IServiceCollection services)
+        {
+            services.AddQuartz(q => {
+                q.UseMicrosoftDependencyInjectionJobFactory();
+
+                q.ScheduleJob<ServerUpdateJob>(trigger =>
+                    trigger
+                    .WithIdentity("ServerUpdateJobTrigger")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(3)
+                        .RepeatForever())
+                );
+
+                q.ScheduleJob<ServerSetFeeJob>(trigger =>
+                    trigger
+                    .WithIdentity("ServerSetFeeJobTrigger")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(1)
+                        .RepeatForever())
+                );
+
+                q.ScheduleJob<ServerMessageJob>(trigger =>
+                    trigger
+                    .WithIdentity("ServerMessageJobTrigger")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithInterval(TimeSpan.FromMilliseconds(500))
+                        .RepeatForever())
+                );
+
+                q.ScheduleJob<HeartBeatCheckJob>(trigger => 
+                    trigger
+                    .WithIdentity("HeartBeatCheckJobTrigger")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInSeconds(30)
+                        .RepeatForever())
+                );
+
+                q.ScheduleJob<HealthCheckTriggerJob>(trigger =>
+                    trigger
+                    .WithIdentity("HealthCheckTriggerJobTrigger")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(1)
+                        .RepeatForever())
+                );
+
+                q.ScheduleJob<SmartChargingJob>(trigger =>
+                    trigger
+                    .WithIdentity("SmartChargingJobTrigger")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(1)
+                        .RepeatForever())
+                );
+
+                q.ScheduleJob<DenyModelCheckJob>(trigger =>
+                    trigger
+                    .WithIdentity("DenyModelCheckJobTrigger")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInMinutes(5)
+                        .RepeatForever())
+                );
+
+                q.ScheduleJob<StationConfigPollingJob>(trigger =>
+                    trigger
+                    .WithIdentity("StationConfigPollingJobTrigger")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInSeconds(30)
+                        .RepeatForever())
+                );
+
+                //q.ScheduleJob<GoogleCheckJob>(trigger =>
+                //    trigger
+                //    .WithIdentity("GoogleCheckJobTrigger")
+                //    .StartNow()
+                //    .WithSimpleSchedule(x => x
+                //        .WithIntervalInSeconds(5)
+                //        .RepeatForever())
+                //);
+            });
+
+            services.AddQuartzHostedService(opt =>
+            {
+                opt.WaitForJobsToComplete = true;
+            });
+        }
+    }
+}

+ 67 - 67
EVCB_OCPP.WSServer/Jobs/DenyModelCheckJob.cs

@@ -1,67 +1,67 @@
-using Dapper;
-using EVCB_OCPP.WSServer.Service.DbService;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-public class DenyModelCheckJob : IJob
-{
-    public DenyModelCheckJob(
-        ProtalServer protalServer,
-        WebDbService webDbService,
-        //IConfiguration configuration,
-        ILogger<DenyModelCheckJob> logger)
-    {
-        //this.webConnectionString = configuration.GetConnectionString("WebDBContext");
-        this.protalServer = protalServer;
-        this.webDbService = webDbService;
-        this.logger = logger;
-    }
-
-    //private readonly string webConnectionString;
-    private readonly ProtalServer protalServer;
-    private readonly WebDbService webDbService;
-    private readonly ILogger<DenyModelCheckJob> logger;
-
-    public async Task Execute(IJobExecutionContext context)
-    {
-        //logger.LogDebug("{0} Started", nameof(DenyModelCheckJob));
-        try
-        {
-            GlobalConfig.DenyModelNames = await webDbService.GetDenyModelNames(context.CancellationToken);
-            //logger.LogDebug("Current DenyList:[{0}]", string.Join(",", GlobalConfig.DenyModelNames));
-
-            if (string.IsNullOrEmpty(GlobalConfig.DenyModelNames[0]))
-            {
-                return;
-            }
-
-            Dictionary<string, WsClientData> _copyClientDic = protalServer.GetClientDic();
-            foreach (var denyName in GlobalConfig.DenyModelNames)
-            {
-                var removeClients = _copyClientDic.Where(x => x.Key.StartsWith(denyName)).Select(x => x.Value).ToList();
-                foreach (var session in removeClients)
-                {
-                    //Console.WriteLine(string.Format("Server forced to shut down ChargeBox ({0}: Reason: DenyModelName-{1}", session.ChargeBoxId, denyName));
-                    logger.LogInformation(string.Format("Server forced to shut down ChargeBox ({0}: Reason: DenyModelName-{1}", session.ChargeBoxId, denyName));
-                    protalServer.RemoveClient(session, "DenyModel");
-                }
-            }
-        }
-        catch (Exception ex)
-        {
-            logger.LogError("DenyModelCheckTrigger  Ex:{0}", ex.ToString());
-        }
-    }
-}
+using Dapper;
+using EVCB_OCPP.WSServer.Service.DbService;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs;
+
+[DisallowConcurrentExecution]
+public class DenyModelCheckJob : IJob
+{
+    public DenyModelCheckJob(
+        ProtalServer protalServer,
+        WebDbService webDbService,
+        //IConfiguration configuration,
+        ILogger<DenyModelCheckJob> logger)
+    {
+        //this.webConnectionString = configuration.GetConnectionString("WebDBContext");
+        this.protalServer = protalServer;
+        this.webDbService = webDbService;
+        this.logger = logger;
+    }
+
+    //private readonly string webConnectionString;
+    private readonly ProtalServer protalServer;
+    private readonly WebDbService webDbService;
+    private readonly ILogger<DenyModelCheckJob> logger;
+
+    public async Task Execute(IJobExecutionContext context)
+    {
+        //logger.LogDebug("{0} Started", nameof(DenyModelCheckJob));
+        try
+        {
+            GlobalConfig.DenyModelNames = await webDbService.GetDenyModelNames(context.CancellationToken);
+            //logger.LogDebug("Current DenyList:[{0}]", string.Join(",", GlobalConfig.DenyModelNames));
+
+            if (string.IsNullOrEmpty(GlobalConfig.DenyModelNames[0]))
+            {
+                return;
+            }
+
+            Dictionary<string, WsClientData> _copyClientDic = protalServer.GetClientDic();
+            foreach (var denyName in GlobalConfig.DenyModelNames)
+            {
+                var removeClients = _copyClientDic.Where(x => x.Key.StartsWith(denyName)).Select(x => x.Value).ToList();
+                foreach (var session in removeClients)
+                {
+                    //Console.WriteLine(string.Format("Server forced to shut down ChargeBox ({0}: Reason: DenyModelName-{1}", session.ChargeBoxId, denyName));
+                    logger.LogInformation(string.Format("Server forced to shut down ChargeBox ({0}: Reason: DenyModelName-{1}", session.ChargeBoxId, denyName));
+                    protalServer.RemoveClient(session, "DenyModel");
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            logger.LogError("DenyModelCheckTrigger  Ex:{0}", ex.ToString());
+        }
+    }
+}

+ 43 - 43
EVCB_OCPP.WSServer/Jobs/HealthCheckTriggerJob.cs

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

+ 71 - 71
EVCB_OCPP.WSServer/Jobs/HeartBeatCheckJob.cs

@@ -1,71 +1,71 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.WSServer.Service.DbService;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-public class HeartBeatCheckJob : IJob
-{
-    public HeartBeatCheckJob(
-        ProtalServer protalServer,
-        IDbContextFactory<MainDBContext> maindbContextFactory,
-        IMainDbService mainDbService,
-        ILogger<HeartBeatCheckJob> logger)
-    {
-        this.protalServer = protalServer;
-        this.maindbContextFactory = maindbContextFactory;
-        this.mainDbService = mainDbService;
-        this.logger = logger;
-    }
-
-    private readonly ProtalServer protalServer;
-    private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
-    private readonly IMainDbService mainDbService;
-    private readonly ILogger<HeartBeatCheckJob> logger;
-
-    public async Task Execute(IJobExecutionContext context)
-    {
-        //logger.LogDebug("{0} Started", nameof(HeartBeatCheckJob));
-        try
-        {
-            Stopwatch watch = new Stopwatch();
-            Dictionary<string, WsClientData> _copyClientDic = protalServer.GetClientDic();
-
-            var cdt = DateTime.UtcNow;
-            var clients = _copyClientDic.Where(x => x.Value.LastActiveTime > cdt.AddSeconds(-120)).Select(x => x.Value).ToList();
-
-            watch.Start();
-
-            //var datas = new List<Machine>();
-            //foreach (var session in clients)
-            //{
-            //    var machine = new Machine() { Id = session.MachineId, HeartbeatUpdatedOn = cdt, ConnectionType = session.UriScheme.Equals("wss") ? 2 : 1 };
-            //    datas.Add(machine);
-            //}
-            //await mainDbService.UpdateHeartBeats(datas);
-            var toUpdateMachineIds = clients.Select(x => x.MachineId).ToList();
-            await mainDbService.UpdateHeartBeats(toUpdateMachineIds);
-
-            watch.Stop();
-            if (watch.ElapsedMilliseconds / 1000 > 5)
-            {
-                logger.LogCritical("Update HeartBeatCheckTrigger cost " + watch.ElapsedMilliseconds / 1000 + " seconds.");
-            }
-        }
-        catch (Exception ex)
-        {
-            Console.WriteLine("***********************************************************");
-            logger.LogError(string.Format("HeartBeatCheckTrigger  Ex:{0}", ex.ToString()));
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.WSServer.Service.DbService;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs;
+
+[DisallowConcurrentExecution]
+public class HeartBeatCheckJob : IJob
+{
+    public HeartBeatCheckJob(
+        ProtalServer protalServer,
+        IDbContextFactory<MainDBContext> maindbContextFactory,
+        IMainDbService mainDbService,
+        ILogger<HeartBeatCheckJob> logger)
+    {
+        this.protalServer = protalServer;
+        this.maindbContextFactory = maindbContextFactory;
+        this.mainDbService = mainDbService;
+        this.logger = logger;
+    }
+
+    private readonly ProtalServer protalServer;
+    private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+    private readonly IMainDbService mainDbService;
+    private readonly ILogger<HeartBeatCheckJob> logger;
+
+    public async Task Execute(IJobExecutionContext context)
+    {
+        //logger.LogDebug("{0} Started", nameof(HeartBeatCheckJob));
+        try
+        {
+            Stopwatch watch = new Stopwatch();
+            Dictionary<string, WsClientData> _copyClientDic = protalServer.GetClientDic();
+
+            var cdt = DateTime.UtcNow;
+            var clients = _copyClientDic.Where(x => x.Value.LastActiveTime > cdt.AddSeconds(-120)).Select(x => x.Value).ToList();
+
+            watch.Start();
+
+            //var datas = new List<Machine>();
+            //foreach (var session in clients)
+            //{
+            //    var machine = new Machine() { Id = session.MachineId, HeartbeatUpdatedOn = cdt, ConnectionType = session.UriScheme.Equals("wss") ? 2 : 1 };
+            //    datas.Add(machine);
+            //}
+            //await mainDbService.UpdateHeartBeats(datas);
+            var toUpdateMachineIds = clients.Select(x => x.MachineId).ToList();
+            await mainDbService.UpdateHeartBeats(toUpdateMachineIds);
+
+            watch.Stop();
+            if (watch.ElapsedMilliseconds / 1000 > 5)
+            {
+                logger.LogCritical("Update HeartBeatCheckTrigger cost " + watch.ElapsedMilliseconds / 1000 + " seconds.");
+            }
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine("***********************************************************");
+            logger.LogError(string.Format("HeartBeatCheckTrigger  Ex:{0}", ex.ToString()));
+        }
+    }
+}

+ 217 - 217
EVCB_OCPP.WSServer/Jobs/ServerMessageJob.cs

@@ -1,217 +1,217 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.MainDb;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.Core;
-using EVCB_OCPP.WSServer.Message;
-using EVCB_OCPP.WSServer.Service;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using Microsoft.VisualBasic;
-using Newtonsoft.Json;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-public class ServerMessageJob : IJob
-{
-    public ServerMessageJob(
-        ProtalServer protalServer,
-        ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice,
-        //VendorIdUpdateService vendorIdReplaceService,
-        IConfiguration configuration,
-        IDbContextFactory<MainDBContext> maindbContextFactory,
-        ILogger<ServerMessageJob> logger)
-    {
-        this.protalServer = protalServer;
-        this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
-        //this.vendorIdUpdateService = vendorIdReplaceService;
-        this.maindbContextFactory = maindbContextFactory;
-        this.logger = logger;
-    }
-
-    private readonly ProtalServer protalServer;
-    private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
-    //private readonly VendorIdUpdateService vendorIdUpdateService;
-    private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
-    private readonly ILogger<ServerMessageJob> logger;
-
-    public async Task Execute(IJobExecutionContext context)
-    {
-        //logger.LogDebug("{0} Started", nameof(ServerMessageJob));
-        try
-        {
-            await ExecuteTrigger();
-        }
-        catch (Exception ex)
-        {
-            logger.LogError("ServerMessageTrigger  Ex:{0}", ex.ToString());
-        }
-    }
-
-    private Task ExecuteTrigger()
-    {
-        var clientDic = protalServer.GetClientDic();
-
-        confirmWaitingMessageSerevice.RemoveExpiredConfirmMessage();
-        ResendServerMessage(clientDic);
-        return SendNewServerMessage(clientDic);
-    }
-
-    private async Task SendNewServerMessage(Dictionary<string, WsClientData> clientDic)
-    {
-        BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-        DateTime dateTimeNow = DateTime.UtcNow;
-        DateTime startDt = dateTimeNow.AddSeconds(-30);
-        DateTime dt = new DateTime(1991, 1, 1);
-        DateTime currentTime = dateTimeNow;
-        List<ServerMessage> commandList;
-        List<Task> waitTasks = new List<Task>();
-
-        using (var db = await maindbContextFactory.CreateDbContextAsync())
-        {
-            commandList = await db.ServerMessage.Where(c => c.ReceivedOn == dt && c.UpdatedOn == dt && c.CreatedOn >= startDt && c.CreatedOn <= currentTime).AsNoTracking().ToListAsync();
-        }
-        //處理主機傳送的有指令
-        var cmdMachineList = commandList.Select(c => c.ChargeBoxId).Distinct().ToList();
-        if (commandList.Count > 0)
-        {
-            // Console.WriteLine(string.Format("Now:{0} commandList Count:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), commandList.Count));
-        }
-
-        Dictionary<WsClientData, List<ServerMessage>> toSendWsClientDataMsgPair = 
-            commandList
-                .GroupBy(x => x.ChargeBoxId)
-                .Where(x => clientDic.ContainsKey(x.Key))
-                .ToDictionary(x => clientDic[x.Key], x => x.ToList());
-
-        await Parallel.ForEachAsync(toSendWsClientDataMsgPair, async (sendPair, token) => //{  });
-        //foreach (var charger_SN in cmdMachineList)
-        {
-            WsClientData session = sendPair.Key;
-            string charger_SN = session.ChargeBoxId;
-            string uuid = string.Empty;
-
-            //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.ISOCPP20)
-            {
-                //continue;
-                return;
-            }
-
-            string rawRequest = string.Empty;
-
-            List<ServerMessage> cmdList = sendPair.Value;
-
-            var profiles = protalServer.Profiles;
-
-            foreach (var item in cmdList)
-            {
-                IRequest request = null;
-                Actions action = Actions.None;
-                Enum.TryParse(item.OutAction, out action);
-                Type _RequestType = null;
-
-                for (int i = 0; i < profiles.Count; i++)
-                {
-                    var feature = profiles[i].GetFeaturebyAction(item.OutAction);
-
-                    if (feature != null)
-                    {
-                        _RequestType = feature.GetRequestType();
-                        break;
-                    }
-                }
-
-                if (_RequestType != null && item.CreatedBy != "Destroyer")
-                {
-                    request = JsonConvert.DeserializeObject(item.OutRequest, _RequestType) as IRequest;
-                    if (action == Actions.DataTransfer)
-                    {
-                        ReplaceVID(session, action, request);
-                    }
-                    uuid = session.queue.store(request);
-                    rawRequest = BasicMessageHandler.GenerateRequest(uuid, item.OutAction, request);
-                    protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
-                }
-
-
-                if (item.CreatedBy == "Destroyer")
-                {
-
-                    if (_RequestType != null)
-                    {
-                        request = Activator.CreateInstance(_RequestType) as IRequest;
-                        uuid = session.queue.store(request);
-                        rawRequest = BasicMessageHandler.GenerateDestroyRequest(uuid, item.OutAction, item.OutRequest);
-                        protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
-
-                    }
-                    else
-                    {
-
-                        rawRequest = BasicMessageHandler.GenerateDestroyRequest(Guid.NewGuid().ToString(), item.OutAction, item.OutRequest);
-                        protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
-
-                    }
-                }
-
-                var tmpTask = confirmWaitingMessageSerevice.Add(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
-                waitTasks.Add(tmpTask);
-                //protalServer.AddConfirmMessage(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
-
-                await Task.Delay(100);
-
-            }
-        }
-        );
-        await Task.WhenAll(waitTasks);
-    }
-
-    private void ResendServerMessage(Dictionary<string, WsClientData> clientDic)
-    {
-        DateTime dateTimeNow = DateTime.UtcNow;
-        List<NeedConfirmMessage> resendList = confirmWaitingMessageSerevice.GetPendingMessages();
-        foreach (var resendItem in resendList)
-        {
-            WsClientData session;
-            if (clientDic.TryGetValue(resendItem.ChargePointSerialNumber, out session))
-            {
-                if (dateTimeNow.Subtract(resendItem.SentOn).TotalSeconds > 1)
-                {
-                    confirmWaitingMessageSerevice.SignalMessageSended(resendItem);
-                    protalServer.SendMsg(session, resendItem.SentMessage, string.Format("{0} {1}", resendItem.SentAction, "Request"), "");
-                }
-
-            }
-
-        }
-    }
-
-    private void ReplaceVID(WsClientData session, Actions action, IRequest request)
-    {
-        if (action != Actions.DataTransfer ||
-            request is not DataTransferRequest dataTransferRequest)
-        {
-            return;
-        }
-
-        string vid = session.ChargePointVendor;
-        if (string.IsNullOrEmpty(vid))
-        {
-            dataTransferRequest.vendorId = string.Empty;
-            return;
-        }
-        dataTransferRequest.vendorId = vid;
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.MainDb;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.Core;
+using EVCB_OCPP.WSServer.Message;
+using EVCB_OCPP.WSServer.Service;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.VisualBasic;
+using Newtonsoft.Json;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs;
+
+[DisallowConcurrentExecution]
+public class ServerMessageJob : IJob
+{
+    public ServerMessageJob(
+        ProtalServer protalServer,
+        ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice,
+        //VendorIdUpdateService vendorIdReplaceService,
+        IConfiguration configuration,
+        IDbContextFactory<MainDBContext> maindbContextFactory,
+        ILogger<ServerMessageJob> logger)
+    {
+        this.protalServer = protalServer;
+        this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
+        //this.vendorIdUpdateService = vendorIdReplaceService;
+        this.maindbContextFactory = maindbContextFactory;
+        this.logger = logger;
+    }
+
+    private readonly ProtalServer protalServer;
+    private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
+    //private readonly VendorIdUpdateService vendorIdUpdateService;
+    private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+    private readonly ILogger<ServerMessageJob> logger;
+
+    public async Task Execute(IJobExecutionContext context)
+    {
+        //logger.LogDebug("{0} Started", nameof(ServerMessageJob));
+        try
+        {
+            await ExecuteTrigger();
+        }
+        catch (Exception ex)
+        {
+            logger.LogError("ServerMessageTrigger  Ex:{0}", ex.ToString());
+        }
+    }
+
+    private Task ExecuteTrigger()
+    {
+        var clientDic = protalServer.GetClientDic();
+
+        confirmWaitingMessageSerevice.RemoveExpiredConfirmMessage();
+        ResendServerMessage(clientDic);
+        return SendNewServerMessage(clientDic);
+    }
+
+    private async Task SendNewServerMessage(Dictionary<string, WsClientData> clientDic)
+    {
+        BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+        DateTime dateTimeNow = DateTime.UtcNow;
+        DateTime startDt = dateTimeNow.AddSeconds(-30);
+        DateTime dt = new DateTime(1991, 1, 1);
+        DateTime currentTime = dateTimeNow;
+        List<ServerMessage> commandList;
+        List<Task> waitTasks = new List<Task>();
+
+        using (var db = await maindbContextFactory.CreateDbContextAsync())
+        {
+            commandList = await db.ServerMessage.Where(c => c.ReceivedOn == dt && c.UpdatedOn == dt && c.CreatedOn >= startDt && c.CreatedOn <= currentTime).AsNoTracking().ToListAsync();
+        }
+        //處理主機傳送的有指令
+        var cmdMachineList = commandList.Select(c => c.ChargeBoxId).Distinct().ToList();
+        if (commandList.Count > 0)
+        {
+            // Console.WriteLine(string.Format("Now:{0} commandList Count:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), commandList.Count));
+        }
+
+        Dictionary<WsClientData, List<ServerMessage>> toSendWsClientDataMsgPair = 
+            commandList
+                .GroupBy(x => x.ChargeBoxId)
+                .Where(x => clientDic.ContainsKey(x.Key))
+                .ToDictionary(x => clientDic[x.Key], x => x.ToList());
+
+        await Parallel.ForEachAsync(toSendWsClientDataMsgPair, async (sendPair, token) => //{  });
+        //foreach (var charger_SN in cmdMachineList)
+        {
+            WsClientData session = sendPair.Key;
+            string charger_SN = session.ChargeBoxId;
+            string uuid = string.Empty;
+
+            //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.ISOCPP20)
+            {
+                //continue;
+                return;
+            }
+
+            string rawRequest = string.Empty;
+
+            List<ServerMessage> cmdList = sendPair.Value;
+
+            var profiles = protalServer.Profiles;
+
+            foreach (var item in cmdList)
+            {
+                IRequest request = null;
+                Actions action = Actions.None;
+                Enum.TryParse(item.OutAction, out action);
+                Type _RequestType = null;
+
+                for (int i = 0; i < profiles.Count; i++)
+                {
+                    var feature = profiles[i].GetFeaturebyAction(item.OutAction);
+
+                    if (feature != null)
+                    {
+                        _RequestType = feature.GetRequestType();
+                        break;
+                    }
+                }
+
+                if (_RequestType != null && item.CreatedBy != "Destroyer")
+                {
+                    request = JsonConvert.DeserializeObject(item.OutRequest, _RequestType) as IRequest;
+                    if (action == Actions.DataTransfer)
+                    {
+                        ReplaceVID(session, action, request);
+                    }
+                    uuid = session.queue.store(request);
+                    rawRequest = BasicMessageHandler.GenerateRequest(uuid, item.OutAction, request);
+                    protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
+                }
+
+
+                if (item.CreatedBy == "Destroyer")
+                {
+
+                    if (_RequestType != null)
+                    {
+                        request = Activator.CreateInstance(_RequestType) as IRequest;
+                        uuid = session.queue.store(request);
+                        rawRequest = BasicMessageHandler.GenerateDestroyRequest(uuid, item.OutAction, item.OutRequest);
+                        protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
+
+                    }
+                    else
+                    {
+
+                        rawRequest = BasicMessageHandler.GenerateDestroyRequest(Guid.NewGuid().ToString(), item.OutAction, item.OutRequest);
+                        protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
+
+                    }
+                }
+
+                var tmpTask = confirmWaitingMessageSerevice.Add(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
+                waitTasks.Add(tmpTask);
+                //protalServer.AddConfirmMessage(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
+
+                await Task.Delay(100);
+
+            }
+        }
+        );
+        await Task.WhenAll(waitTasks);
+    }
+
+    private void ResendServerMessage(Dictionary<string, WsClientData> clientDic)
+    {
+        DateTime dateTimeNow = DateTime.UtcNow;
+        List<NeedConfirmMessage> resendList = confirmWaitingMessageSerevice.GetPendingMessages();
+        foreach (var resendItem in resendList)
+        {
+            WsClientData session;
+            if (clientDic.TryGetValue(resendItem.ChargePointSerialNumber, out session))
+            {
+                if (dateTimeNow.Subtract(resendItem.SentOn).TotalSeconds > 1)
+                {
+                    confirmWaitingMessageSerevice.SignalMessageSended(resendItem);
+                    protalServer.SendMsg(session, resendItem.SentMessage, string.Format("{0} {1}", resendItem.SentAction, "Request"), "");
+                }
+
+            }
+
+        }
+    }
+
+    private void ReplaceVID(WsClientData session, Actions action, IRequest request)
+    {
+        if (action != Actions.DataTransfer ||
+            request is not DataTransferRequest dataTransferRequest)
+        {
+            return;
+        }
+
+        string vid = session.ChargePointVendor;
+        if (string.IsNullOrEmpty(vid))
+        {
+            dataTransferRequest.vendorId = string.Empty;
+            return;
+        }
+        dataTransferRequest.vendorId = vid;
+    }
+}

+ 97 - 97
EVCB_OCPP.WSServer/Jobs/ServerSetFeeJob.cs

@@ -1,97 +1,97 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages.Core;
-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;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-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;
-    private readonly ILogger<ServerSetFeeJob> logger;
-    //private readonly string webConnectionString;
-
-    public ServerSetFeeJob(
-        ProtalServer protalServer,
-        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;
-        this.logger = logger;
-        //this.webConnectionString = configuration.GetConnectionString("WebDBContext");
-    }
-
-    public async Task Execute(IJobExecutionContext context)
-    {
-        //logger.LogDebug("{0} Started", nameof(ServerSetFeeJob));
-        //BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-        Dictionary<string, WsClientData> _copyClientDic = protalServer.GetClientDic();
-        //using var db = maindbContextFactory.CreateDbContextAsync();
-        foreach (var item in _copyClientDic)
-        {
-            try
-            {
-                WsClientData session = item.Value;
-                if (!session.IsCheckIn)
-                {
-                    continue;
-                }
-
-                string displayPriceText = await webDbService.SetDefaultFee(session);
-                if (string.IsNullOrEmpty(displayPriceText) || displayPriceText == session.DisplayPrice)
-                {
-                    continue;
-                }
-
-                protalServer.UpdateClientDisplayPrice(item.Key, displayPriceText);
-
-                await messageService.SendChangeConfigurationRequest(
-                    session.ChargeBoxId, key: "DefaultPrice", value: displayPriceText);
-
-                if (session.CustomerId == new Guid("10C7F5BD-C89A-4E2A-8611-B617E0B41A73"))
-                {
-                    await messageService.SendChangeConfigurationRequest(
-                        session.ChargeBoxId, key: "ConnectionTimeOut", value: "120");
-
-                    await messageService.SendChangeConfigurationRequest(
-                        session.ChargeBoxId, key: "MeterValueSampleInterval", value: "3");
-                }
-            }
-            catch (Exception ex)
-            {
-                logger.LogError("ServerSetFeeTrigger ChargeBoxId:{0}  Ex:{1}", item.Key, ex.ToString());
-            }
-        }
-    }
-}
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages.Core;
+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;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs;
+
+[DisallowConcurrentExecution]
+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;
+    private readonly ILogger<ServerSetFeeJob> logger;
+    //private readonly string webConnectionString;
+
+    public ServerSetFeeJob(
+        ProtalServer protalServer,
+        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;
+        this.logger = logger;
+        //this.webConnectionString = configuration.GetConnectionString("WebDBContext");
+    }
+
+    public async Task Execute(IJobExecutionContext context)
+    {
+        //logger.LogDebug("{0} Started", nameof(ServerSetFeeJob));
+        //BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+        Dictionary<string, WsClientData> _copyClientDic = protalServer.GetClientDic();
+        //using var db = maindbContextFactory.CreateDbContextAsync();
+        foreach (var item in _copyClientDic)
+        {
+            try
+            {
+                WsClientData session = item.Value;
+                if (!session.IsCheckIn)
+                {
+                    continue;
+                }
+
+                string displayPriceText = await webDbService.SetDefaultFee(session);
+                if (string.IsNullOrEmpty(displayPriceText) || displayPriceText == session.DisplayPrice)
+                {
+                    continue;
+                }
+
+                protalServer.UpdateClientDisplayPrice(item.Key, displayPriceText);
+
+                await messageService.SendChangeConfigurationRequest(
+                    session.ChargeBoxId, key: "DefaultPrice", value: displayPriceText);
+
+                if (session.CustomerId == new Guid("10C7F5BD-C89A-4E2A-8611-B617E0B41A73"))
+                {
+                    await messageService.SendChangeConfigurationRequest(
+                        session.ChargeBoxId, key: "ConnectionTimeOut", value: "120");
+
+                    await messageService.SendChangeConfigurationRequest(
+                        session.ChargeBoxId, key: "MeterValueSampleInterval", value: "3");
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.LogError("ServerSetFeeTrigger ChargeBoxId:{0}  Ex:{1}", item.Key, ex.ToString());
+            }
+        }
+    }
+}

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

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

+ 85 - 85
EVCB_OCPP.WSServer/Jobs/SmartChargingJob.cs

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

+ 27 - 27
EVCB_OCPP.WSServer/Jobs/StationConfigPollingJob.cs

@@ -1,27 +1,27 @@
-using EVCB_OCPP.WSServer.Service;
-using EVCB_OCPP.WSServer.Service.DbService;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs
-{
-    [DisallowConcurrentExecution]
-    internal class StationConfigPollingJob : IJob
-    {
-        public StationConfigPollingJob(
-            StationConfigService stationConfigService)
-        {
-            this.stationConfigService = stationConfigService;
-        }
-        private readonly StationConfigService stationConfigService;
-
-        public Task Execute(IJobExecutionContext context)
-        {
-            return stationConfigService.CheckAndUpdateStationConfig();
-        }
-    }
-}
+using EVCB_OCPP.WSServer.Service;
+using EVCB_OCPP.WSServer.Service.DbService;
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Jobs
+{
+    [DisallowConcurrentExecution]
+    internal class StationConfigPollingJob : IJob
+    {
+        public StationConfigPollingJob(
+            StationConfigService stationConfigService)
+        {
+            this.stationConfigService = stationConfigService;
+        }
+        private readonly StationConfigService stationConfigService;
+
+        public Task Execute(IJobExecutionContext context)
+        {
+            return stationConfigService.CheckAndUpdateStationConfig();
+        }
+    }
+}

+ 158 - 158
EVCB_OCPP.WSServer/Message/BasicMessageHandler.cs

@@ -1,158 +1,158 @@
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Serialization;
-using NLog;
-
-
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    /// <summary>
-    /// 實現 OCPP 基本傳送規範,
-    /// 1.訊息 基本格式,將訊息包裝成 Call 、CallResult、CallError 三種格式
-    /// 2.OCPP 定義的傳送規則:交易相關的訊息必須依照時序性傳送,一個傳完才能接著送下一個(忽略規則 由Center System定義)
-    /// </summary>
-    internal class BasicMessageHandler
-    {
-        static internal Logger logger = NLog.LogManager.GetCurrentClassLogger();
-
-        #region 傳送 or 解析訊息需要欄位
-        private const int INDEX_MESSAGEID = 0;
-        private const int INDEX_UNIQUEID = 1;
-        internal const int TYPENUMBER_CALL = 2;
-        private const int INDEX_CALL_ACTION = 2;
-        private const int INDEX_CALL_PAYLOAD = 3;
-
-        internal const int TYPENUMBER_CALLRESULT = 3;
-        private const int INDEX_CALLRESULT_PAYLOAD = 2;
-
-        internal const int TYPENUMBER_CALLERROR = 4;
-        private const int INDEX_CALLERROR_ERRORCODE = 2;
-        private const int INDEX_CALLERROR_DESCRIPTION = 3;
-        private const int INDEX_CALLERROR_PAYLOAD = 4;
-
-        private const string CALL_FORMAT = "[2,\"{0}\",\"{1}\",{2}]";
-        private const string CALLRESULT_FORMAT = "[3,\"{0}\",{1}]";
-        private const string CALLERROR_FORMAT = "[4,\"{0}\",\"{1}\",\"{2}\",{3}]";
-        private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
-        private const string DATE_FORMAT_WITH_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
-        #endregion
-
-        private OCPP16MessageHandler _ocpp16Handler = new OCPP16MessageHandler();
-        private OCPP20MessageHandler _ocpp20Handler = new OCPP20MessageHandler();
-
-        /// <summary>
-        /// 將收到的封包做基本的拆解分成 Call 、CallResult、CallError
-        /// </summary>
-        /// <param name="client"></param>
-        /// <param name="data"></param>
-        /// <returns></returns>
-        internal MessageResult AnalysisReceiveData(WsClientData client, string data)
-        {
-            MessageResult result = null;
-            if (!client.ISOCPP20)
-            {
-                result = _ocpp16Handler.AnalysisReceiveData(client, data);
-            }
-            else
-            {
-                result = _ocpp20Handler.AnalysisReceiveData(client, data);
-            }
-
-            return result;
-        }
-
-
-        static internal string GenerateCallError(string uniqueId, string errorCode, string errorDescription)
-        {
-
-            string msg = string.Format(CALLERROR_FORMAT, uniqueId, errorCode, errorDescription, "{}");
-            return msg;
-
-        }
-
-        static internal string GenerateConfirmation(string uniqueId, IConfirmation confirmation)
-        {
-            string msg = string.Empty;
-            if (confirmation != null && confirmation.Validate())
-            {
-
-                msg = string.Format(CALLRESULT_FORMAT, uniqueId, JsonConvert.SerializeObject(confirmation, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }));
-            }
-            else
-            {
-                logger.Error(string.Format("confirmation is null  or InVaild in GenerateConfirmation Method"), "Warning");
-            }
-            return msg;
-        }
-
-        static internal string GenerateConfirmationofOCPP20(string uniqueId, EVCB_OCPP20.Packet.Messages.IConfirmation confirmation)
-        {
-            string msg = string.Empty;
-            if (confirmation != null && confirmation.Validate())
-            {
-
-                msg = string.Format(CALLRESULT_FORMAT, uniqueId, JsonConvert.SerializeObject(confirmation, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }));
-            }
-            else
-            {
-                logger.Error(string.Format("confirmation is null  or InVaild in GenerateConfirmation Method"), "Warning");
-            }
-            return msg;
-        }
-
-        static internal string GenerateRequest(string uniqueId, string action, IRequest request)
-        {
-            string msg = string.Empty;
-            if (request != null && request.Validate())
-            {
-
-                msg = string.Format(CALL_FORMAT, uniqueId, action, JsonConvert.SerializeObject(request, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }));
-            }
-            else
-            {
-                if (action == Actions.ChangeConfiguration.ToString())
-                {
-                    if(!request.Validate())
-                    {
-                        logger.Error("!Validate", "Warning");
-                    }
-
-                    if (request == null)
-                    {
-                        logger.Error("!NULL", "Warning");
-                    }
-
-                }
-
-                logger.Error(string.Format("confirmation is null  or InVaild in GenerateRequest Method "+ action), "Warning");
-            }
-            return msg;
-        }
-
-        static internal string GenerateRequestofOCPP20(string uniqueId, string action, EVCB_OCPP20.Packet.Messages.IRequest request)
-        {
-            string msg = string.Empty;
-            if (request != null && request.Validate())
-            {
-
-                msg = string.Format(CALL_FORMAT, uniqueId, action, JsonConvert.SerializeObject(request, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }));
-            }
-            else
-            {
-                logger.Error(string.Format("confirmation is null  or InVaild in GenerateRequest Method"), "Warning");
-            }
-            return msg;
-        }
-
-        static internal string GenerateDestroyRequest(string uniqueId, string action, string request)
-        {
-
-
-            return string.Format(CALL_FORMAT, uniqueId, action, request);
-        }
-    }
-}
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using NLog;
+
+
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    /// <summary>
+    /// 實現 OCPP 基本傳送規範,
+    /// 1.訊息 基本格式,將訊息包裝成 Call 、CallResult、CallError 三種格式
+    /// 2.OCPP 定義的傳送規則:交易相關的訊息必須依照時序性傳送,一個傳完才能接著送下一個(忽略規則 由Center System定義)
+    /// </summary>
+    internal class BasicMessageHandler
+    {
+        static internal Logger logger = NLog.LogManager.GetCurrentClassLogger();
+
+        #region 傳送 or 解析訊息需要欄位
+        private const int INDEX_MESSAGEID = 0;
+        private const int INDEX_UNIQUEID = 1;
+        internal const int TYPENUMBER_CALL = 2;
+        private const int INDEX_CALL_ACTION = 2;
+        private const int INDEX_CALL_PAYLOAD = 3;
+
+        internal const int TYPENUMBER_CALLRESULT = 3;
+        private const int INDEX_CALLRESULT_PAYLOAD = 2;
+
+        internal const int TYPENUMBER_CALLERROR = 4;
+        private const int INDEX_CALLERROR_ERRORCODE = 2;
+        private const int INDEX_CALLERROR_DESCRIPTION = 3;
+        private const int INDEX_CALLERROR_PAYLOAD = 4;
+
+        private const string CALL_FORMAT = "[2,\"{0}\",\"{1}\",{2}]";
+        private const string CALLRESULT_FORMAT = "[3,\"{0}\",{1}]";
+        private const string CALLERROR_FORMAT = "[4,\"{0}\",\"{1}\",\"{2}\",{3}]";
+        private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+        private const string DATE_FORMAT_WITH_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+        #endregion
+
+        private OCPP16MessageHandler _ocpp16Handler = new OCPP16MessageHandler();
+        private OCPP20MessageHandler _ocpp20Handler = new OCPP20MessageHandler();
+
+        /// <summary>
+        /// 將收到的封包做基本的拆解分成 Call 、CallResult、CallError
+        /// </summary>
+        /// <param name="client"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        internal MessageResult AnalysisReceiveData(WsClientData client, string data)
+        {
+            MessageResult result = null;
+            if (!client.ISOCPP20)
+            {
+                result = _ocpp16Handler.AnalysisReceiveData(client, data);
+            }
+            else
+            {
+                result = _ocpp20Handler.AnalysisReceiveData(client, data);
+            }
+
+            return result;
+        }
+
+
+        static internal string GenerateCallError(string uniqueId, string errorCode, string errorDescription)
+        {
+
+            string msg = string.Format(CALLERROR_FORMAT, uniqueId, errorCode, errorDescription, "{}");
+            return msg;
+
+        }
+
+        static internal string GenerateConfirmation(string uniqueId, IConfirmation confirmation)
+        {
+            string msg = string.Empty;
+            if (confirmation != null && confirmation.Validate())
+            {
+
+                msg = string.Format(CALLRESULT_FORMAT, uniqueId, JsonConvert.SerializeObject(confirmation, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }));
+            }
+            else
+            {
+                logger.Error(string.Format("confirmation is null  or InVaild in GenerateConfirmation Method"), "Warning");
+            }
+            return msg;
+        }
+
+        static internal string GenerateConfirmationofOCPP20(string uniqueId, EVCB_OCPP20.Packet.Messages.IConfirmation confirmation)
+        {
+            string msg = string.Empty;
+            if (confirmation != null && confirmation.Validate())
+            {
+
+                msg = string.Format(CALLRESULT_FORMAT, uniqueId, JsonConvert.SerializeObject(confirmation, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }));
+            }
+            else
+            {
+                logger.Error(string.Format("confirmation is null  or InVaild in GenerateConfirmation Method"), "Warning");
+            }
+            return msg;
+        }
+
+        static internal string GenerateRequest(string uniqueId, string action, IRequest request)
+        {
+            string msg = string.Empty;
+            if (request != null && request.Validate())
+            {
+
+                msg = string.Format(CALL_FORMAT, uniqueId, action, JsonConvert.SerializeObject(request, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }));
+            }
+            else
+            {
+                if (action == Actions.ChangeConfiguration.ToString())
+                {
+                    if(!request.Validate())
+                    {
+                        logger.Error("!Validate", "Warning");
+                    }
+
+                    if (request == null)
+                    {
+                        logger.Error("!NULL", "Warning");
+                    }
+
+                }
+
+                logger.Error(string.Format("confirmation is null  or InVaild in GenerateRequest Method "+ action), "Warning");
+            }
+            return msg;
+        }
+
+        static internal string GenerateRequestofOCPP20(string uniqueId, string action, EVCB_OCPP20.Packet.Messages.IRequest request)
+        {
+            string msg = string.Empty;
+            if (request != null && request.Validate())
+            {
+
+                msg = string.Format(CALL_FORMAT, uniqueId, action, JsonConvert.SerializeObject(request, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }));
+            }
+            else
+            {
+                logger.Error(string.Format("confirmation is null  or InVaild in GenerateRequest Method"), "Warning");
+            }
+            return msg;
+        }
+
+        static internal string GenerateDestroyRequest(string uniqueId, string action, string request)
+        {
+
+
+            return string.Format(CALL_FORMAT, uniqueId, action, request);
+        }
+    }
+}

+ 1814 - 1814
EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs

@@ -1,1814 +1,1814 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.Core;
-using EVCB_OCPP.Packet.Messages.SubTypes;
-using EVCB_OCPP.WSServer.Dto;
-using EVCB_OCPP.WSServer.Helper;
-using EVCB_OCPP.WSServer.Service;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-using System.Data;
-using System.Diagnostics;
-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;
-
-public class ID_CreditDeductResult
-{
-	public int txId { set; get; }
-
-	public string creditNo { set; get; }
-
-
-	public bool deductResult { set; get; }
-
-	public bool isDonateInvoice { set; get; }
-
-
-	public decimal amount { set; get; }
-
-	public string approvalNo { set; get; }
-
-}
-
-public class ID_ReaderStatus
-{
-	public int ConnectorId { set; get; }
-
-	public string creditNo { set; get; }
-
-
-	public string SerialNo { set; get; }
-
-	public int readerStatus { set; get; }
-
-	public string VEMData { set; get; }
-
-	public DateTime Timestamp { set; get; }
-
-
-
-
-}
-
-internal partial class ProfileHandler
-{
-	private readonly ILogger logger;
-	//private readonly BlockingTreePrintService blockingTreePrintService;
-	//private readonly GoogleGetTimePrintService googleGetTimePrintService;
-	private readonly ServerMessageService messageService;
-
-	//private readonly string webConnectionString;// = ConfigurationManager.ConnectionStrings[].ConnectionString;
-	private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
-	private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
-	private readonly MeterValueDbService meterValueDbService;
-
-	//private readonly IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory;
-	private readonly IBusinessServiceFactory businessServiceFactory;
-	private readonly IMainDbService mainDbService;
-	private OuterHttpClient httpClient;
-
-	public ProfileHandler(
-		IConfiguration configuration,
-		IDbContextFactory<MainDBContext> maindbContextFactory,
-		ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory,
-		//IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory,
-		MeterValueDbService meterValueDbService,
-		IBusinessServiceFactory businessServiceFactory,
-		IMainDbService mainDbService,
-		ILogger<ProfileHandler> logger,
-		//BlockingTreePrintService blockingTreePrintService,
-		//GoogleGetTimePrintService googleGetTimePrintService,
-		ServerMessageService messageService,
-		OuterHttpClient httpClient)
-	{
-		//webConnectionString = configuration.GetConnectionString("WebDBContext");
-
-		this.logger = logger;
-		//this.blockingTreePrintService = blockingTreePrintService;
-		//this.googleGetTimePrintService = googleGetTimePrintService;
-		this.messageService = messageService;
-		this.maindbContextFactory = maindbContextFactory;
-		this.webDbConnectionFactory = webDbConnectionFactory;
-		this.meterValueDbService = meterValueDbService;
-		this.mainDbService = mainDbService;
-		//this.metervaluedbContextFactory = metervaluedbContextFactory;
-		this.businessServiceFactory = businessServiceFactory;
-		this.httpClient = httpClient;
-	}
-
-	async internal Task<MessageResult> ExecuteCoreRequest(Actions action, WsClientData session, IRequest request)
-	{
-		Stopwatch watch = new Stopwatch();
-		//if (action == Actions.Heartbeat || action == Actions.StopTransaction)
-		//{
-		//    watch.Start();
-		//}
-		watch.Start();
-		MessageResult result = new MessageResult() { Success = false };
-
-		try
-		{
-			switch (action)
-			{
-				case Actions.DataTransfer:
-					{
-						DataTransferRequest _request = request as DataTransferRequest;
-						var confirm = new DataTransferConfirmation() { status = DataTransferStatus.UnknownMessageId };
-
-						if (_request.messageId == "ID_CreditDeductResult")
-						{
-
-							var creditDeductResult = JsonConvert.DeserializeObject<ID_CreditDeductResult>(_request.data);
-							if (session.CustomerId == new Guid("009E603C-79CD-4620-A2B8-D9349C0E8AD8"))
-							{
-								var report = new
-								{
-									ChargeBoxId = session.ChargeBoxId,
-									IsDonateInvoice = creditDeductResult.isDonateInvoice,
-									CreditNo = creditDeductResult.creditNo,
-									DeductResult = creditDeductResult.deductResult,
-									SessionId = creditDeductResult.txId,
-									ApprovalNo = creditDeductResult.approvalNo,
-									TotalCost = creditDeductResult.amount,
-
-								};
-
-								var response = await httpClient.Post(GlobalConfig.TCC_API_URL + "prepare_issue_invoice", new Dictionary<string, string>()
-									{
-										{ "PartnerId",session.CustomerId.ToString()}
-
-									}, report, GlobalConfig.TCC_SALTKEY);
-
-								logger.LogDebug(JsonConvert.SerializeObject(response));
-							}
-
-
-							confirm.status = DataTransferStatus.Accepted;
-							confirm.data = JsonConvert.SerializeObject(new { txId = creditDeductResult.txId, creditNo = creditDeductResult.creditNo, msgId = _request.messageId });
-						}
-						if (_request.messageId == "ID_ReaderStatus")
-						{
-							if (session.CustomerId == new Guid("009E603C-79CD-4620-A2B8-D9349C0E8AD8"))
-							{
-								var preauth_status = JsonConvert.DeserializeObject<ID_ReaderStatus>(_request.data);
-								var report = new
-								{
-									ChargeBoxId = session.ChargeBoxId,
-									ConnectorId = preauth_status.ConnectorId,
-									CreditNo = preauth_status.creditNo,
-									ReaderStatus = preauth_status.readerStatus,
-									SerialNo = preauth_status.SerialNo,
-									VEMData = preauth_status.VEMData,
-									Timestamp = preauth_status.Timestamp
-
-								};
-
-								var response = await httpClient.Post(GlobalConfig.TCC_API_URL + "preauth_status", new Dictionary<string, string>()
-									{
-										{ "PartnerId",session.CustomerId.ToString()}
-
-									}, report, GlobalConfig.TCC_SALTKEY);
-
-
-								confirm.status = DataTransferStatus.Accepted;
-							}
-						}
-						if (_request.messageId == "ID_OCMF")
-						{
-							JObject jo = JObject.Parse(_request.data);
-
-							logger.LogDebug("{0}\r\n{1}\r\n{2}", jo["txId"].Value<int>(), jo["dataString"].Value<string>(), jo["publicKey"].Value<string>());
-
-							await mainDbService.AddOCMF(new Ocmf()
-							{
-								TransactionId = jo["txId"].Value<int>(),
-								DataString = jo["dataString"].Value<string>(),
-								PublicKey = jo["publicKey"].Value<string>()
-							});
-
-							confirm.status = DataTransferStatus.Accepted;
-							confirm.data = JsonConvert.SerializeObject(new { txId = jo["txId"].Value<int>(), msgId = _request.messageId });
-						}
-
-						if (_request.messageId == "Authorize")
-						{
-							string iso15118_token = string.Empty;
-							JObject jo = JObject.Parse(_request.data);
-							if (jo.ContainsKey("idToken"))
-							{
-								iso15118_token = jo["idToken"]["idToken"].Value<string>();
-
-							}
-
-							confirm.status = DataTransferStatus.Accepted;
-							confirm.data = JsonConvert.SerializeObject(
-								new
-								{
-									certificateStatus = iso15118_token == "12345678901234" ? "Accepted" : "CertificateExpired",
-									idTokenInfo = new
-									{
-										status = iso15118_token == "12345678901234" ? "Accepted" : "Invalid"
-									}
-								});
-						}
-						result.Message = confirm;
-						result.Success = true;
-					}
-					break;
-				case Actions.BootNotification:
-					{
-						BootNotificationRequest _request = request as BootNotificationRequest;
-						int heartbeat_interval = GlobalConfig.GetHEARTBEAT_INTERVAL();
-						//var _machine = db.Machine.FirstOrDefault(x => x.ChargeBoxId == session.ChargeBoxId);
-
-						if (session.BootStatus == BootStatus.Startup)
-						{
-                            Machine _machine = new();
-                            _machine.ChargeBoxSerialNumber = string.IsNullOrEmpty(_request.chargeBoxSerialNumber) ? string.Empty : _request.chargeBoxSerialNumber;
-                            _machine.ChargePointSerialNumber = string.IsNullOrEmpty(_request.chargePointSerialNumber) ? string.Empty : _request.chargePointSerialNumber;
-                            _machine.ChargePointModel = string.IsNullOrEmpty(_request.chargePointModel) ? string.Empty : _request.chargePointModel;
-                            _machine.ChargePointVendor = string.IsNullOrEmpty(_request.chargePointVendor) ? string.Empty : _request.chargePointVendor;
-                            _machine.FwCurrentVersion = string.IsNullOrEmpty(_request.firmwareVersion) ? string.Empty : _request.firmwareVersion;
-                            _machine.Iccid = string.IsNullOrEmpty(_request.iccid) ? string.Empty : _request.iccid;
-                            //_machine.Iccid = DateTime.UtcNow.ToString("yy-MM-dd HH:mm");
-                            _machine.Imsi = string.IsNullOrEmpty(_request.imsi) ? string.Empty : _request.imsi;
-                            _machine.MeterSerialNumber = string.IsNullOrEmpty(_request.meterSerialNumber) ? string.Empty : _request.meterSerialNumber;
-                            _machine.MeterType = string.IsNullOrEmpty(_request.meterType) ? string.Empty : _request.meterType;
-
-                            await mainDbService.UpdateMachineBasicInfo(session.ChargeBoxId, _machine);
-                        }
-
-						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:
-                                var configValue = await mainDbService.GetMachineHeartbeatInterval(session.ChargeBoxId);
-
-                                if (configValue != null)
-                                {
-                                    int.TryParse(configValue, out heartbeat_interval);
-                                    heartbeat_interval = heartbeat_interval == 0 ? GlobalConfig.GetHEARTBEAT_INTERVAL() : heartbeat_interval;
-                                }
-
-                                toReturnInterval = heartbeat_interval;
-                                toReturnRegistrationStatus = RegistrationStatus.Accepted;
-                                session.BootStatus = BootStatus.Accepted;
-                                break;
-                        }
-
-                        var confirm = new BootNotificationConfirmation()
-						{
-							currentTime = DateTime.UtcNow,
-                            interval = toReturnInterval,
-                            status = toReturnRegistrationStatus
-                        };
-
-						result.Message = confirm;
-						result.Success = true;
-					}
-					break;
-				case Actions.StatusNotification:
-                    {
-                        var statusNotificationTimer = Stopwatch.StartNew();
-                        long s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0;
-                        //只保留最新上報狀況
-                        StatusNotificationRequest _request = request as StatusNotificationRequest;
-                        int preStatus = 0;
-                        ConnectorStatus _oldStatus;
-
-                        _oldStatus = await mainDbService.GetConnectorStatus(session.ChargeBoxId, _request.connectorId);
-
-                        s1 = statusNotificationTimer.ElapsedMilliseconds;
-
-                        if (_oldStatus != null && (_request.status != (ChargePointStatus)_oldStatus.Status || _request.status == ChargePointStatus.Faulted))
-                        {
-                            preStatus = _oldStatus.Status;
-
-                            await mainDbService.UpdateConnectorStatus(_oldStatus.Id, new ConnectorStatus()
-                            {
-                                CreatedOn = _request.timestamp.HasValue ? _request.timestamp.Value : DateTime.UtcNow,
-                                Status = (int)_request.status,
-                                ChargePointErrorCodeId = (int)_request.errorCode,
-                                ErrorInfo = string.IsNullOrEmpty(_request.info) ? string.Empty : _request.info,
-                                VendorId = string.IsNullOrEmpty(_request.vendorId) ? string.Empty : _request.vendorId,
-                                VendorErrorCode = string.IsNullOrEmpty(_request.vendorErrorCode) ? string.Empty : _request.vendorErrorCode
-                            });
-
-                            if (preStatus == (int)ChargePointStatus.Faulted)
-                            {
-                                if (_request.status != ChargePointStatus.Faulted ||
-                                    (_oldStatus.ChargePointErrorCodeId != (int)_request.errorCode && _oldStatus.VendorErrorCode != _request.vendorErrorCode))
-                                {
-                                    await mainDbService.FillupFinishedTimetoMachineError(
-                                        ChargeBoxId: session.ChargeBoxId,
-                                        ConnectorId: (byte)_request.connectorId,
-                                        FinishedOn: _request.timestamp.HasValue ? _request.timestamp.Value : DateTime.UtcNow
-                                        );
-                                }
-
-                            }
-                        }
-
-                        s2 = statusNotificationTimer.ElapsedMilliseconds;
-
-                        if (_oldStatus == null)
-                        {
-                            await mainDbService.AddConnectorStatus(
-                                ChargeBoxId: session.ChargeBoxId,
-                                ConnectorId: (byte)_request.connectorId,
-                                CreatedOn: _request.timestamp.HasValue ? _request.timestamp.Value : DateTime.UtcNow,
-                                Status: (int)_request.status,
-                                ChargePointErrorCodeId: (int)_request.errorCode,
-                                ErrorInfo: string.IsNullOrEmpty(_request.info) ? string.Empty : _request.info,
-                                VendorId: string.IsNullOrEmpty(_request.vendorId) ? string.Empty : _request.vendorId,
-                                VendorErrorCode: string.IsNullOrEmpty(_request.vendorErrorCode) ? string.Empty : _request.vendorErrorCode);
-                        }
-                        s3 = statusNotificationTimer.ElapsedMilliseconds;
-
-                        bool isNeedAddMachineError = CheckNeedAddMachineError(_request, _oldStatus);
-
-                        //if (_request.status == Packet.Messages.SubTypes.ChargePointStatus.Faulted &&
-                        //(_oldStatus != null && 
-                        //(_oldStatus.ChargePointErrorCodeId != (int)_request.errorCode && _oldStatus.VendorErrorCode != _request.vendorErrorCode)))
-                        if (isNeedAddMachineError)
-                        {
-                            await mainDbService.AddMachineError(ConnectorId: (byte)_request.connectorId,
-                                    CreatedOn: _request.timestamp.HasValue ? _request.timestamp.Value : DateTime.UtcNow,
-                                    Status: (int)_request.status,
-                                    ChargeBoxId: session.ChargeBoxId,
-                                    ErrorCodeId: (int)_request.errorCode,
-                                    ErrorInfo: string.IsNullOrEmpty(_request.info) ? string.Empty : _request.info,
-                                    PreStatus: _oldStatus == null ? -1 : preStatus,
-                                    VendorErrorCode: string.IsNullOrEmpty(_request.vendorErrorCode) ? string.Empty : _request.vendorErrorCode,
-                                    VendorId: string.IsNullOrEmpty(_request.vendorId) ? string.Empty : _request.vendorId);
-                        }
-
-                        s4 = statusNotificationTimer.ElapsedMilliseconds;
-                        if (_request.status == Packet.Messages.SubTypes.ChargePointStatus.Faulted)
-                        {
-                            //var businessService = BusinessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
-                            //var businessService = await serviceProvider.GetService<BusinessServiceFactory>().CreateBusinessService(session.CustomerId.ToString());
-                            var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
-                            var notification = businessService.NotifyFaultStatus(new ErrorDetails()
-                            {
-                                ChargeBoxId = session.ChargeBoxId,
-                                ConnectorId = _request.connectorId,
-                                ErrorCode = _request.errorCode,
-                                Info = string.IsNullOrEmpty(_request.info) ? string.Empty : _request.info,
-                                OCcuredOn = _request.timestamp ?? DateTime.UtcNow,
-                                VendorErrorCode = string.IsNullOrEmpty(_request.vendorErrorCode) ? string.Empty : _request.vendorErrorCode,
-
-                            });
-                        }
-                        s5 = statusNotificationTimer.ElapsedMilliseconds;
-
-                        var confirm = new StatusNotificationConfirmation() { };
-                        result.Message = confirm;
-                        result.Success = true;
-
-                        statusNotificationTimer.Stop();
-                        if (statusNotificationTimer.ElapsedMilliseconds / 1000 > 1)
-                        {
-                            logger.LogCritical(string.Format("StatusNotification took {0}/{1}/{2}/{3}/{4}", s1, s2, s3, s4, s5));
-                        }
-                    }
-                    break;
-				case Actions.Heartbeat:
-					{
-
-						var confirm = new HeartbeatConfirmation() { currentTime = DateTime.UtcNow };
-						result.Message = confirm;
-						result.Success = true;
-					}
-					break;
-				case Actions.MeterValues:
-					{
-						var meterValueTimer = Stopwatch.StartNew();
-						long s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, insertTasksCnt = 0;
-						MeterValuesRequest _request = request as MeterValuesRequest;
-
-						if (_request.meterValue.Count > 0)
-						{
-							s1 = meterValueTimer.ElapsedMilliseconds;
-							foreach (var item in _request.meterValue)
-							{
-								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;
-
-
-										using (var maindb = await maindbContextFactory.CreateDbContextAsync())
-										{
-											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()
-										});
-									}
-
-								}
-
-							}
-
-							s2 = meterValueTimer.ElapsedMilliseconds;
-							//List<Task> insertTasks = new();
-							List<InsertMeterValueParam> datas = new();
-							foreach (var item in _request.meterValue)
-							{
-								foreach (var sampleVaule in item.sampledValue)
-								{
-									if (sampleVaule.format == ValueFormat.SignedData) continue;
-
-									decimal value = Convert.ToDecimal(sampleVaule.value);
-									datas.Add(new InsertMeterValueParam(
-										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));
-									//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);
-
-									//var task = Task.Delay(2_000); 
-									//insertTasks.Add(task);
-								}
-							}
-							//insertTasksCnt = insertTasks.Count;
-							insertTasksCnt = datas.Count;
-							s3 = meterValueTimer.ElapsedMilliseconds;
-							//await Task.WhenAll(insertTasks);
-							await meterValueDbService.InsertBundleAsync(datas);
-							s4 = meterValueTimer.ElapsedMilliseconds;
-						}
-
-						//  if (energy_kwh > 0)
-						if (_request.transactionId != null)
-						{
-							try
-							{
-								if (session.IsBilling)
-								{
-									await messageService.SendDataTransferRequest(
-										session.ChargeBoxId,
-										messageId: "ID_TxEnergy",
-										vendorId: "Phihong Technology",
-										data: JsonConvert.SerializeObject(new { txId = _request.transactionId, ConnectorId = _request.connectorId })
-										);
-								}
-							}
-							catch (Exception ex)
-							{
-
-								logger.LogTrace(string.Format("{0} :{1}", session.ChargeBoxId + " RunningCost", ex.Message));
-
-							}
-
-						}
-
-						s5 = meterValueTimer.ElapsedMilliseconds;
-						meterValueTimer.Stop();
-						if (meterValueTimer.ElapsedMilliseconds / 1000 > 1)
-						{
-
-							logger.LogCritical(string.Format("MeterValues took {0}/{1}/{2}/{3}/{4}:{5}", s1 / 1000, s2 / 1000, s3 / 1000, s4 / 1000, s5 / 1000, insertTasksCnt));
-						}
-
-						var confirm = new MeterValuesConfirmation() { };
-						result.Message = confirm;
-						result.Success = true;
-					}
-					break;
-				case Actions.StartTransaction:
-					{
-						var timer = Stopwatch.StartNew();
-						long t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0;
-
-						StartTransactionRequest _request = request as StartTransactionRequest;
-
-						int _transactionId = -1;
-
-						var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
-						t0 = timer.ElapsedMilliseconds;
-
-						var _idTagInfo = new IdTagInfo() { expiryDate = DateTime.UtcNow.AddDays(1), status = AuthorizationStatus.Accepted };
-
-						#region PnC 邏輯
-
-						if (!string.IsNullOrEmpty(_request.idTag))
-						{
-							_request.idTag = _request.idTag.StartsWith("vid:") ? _request.idTag.Replace("vid:", "") : _request.idTag;
-						}
-
-						#endregion
-
-						if (_request.idTag != "Backend")
-						{
-							var authorization_result = await businessService.Authorize(session.ChargeBoxId, _request.idTag, source: Actions.StartTransaction.ToString());
-							_idTagInfo = authorization_result.IdTagInfo;
-							t1 = timer.ElapsedMilliseconds;
-
-							if (_idTagInfo.status == AuthorizationStatus.Accepted && authorization_result.ChargePointFee != null)
-							{
-								var price = authorization_result.ChargePointFee.Where(x => x.IsAC == session.IsAC).First();
-								if (price != null)
-								{
-									session.UserPrices[_request.idTag] = price.PerkWhFee.HasValue ? JsonConvert.SerializeObject(new List<ChargingPrice>() { new ChargingPrice() { StartTime = "00:00", EndTime = "23:59", Fee = price.PerkWhFee.Value } }) : price.PerHourFee.Value.ToString();
-									session.UserPrices[_request.idTag] += "|+" + authorization_result.AccountBalance + "+" + "&" + price.ParkingFee + "&|" + price.Currency;
-								}
-							}
-
-						}
-
-						//特例****飛宏客戶旗下的電樁,若遇到Portal沒回應的狀況 ~允許充電
-						if (session.CustomerId.ToString().ToUpper() == "8456AED9-6DD9-4BF3-A94C-9F5DCB9506F7" && _idTagInfo.status == AuthorizationStatus.ConcurrentTx)
-						{
-							_idTagInfo = new IdTagInfo() { expiryDate = DateTime.UtcNow.AddDays(1), status = AuthorizationStatus.Accepted };
-						}
-
-						string accountBalance = "0";
-						if (session.CustomerId.ToString().ToUpper() == "10C7F5BD-C89A-4E2A-8611-B617E0B41A73")
-						{
-							using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
-							{
-								var parameters = new DynamicParameters();
-								parameters.Add("@IdTag", _request.idTag, DbType.String, ParameterDirection.Input, 50);
-								string strSql = "select parentIdTag from [dbo].[LocalListDetail]  where ListId = 27 and IdTag=@IdTag; ";
-								accountBalance = await conn.ExecuteScalarAsync<string>(strSql, parameters);
-							}
-						}
-
-						var _CustomerId = await mainDbService.GetCustomerIdByChargeBoxId(session.ChargeBoxId);
-						t2 = timer.ElapsedMilliseconds;
-
-						var _existedTx = await mainDbService.TryGetDuplicatedTransactionId(session.ChargeBoxId, _CustomerId, _request.connectorId, _request.timestamp);
-						t3 = timer.ElapsedMilliseconds;
-
-						if (_existedTx != null)
-						{
-							_transactionId = _existedTx.Value;
-							logger.LogError("Duplication ***************************************************** " + _existedTx);
-						}
-						else
-						{
-							TransactionRecord _newTransaction;//= new TransactionRecord();
-							_newTransaction = new TransactionRecord()
-							{
-								ChargeBoxId = session.ChargeBoxId,
-								ConnectorId = (byte)_request.connectorId,
-								CreatedOn = DateTime.UtcNow,
-								StartIdTag = _request.idTag,
-								MeterStart = _request.meterStart,
-								CustomerId = _CustomerId,
-								StartTime = _request.timestamp.ToUniversalTime(),
-								ReservationId = _request.reservationId.HasValue ? _request.reservationId.Value : 0,
-							};
-
-							if (session.UserPrices.ContainsKey(_request.idTag))
-							{
-								_newTransaction.Fee = !session.IsBilling ? string.Empty : session.UserPrices[_request.idTag];
-
-							}
-							else
-							{
-								_newTransaction.Fee = !session.IsBilling ? string.Empty : session.BillingMethod == 1 ? JsonConvert.SerializeObject(session.ChargingPrices) : session.ChargingFeebyHour.ToString();
-								_newTransaction.Fee += !session.IsBilling ? string.Empty : "|+" + accountBalance + "+" + "&" + session.ParkingFee + "&|" + session.Currency;
-							}
-
-							//using (var db = await maindbContextFactory.CreateDbContextAsync())
-							//{
-							//    await db.TransactionRecord.AddAsync(_newTransaction);
-
-							//    await db.SaveChangesAsync();
-
-							//    _transactionId = _newTransaction.Id;
-							//}
-
-							_transactionId = await mainDbService.AddNewTransactionRecord(_newTransaction);
-							t4 = timer.ElapsedMilliseconds;
-
-							logger.LogInformation("***************************************************** ");
-							logger.LogInformation(string.Format("{0} :TransactionId {1} ", session.ChargeBoxId, _transactionId));
-							logger.LogInformation("***************************************************** ");
-						}
-
-
-						var confirm = new StartTransactionConfirmation()
-						{
-							idTagInfo = _idTagInfo,
-							transactionId = _transactionId
-						};
-
-
-						result.Message = confirm;
-						result.Success = true;
-
-						timer.Stop();
-						t5 = timer.ElapsedMilliseconds;
-						if (t5 > 1000)
-						{
-							logger.Log(LogLevel.Critical, "{action} {ChargeBoxId} time {t0}/{t1}/{t2}/{t3}/{t4}/{totalTime}", action.ToString(), session.ChargeBoxId, t0, t1, t2, t3, t4, t5);
-						}
-					}
-					break;
-				case Actions.StopTransaction:
-					{
-						StopTransactionRequest _request = request as StopTransactionRequest;
-
-						//遠傳太久以前的停止充電 或 電樁上傳TransactionId=0 直接拒絕 避免電樁持續重送~~~~~~~
-						if (_request.timestamp < new DateTime(2021, 11, 1) || _request.transactionId == 0)
-						{
-							var confirm = new StopTransactionConfirmation()
-							{
-								idTagInfo = new IdTagInfo()
-								{
-									status = AuthorizationStatus.Invalid
-								}
-							};
-
-							result.Message = confirm;
-							result.Success = true;
-							return result;
-						}
-
-						long getDateTimeTime, getServiceTime, getTagInfoTime, dbOpTime = 0, meterValueTime = 0;
-						var stopTrasactionTimer = Stopwatch.StartNew();
-
-						int _ConnectorId = 0;
-						var utcNow = DateTime.UtcNow;
-
-						getDateTimeTime = stopTrasactionTimer.ElapsedMilliseconds;
-						var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
-						getServiceTime = stopTrasactionTimer.ElapsedMilliseconds;
-
-						TransactionRecord transaction;
-						transaction = await mainDbService.GetTransactionForStopTransaction(_request.transactionId, session.ChargeBoxId);
-
-						var _idTagInfo = string.IsNullOrEmpty(_request.idTag) ? null : (
-							_request.idTag == "Backend" ?
-								new IdTagInfo()
-								{
-									expiryDate = utcNow.AddDays(1),
-									status = AuthorizationStatus.Accepted
-								} :
-								(await businessService.Authorize(session.ChargeBoxId, _request.idTag, transaction?.ConnectorId, source: Actions.StopTransaction.ToString())).IdTagInfo
-							);
-						getTagInfoTime = stopTrasactionTimer.ElapsedMilliseconds;
-
-						//特例****飛宏客戶旗下的電樁,若遇到Portal沒回應的狀況 ~允許充電
-						if (session.CustomerId.ToString().ToUpper() == "8456AED9-6DD9-4BF3-A94C-9F5DCB9506F7" && _idTagInfo != null && _idTagInfo.status == AuthorizationStatus.ConcurrentTx)
-						{
-							_idTagInfo = new IdTagInfo() { expiryDate = utcNow.AddDays(1), status = AuthorizationStatus.Accepted };
-						}
-
-						#region PnC 邏輯
-						if (!string.IsNullOrEmpty(_request.idTag))
-						{
-							_request.idTag = _request.idTag.StartsWith("vid:") ? _request.idTag.Replace("vid:", "") : _request.idTag;
-						}
-						#endregion
-
-						try
-						{
-							if (transaction is null)
-							{
-								result.Exception = new Exception("Can't find transactionId " + _request.transactionId);
-							}
-							else
-							{
-								#region 加入Transaction Start/StopSOC
-								if (!session.IsAC && _request.transactionId > 0)
-								{
-									var SearchTime = transaction.StartTime;
-									var txStopTime = _request.timestamp;
-									List<int> SOCCollection = new List<int>();
-
-									while (SearchTime.Date <= txStopTime.Date)
-									{
-
-										var searchResults = await meterValueDbService.GetTransactionSOC(transaction.Id, SearchTime.Date);
-										SOCCollection.AddRange(searchResults);
-										SearchTime = SearchTime.AddDays(1);
-									}
-
-									SOCCollection.Sort();
-									logger.LogDebug(string.Format("SOCCollection:" + String.Join(",", SOCCollection.Select(x => x.ToString()).ToArray())));
-
-									await mainDbService.UpdateTransactionSOC(
-										transaction.Id,
-										startsoc: SOCCollection.Count == 0 ? "" : SOCCollection.First().ToString("0"),
-										stopsoc: SOCCollection.Count == 0 ? "" : SOCCollection.Last().ToString("0")
-										);
-								}
-								#endregion
-
-								_ConnectorId = transaction.ConnectorId;
-
-								var confirm = new StopTransactionConfirmation()
-								{
-									idTagInfo = _idTagInfo
-
-								};
-
-								//Avoid rewrite transaction data
-								if (transaction.StopTime != GlobalConfig.DefaultNullTime)
-								{
-									result.Message = confirm;
-									result.Success = true;
-									return result;
-								}
-
-								await mainDbService.UpdateTransaction(_request.transactionId,
-									meterStop: _request.meterStop,
-									stopTime: _request.timestamp.ToUniversalTime(),
-									stopReasonId: _request.reason.HasValue ? (int)_request.reason.Value : 0,
-									stopReason: _request.reason.HasValue ? _request.reason.Value.ToString() : Reason.Local.ToString(),
-									stopIdTag: _request.idTag,
-									receipt: string.Empty,
-									cost: session.IsBilling ? -1 : 0);
-
-								if (_request.transactionData == null || _request.transactionData.Count == 0)
-								{
-									_request.transactionData = new List<MeterValue>()
-											{
-												new MeterValue() {  timestamp= _request.timestamp, sampledValue=new List<SampledValue>()}
-											};
-								}
-
-								if (_request.transactionData != null && _request.transactionData.Count > 0)
-								{
-									//清除 StopTransaction TransactionData 
-									_request.transactionData[0].sampledValue.Clear();
-
-									_request.transactionData[0].sampledValue.Add(new SampledValue()
-									{
-										context = ReadingContext.Transaction_End,
-										format = ValueFormat.Raw,
-										location = Location.Outlet,
-										phase = _request.transactionData[0].sampledValue.Where(x => x.context.HasValue).Select(x => x.phase).FirstOrDefault(),
-										unit = UnitOfMeasure.Wh,
-										measurand = Measurand.TotalEnergy,
-										value = decimal.Subtract(transaction.MeterStop, transaction.MeterStart).ToString()
-									});
-								}
-
-								if (session.IsBilling)
-								{
-									await messageService.SendDataTransferRequest(
-										session.ChargeBoxId,
-										messageId: "ID_TxEnergy",
-										vendorId: "Phihong Technology",
-										data: JsonConvert.SerializeObject(new { txId = _request.transactionId, ConnectorId = transaction.ConnectorId })
-										);
-								}
-
-								result.Message = confirm;
-								result.Success = true;
-
-							}
-							dbOpTime = watch.ElapsedMilliseconds;
-
-							#region Save MeterValue
-
-							if (_request.transactionData != null &&
-								_request.transactionData.Count > 0)
-							{
-								//List<Task> insertTasks = new();
-								List<InsertMeterValueParam> datas = new();
-								foreach (var item in _request.transactionData)
-								{
-									foreach (var sampleVaule in item.sampledValue)
-									{
-										decimal value = Convert.ToDecimal(sampleVaule.value);
-										datas.Add(new InsertMeterValueParam(
-											chargeBoxId: session.ChargeBoxId
-											, connectorId: (byte)_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));
-										//var task = meterValueDbService.InsertAsync(
-										//    chargeBoxId: session.ChargeBoxId
-										//        , connectorId: (byte)_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);
-										//insertTasks.Add(task);
-									}
-								}
-								//await Task.WhenAll(insertTasks);
-								await meterValueDbService.InsertBundleAsync(datas);
-							}
-							#endregion
-
-							meterValueTime = watch.ElapsedMilliseconds;
-						}
-						catch (Exception ex)
-						{
-							result.Exception = new Exception("TransactionId " + _request.transactionId + " " + ex.Message);
-							result.CallErrorMsg = "Reject Response Message";
-							result.Success = false;
-
-							logger.LogCritical("StopTransaction {msg} trace:{trace}", ex.Message, ex.StackTrace);
-							// return result;
-						}
-
-						stopTrasactionTimer.Stop();
-
-						if (stopTrasactionTimer.ElapsedMilliseconds > 1000)
-						{
-							logger.Log(LogLevel.Critical, "ExecuteCoreRequest {action} {ChargeBoxId} took {time} sec", action.ToString(), session.ChargeBoxId, stopTrasactionTimer.ElapsedMilliseconds / 1000);
-							logger.Log(LogLevel.Critical, "{action} {ChargeBoxId} time {getDateTime}/{serviceTime}/{tagInfoTime}/{dbOpTime}/{meterValueTime}", action.ToString(), session.ChargeBoxId, getDateTimeTime, getServiceTime, getTagInfoTime, dbOpTime, meterValueTime);
-						}
-
-					}
-					break;
-				case Actions.Authorize:
-					{
-						AuthorizeRequest _request = request as AuthorizeRequest;
-
-						var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
-						var confirm = new AuthorizeConfirmation()
-						{
-							idTagInfo = new IdTagInfo() { expiryDate = DateTime.UtcNow.AddDays(1), status = AuthorizationStatus.Accepted }
-						};
-						if (_request.idTag != "Backend")
-						{
-							var authorization_result = await businessService.Authorize(session.ChargeBoxId, _request.idTag, source: Actions.Authorize.ToString());
-							confirm.idTagInfo = authorization_result.IdTagInfo;
-
-							if (confirm.idTagInfo.status == AuthorizationStatus.Accepted && authorization_result.ChargePointFee != null)
-							{
-								var price = authorization_result.ChargePointFee.Where(x => x.IsAC == session.IsAC).First();
-								if (price != null)
-								{
-									session.UserPrices[_request.idTag] = price.PerkWhFee.HasValue ? JsonConvert.SerializeObject(new List<ChargingPrice>() { new ChargingPrice() { StartTime = "00:00", EndTime = "23:59", Fee = price.PerkWhFee.Value } }) : price.PerHourFee.Value.ToString();
-									session.UserPrices[_request.idTag] += "|+" + authorization_result.AccountBalance + "+" + "&" + price.ParkingFee + "&|" + price.Currency;
-
-									session.UserDisplayPrices[_request.idTag] = price.DisplayMessage;
-								}
-							}
-
-						}
-						//特例****飛宏客戶旗下的電樁,若遇到Portal沒回應的狀況 ~允許充電
-						if (session.CustomerId.ToString().ToUpper() == "8456AED9-6DD9-4BF3-A94C-9F5DCB9506F7" && confirm.idTagInfo.status == AuthorizationStatus.ConcurrentTx)
-						{
-							confirm.idTagInfo = new IdTagInfo() { expiryDate = DateTime.UtcNow.AddDays(1), status = AuthorizationStatus.Accepted };
-						}
-
-						result.Message = confirm;
-						result.Success = true;
-					}
-					break;
-				default:
-					{
-						logger.LogWarning(string.Format("Not Implement {0} Logic(ExecuteCoreRequest)", request.GetType().ToString().Replace("OCPPPackage.Messages.Core.", "")));
-					}
-					break;
-			}
-		}
-		catch (Exception ex)
-		{
-			logger.LogCritical("chargeBoxId:{0} {1}", session?.ChargeBoxId, action);
-			logger.LogCritical("Data {0}", request?.ToString());
-			logger.LogCritical("Error {0}", ex.ToString());
-			result.Exception = ex;
-		}
-
-
-		//if (action == Actions.Heartbeat)
-		//{
-		watch.Stop();
-		if (watch.ElapsedMilliseconds / 1000 > 3)
-		{
-			logger.LogError("Processing " + action.ToString() + " costs " + watch.ElapsedMilliseconds / 1000 + " seconds"); ;
-		}
-		//}
-
-		if (watch.ElapsedMilliseconds > 5_000)
-		{
-			//ThreadPool.GetAvailableThreads(out int workerThreads,out int completionThreads);
-			//logger.LogInformation($"ThreadPool workerThreads:{workerThreads} completionThreads:{completionThreads}");
-
-			//await blockingTreePrintService.PrintDbBlockingTree();
-			//await googleGetTimePrintService.Print();
-		}
-
-		return result;
-	}
-
-    async internal Task<MessageResult> ExecuteCoreConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
-	{
-		MessageResult result = new MessageResult() { Success = true };
-
-		try
-		{
-			switch (action)
-			{
-				case Actions.DataTransfer:
-					{
-						DataTransferConfirmation _confirm = confirm as DataTransferConfirmation;
-						DataTransferRequest _request = _confirm.GetRequest() as DataTransferRequest;
-						using (var db = await maindbContextFactory.CreateDbContextAsync())
-						{
-							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-							if (operation != null)
-							{
-								operation.FinishedOn = DateTime.UtcNow;
-								operation.Status = 1;//電樁有回覆
-								operation.EvseStatus = (int)_confirm.status;
-								operation.EvseValue = string.IsNullOrEmpty(_confirm.data) ? "" : _confirm.data;
-								await db.SaveChangesAsync();
-							}
-
-							if (_request.messageId == "ID_FirmwareVersion")
-							{
-								var machine = new Machine() { Id = session.MachineId };
-								if (machine != null)
-								{
-									db.ChangeTracker.AutoDetectChangesEnabled = false;
-									//db.Configuration.ValidateOnSaveEnabled = false;
-									db.Machine.Attach(machine);
-									machine.BoardVersions = _confirm.data;
-									db.Entry(machine).Property(x => x.BoardVersions).IsModified = true;
-									await db.SaveChangesAsync();
-								}
-							}
-
-							if (_request.messageId == "ID_TxEnergy") //計費
-							{
-								if (_confirm.status == DataTransferStatus.Accepted)
-								{
-									decimal couponPoint = 0m;
-									string farewellMessage = string.Empty;
-									string receipt = string.Empty;
-									List<ChargingBill> bill = new List<ChargingBill>();
-									List<ChargingPrice> chargingPrices = new List<ChargingPrice>();
-									var txEnergy = JsonConvert.DeserializeObject<TransactionEnergy>(_confirm.data);
-									var feedto = await db.TransactionRecord.Where(x => x.Id == txEnergy.TxId).Select(x => new { Id = x.Id, ConnectorId = x.ConnectorId, Fee = x.Fee, StopTime = x.StopTime, StartTime = x.StartTime, NotifyPnC = x.NotifyPnC }).FirstOrDefaultAsync();
-									decimal chargedEnergy = 0m;
-									if (feedto == null || string.IsNullOrEmpty(feedto.Fee)) return result;
-
-									if (!feedto.NotifyPnC && !string.IsNullOrEmpty(txEnergy.EVCCID))
-									{
-										//send PnC notification....
-										var customerInfo = db.Customer.Where(x => x.Id == session.CustomerId).Select(x => new { x.InstantStopTxReport, x.ApiUrl, x.ApiKey }).FirstOrDefault();
-
-										var request = new
-										{
-											SessionId = feedto.Id,
-											ChargeBoxId = session.ChargeBoxId,
-											EVCCID = txEnergy.EVCCID
-
-										};
-
-										logger.LogDebug(customerInfo.ApiUrl + "session_pncinfo=>" + JsonConvert.SerializeObject(request));
-
-										var response = await httpClient.Post(customerInfo.ApiUrl + "session_pncinfo", new Dictionary<string, string>()
-												{
-													{ "PartnerId",session.CustomerId.ToString()}
-
-												}, request, customerInfo.ApiKey);
-
-										if (response.Status == System.Net.HttpStatusCode.OK)
-										{
-											var _httpResult = JsonConvert.DeserializeObject<CPOOuterResponse>(response.Response);
-											logger.LogDebug("session_pncinfo Response" + JsonConvert.SerializeObject(response));
-											var pnc_info = db.TransactionRecord.Where(x => x.Id == txEnergy.TxId).FirstOrDefault();
-											if (response.Status == System.Net.HttpStatusCode.OK)
-											{
-												pnc_info.NotifyPnC = true;
-											}
-
-											pnc_info.Evccid = txEnergy.EVCCID;
-											db.ChangeTracker.AutoDetectChangesEnabled = false;
-											db.TransactionRecord.Attach(pnc_info);
-											db.Entry(pnc_info).Property(x => x.Evccid).IsModified = true;
-											db.Entry(pnc_info).Property(x => x.NotifyPnC).IsModified = true;
-
-											await db.SaveChangesAsync();
-										}
-									}
-
-									string currency = feedto.Fee.Substring(feedto.Fee.Length - 3);
-									decimal chargingCost = 0;
-									if (feedto.Fee.Length > 58)
-									{
-										chargingPrices = JsonConvert.DeserializeObject<List<ChargingPrice>>(feedto.Fee.Split('|')[0]);
-										foreach (var item in txEnergy.PeriodEnergy)
-										{
-											DateTime dt = new DateTime(2021, 01, 01, int.Parse(item.Key), 0, 0, DateTimeKind.Utc);
-											string startTime = dt.ToString("hh:mm tt", new CultureInfo("en-us"));
-											decimal perfee = 0;
-
-											// 小數點第5位4捨五入
-											var periodEnergy = PeriodEnergyRounding(item.Value);
-
-											chargedEnergy += periodEnergy;
-											if (chargingPrices.Count == 1)
-											{
-												perfee = Decimal.Multiply(periodEnergy, chargingPrices[0].Fee);
-												if (bill.Count == 0)
-												{
-													bill.Add(new ChargingBill()
-													{
-														StartTime = "12:00 AM",
-														EndTime = "11:59 PM",
-														Fee = chargingPrices[0].Fee
-													});
-												}
-
-												bill[0].PeriodEnergy += periodEnergy;
-
-											}
-											else
-											{
-												var price = chargingPrices.Where(x => x.StartTime == startTime).FirstOrDefault();
-												perfee = Decimal.Multiply(periodEnergy, price.Fee);
-
-												bill.Add(new ChargingBill()
-												{
-													StartTime = price.StartTime,
-													EndTime = price.EndTime,
-													PeriodEnergy = periodEnergy,
-													Fee = price.Fee,
-
-												});
-											}
-											if (bill.Count > 0)
-											{
-												bill[bill.Count - 1].Total += DollarRounding(perfee, session.Currency);
-												chargingCost += bill[bill.Count - 1].Total;
-
-												if (bill.Count == 1)
-												{
-
-													bill[bill.Count - 1].Total = DollarRounding(Decimal.Multiply(bill[0].PeriodEnergy, bill[0].Fee), session.Currency);
-													chargingCost = bill[bill.Count - 1].Total;
-												}
-											}
-										}
-									}
-									else
-									{
-										//以小時計費
-										foreach (var item in txEnergy.PeriodEnergy)
-										{
-											// 小數點第5位4捨五入
-											var periodEnergy = PeriodEnergyRounding(item.Value);
-											chargedEnergy += periodEnergy;
-										}
-
-										var fee = decimal.Parse(feedto.Fee.Split('|')[0]);
-										var charging_stoptime = feedto.StopTime == GlobalConfig.DefaultNullTime ? DateTime.Parse(DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm")) : DateTime.Parse(feedto.StopTime.ToString("yyyy/MM/dd HH:mm"));
-										var charging_starttime = DateTime.Parse(feedto.StartTime.ToString("yyyy/MM/dd HH:mm"));
-										chargingCost = Decimal.Multiply((decimal)charging_stoptime.Subtract(charging_starttime).TotalHours, fee);
-										chargingCost = DollarRounding(chargingCost, session.Currency);
-									}
-
-									// 計算停車費
-									var parkingFee = decimal.Parse(feedto.Fee.Split('&')[1]);
-									var stoptime = feedto.StopTime == GlobalConfig.DefaultNullTime ? DateTime.Parse(DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm")) : DateTime.Parse(feedto.StopTime.ToString("yyyy/MM/dd HH:mm"));
-									var starttime = DateTime.Parse(feedto.StartTime.ToString("yyyy/MM/dd HH:mm"));
-									var totalHours = stoptime.Subtract(starttime).TotalHours;
-									var parkingCost = Decimal.Multiply((decimal)totalHours, parkingFee);
-									parkingCost = DollarRounding(parkingCost, session.Currency);
-
-									if (feedto.StopTime != GlobalConfig.DefaultNullTime)
-									{
-										//var customerInfo = await db.Customer
-										//    .Where(x => x.Id == session.CustomerId).Select(x => new { x.InstantStopTxReport, x.ApiUrl, x.ApiKey })
-										//    .FirstOrDefaultAsync();
-										var customerInfo = await mainDbService.GetCustomer(session.CustomerId);
-
-										decimal accountBalance = 0;
-										decimal.TryParse(feedto.Fee.Split('+')[1], out accountBalance);
-
-										var tx = await db.TransactionRecord.Where(x => x.Id == txEnergy.TxId).FirstOrDefaultAsync();
-										if (tx == null)
-										{
-											logger.LogWarning("Tx is empty");
-											return result;
-										}
-
-										if (tx.BillingDone) return result;
-
-
-										var startTime = new DateTime(tx.StartTime.Year, tx.StartTime.Month, tx.StartTime.Day, tx.StartTime.Hour, 0, 0);
-										List<ChargingBill> confirmbill = new List<ChargingBill>();
-										receipt = string.Format("({0} )Energy:", chargedEnergy);
-
-										while (startTime < tx.StopTime)
-										{
-											if (bill.Count == 1)
-											{
-												confirmbill = bill;
-												receipt += string.Format("| {0} - {1}:| {2} kWh @ ${3}/kWh= ${4}", tx.StartTime.ToString("hh:mm tt", new CultureInfo("en-us")), tx.StopTime.ToString("hh:mm tt", new CultureInfo("en-us")),
-													confirmbill[0].PeriodEnergy.ToString("0.0000"), bill[0].Fee, bill[0].Total);
-
-												break;
-											}
-											if (bill.Count == 0)
-											{
-												receipt += string.Format("| {0} - {1} @ ${2}/hr= ${3}", feedto.StartTime.ToString("hh:mm tt", new CultureInfo("en-us")),
-												   feedto.StopTime.ToString("hh:mm tt", new CultureInfo("en-us")), feedto.Fee.Split('|')[0], chargingCost);
-
-												break;
-											}
-											if (bill.Count > 1)
-											{
-												var time = startTime.ToString("hh:mm tt", new CultureInfo("en-us"));
-												var tt = bill.Where(x => x.StartTime == time).FirstOrDefault();
-												confirmbill.Add(tt);
-												if (confirmbill.Count == 1)
-												{
-													confirmbill[0].StartTime = tx.StartTime.ToString("hh:mm tt", new CultureInfo("en-us"));
-												}
-
-
-												var stopTimeText = tx.StopTime.ToString("hh:mm tt", new CultureInfo("en-us"));
-												if (confirmbill[confirmbill.Count - 1].StartTime.Contains(stopTimeText.Split(' ')[1]))
-												{
-													var subHourText = (int.Parse(stopTimeText.Split(':')[0])).ToString();
-													subHourText = subHourText.Length == 1 ? "0" + subHourText : subHourText;
-													if (confirmbill[confirmbill.Count - 1].StartTime.Contains(subHourText))
-													{
-														confirmbill[confirmbill.Count - 1].EndTime = stopTimeText;
-													}
-
-												}
-												receipt += string.Format("| {0} - {1}:| {2} kWh @ ${3}/kWh= ${4}", confirmbill[confirmbill.Count - 1].StartTime, confirmbill[confirmbill.Count - 1].EndTime,
-													confirmbill[confirmbill.Count - 1].PeriodEnergy.ToString("0.0000"), confirmbill[confirmbill.Count - 1].Fee, confirmbill[confirmbill.Count - 1].Total);
-
-												if (confirmbill.Count == 24) break;
-
-											}
-											startTime = startTime.AddHours(1);
-
-										}
-
-										chargingCost = confirmbill.Count > 0 ? confirmbill.Sum(x => x.Total) : chargingCost;
-										receipt += string.Format("|Total Energy Fee : ${0}", chargingCost);
-
-										receipt += string.Format("|Parking Fee: | {0} - {1}: | {2} @ ${3}/hr= ${4}", feedto.StartTime.ToString("hh:mm tt", new CultureInfo("en-us")),
-										feedto.StopTime.ToString("hh:mm tt", new CultureInfo("en-us")), (totalHours / 1 >= 1) ? string.Format("{0} hours {1} minutes", (int)totalHours / 1, ((totalHours % 1) * 60).ToString("0.0")) : string.Format("{0} minutes", ((totalHours % 1) * 60).ToString("0.0")), parkingFee, parkingCost);
-										receipt += string.Format("|Stop Reason: {0}", tx.StopReason);
-
-										tx.Cost = chargingCost + parkingCost;
-
-
-										if (customerInfo != null && customerInfo.InstantStopTxReport)
-										{
-
-											var request = new
-											{
-												ChargeBoxId = tx.ChargeBoxId,
-												ConnectorId = tx.ConnectorId,
-												SessionId = tx.Id,
-												MeterStart = tx.MeterStart,
-												MeterStop = tx.MeterStop,
-												IdTag = tx.StartIdTag,
-												StartTime = tx.StartTime.ToString(GlobalConfig.UTC_DATETIMEFORMAT),
-												StopTime = tx.StopTime.ToString(GlobalConfig.UTC_DATETIMEFORMAT),
-												StopReason = tx.StopReasonId < 1 ? "Unknown" : (tx.StopReasonId > 12 ? "Unknown" : ((Reason)tx.StopReasonId).ToString()),
-												Receipt = tx.Receipt,
-												TotalCost = tx.Cost,
-												Fee = tx.Fee
-
-											};
-
-											logger.LogDebug("completed_session " + JsonConvert.SerializeObject(request));
-											var response = await httpClient.Post(customerInfo.ApiUrl + "completed_session", new Dictionary<string, string>()
-											{
-												{ "PartnerId",session.CustomerId.ToString()}
-
-											}, request, customerInfo.ApiKey);
-
-
-											logger.LogDebug("completed_session Response" + response.Response);
-
-											if (response.Success && !string.IsNullOrEmpty(response.Response))
-											{
-												var _httpResult = JsonConvert.DeserializeObject<CPOOuterResponse>(response.Response);
-												logger.LogDebug("completed_session Response" + response.Response);
-												JObject jo = JObject.Parse(_httpResult.Data);
-												if (jo.ContainsKey("CouponPoint"))
-												{
-													couponPoint = jo["CouponPoint"].Value<Decimal>();
-
-												}
-
-												if (jo.ContainsKey("FarewellMessage"))
-												{
-													farewellMessage = jo["FarewellMessage"].Value<string>();
-												}
-											}
-										}
-
-										tx.Receipt = receipt;
-										tx.BillingDone = true;
-										db.ChangeTracker.AutoDetectChangesEnabled = false;
-										//db.Configuration.ValidateOnSaveEnabled = false;
-										db.TransactionRecord.Attach(tx);
-										db.Entry(tx).Property(x => x.Cost).IsModified = true;
-										db.Entry(tx).Property(x => x.Receipt).IsModified = true;
-										db.Entry(tx).Property(x => x.BillingDone).IsModified = true;
-
-										await db.SaveChangesAsync();
-
-										await messageService.SendDataTransferRequest(
-											session.ChargeBoxId,
-											messageId: "FinalCost",
-											vendorId: "Phihong Technology",
-											data: JsonConvert.SerializeObject(new
-											{
-												txId = txEnergy.TxId,
-												description = JsonConvert.SerializeObject(new
-												{
-													chargedEnergy = chargedEnergy,
-													chargingFee = chargingCost,
-													parkTime = (int)stoptime.Subtract(starttime).TotalSeconds,
-													parkingFee = parkingCost,
-													currency = currency,
-													couponPoint = couponPoint,
-													accountBalance = accountBalance - tx.Cost,
-													farewellMessage = farewellMessage
-												})
-											})
-											);
-
-										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 = await webDbConnectionFactory.CreateAsync())
-										{
-											var parameters = new DynamicParameters();
-											parameters.Add("@IdTag", tx.StartIdTag, DbType.String, ParameterDirection.Input, 50);
-											parameters.Add("@parentIdTag", accountBalance - tx.Cost, DbType.String, ParameterDirection.Input, 50);
-											string strSql = "update [dbo].[LocalListDetail] set parentIdTag =@parentIdTag  where ListId = 27 and IdTag=@IdTag; ";
-											await conn.ExecuteAsync(strSql, parameters);
-
-										}
-
-										#region 提供給PHA 過CDFA認證 使用
-										if (tx.CustomerId == Guid.Parse("10C7F5BD-C89A-4E2A-8611-B617E0B41A73"))
-										{
-											var mail_response = httpClient.PostFormDataAsync("https://charge.zerovatech.com/CDFA/" + tx.Id, new Dictionary<string, string>()
-											{
-												{ "email","2"},
-												{ "to","wonderj@phihongusa.com;jessica_tseng@phihong.com.tw"}
-                                                //{ "to","jessica_tseng@phihong.com.tw"}
-                                           }, null);
-
-											logger.LogTrace(JsonConvert.SerializeObject(mail_response));
-
-										}
-										#endregion
-
-									}
-									else
-									{
-										await messageService.SendDataTransferRequest(
-											session.ChargeBoxId,
-											messageId: "RunningCost",
-											vendorId: "Phihong Technology",
-											data: JsonConvert.SerializeObject(new
-											{
-												txId = txEnergy.TxId,
-												description = JsonConvert.SerializeObject(new
-												{
-													chargedEnergy = chargedEnergy,
-													chargingFee = chargingCost,
-													parkTime = (int)stoptime.Subtract(starttime).TotalSeconds,
-													parkingFee = parkingCost,
-													currency = currency
-												})
-
-											})
-											);
-
-										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
-											);
-									}
-								}
-
-							}
-
-							#region 台泥
-							if (_request.messageId == "ID_GetTxUserInfo")
-							{
-								var txUserInfo = JsonConvert.DeserializeObject<ID_GetTxUserInfo>(_confirm.data);
-								if (session.CustomerId == new Guid("009E603C-79CD-4620-A2B8-D9349C0E8AD8"))
-								{
-									var request = new
-									{
-										ChargeBoxId = session.ChargeBoxId,
-										ConnectorId = txUserInfo.ConnectorId,
-										SessionId = txUserInfo.TxId,
-										SerialNo = txUserInfo.SerialNo,
-										StartTime = txUserInfo.StartTime.ToString(GlobalConfig.UTC_DATETIMEFORMAT),
-										VEMData = txUserInfo.VEMData
-									};
-
-									var response = httpClient.Post(GlobalConfig.TCC_API_URL + "start_session", new Dictionary<string, string>()
-									{
-										{ "PartnerId",session.CustomerId.ToString()}
-
-									}, request, GlobalConfig.TCC_SALTKEY);
-
-									logger.LogDebug(JsonConvert.SerializeObject(response));
-								}
-
-							}
-
-							#endregion
-						}
-					}
-					break;
-				case Actions.ChangeAvailability:
-					{
-						ChangeAvailabilityConfirmation _confirm = confirm as ChangeAvailabilityConfirmation;
-						ChangeAvailabilityRequest _request = _confirm.GetRequest() as ChangeAvailabilityRequest;
-
-						using (var db = await maindbContextFactory.CreateDbContextAsync())
-						{
-							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-							if (operation != null)
-							{
-								operation.FinishedOn = DateTime.UtcNow;
-								operation.Status = 1;//電樁有回覆
-								operation.EvseStatus = (int)_confirm.status;
-								operation.EvseValue = _confirm.status.ToString();
-								await db.SaveChangesAsync();
-							}
-
-						}
-					}
-					break;
-				case Actions.ClearCache:
-					{
-						ClearCacheConfirmation _confirm = confirm as ClearCacheConfirmation;
-						ClearCacheRequest _request = _confirm.GetRequest() as ClearCacheRequest;
-
-						using (var db = await maindbContextFactory.CreateDbContextAsync())
-						{
-							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-							if (operation != null)
-							{
-								operation.FinishedOn = DateTime.UtcNow;
-								operation.Status = 1;//電樁有回覆
-								operation.EvseStatus = (int)_confirm.status;
-								operation.EvseValue = _confirm.status.ToString();
-								await db.SaveChangesAsync();
-							}
-
-						}
-					}
-					break;
-				case Actions.RemoteStartTransaction:
-					{
-						RemoteStartTransactionConfirmation _confirm = confirm as RemoteStartTransactionConfirmation;
-						RemoteStartTransactionRequest _request = _confirm.GetRequest() as RemoteStartTransactionRequest;
-
-						using (var db = await maindbContextFactory.CreateDbContextAsync())
-						{
-							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-							if (operation != null)
-							{
-								operation.FinishedOn = DateTime.UtcNow;
-								operation.Status = 1;//電樁有回覆
-								operation.EvseStatus = (int)_confirm.status;
-								operation.EvseValue = _confirm.status.ToString();
-								await db.SaveChangesAsync();
-							}
-
-						}
-					}
-					break;
-				case Actions.RemoteStopTransaction:
-					{
-						RemoteStopTransactionConfirmation _confirm = confirm as RemoteStopTransactionConfirmation;
-						RemoteStopTransactionRequest _request = _confirm.GetRequest() as RemoteStopTransactionRequest;
-
-						using (var db = await maindbContextFactory.CreateDbContextAsync())
-						{
-							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-							if (operation != null)
-							{
-								operation.FinishedOn = DateTime.UtcNow;
-								operation.Status = 1;//電樁有回覆
-								operation.EvseStatus = (int)_confirm.status;
-								operation.EvseValue = _confirm.status.ToString();
-								await db.SaveChangesAsync();
-							}
-
-						}
-					}
-					break;
-				case Actions.Reset:
-					{
-						ResetConfirmation _confirm = confirm as ResetConfirmation;
-						ResetRequest _request = _confirm.GetRequest() as ResetRequest;
-
-						using (var db = await maindbContextFactory.CreateDbContextAsync())
-						{
-							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-							if (operation != null)
-							{
-								operation.FinishedOn = DateTime.UtcNow;
-								operation.Status = 1;//電樁有回覆
-								operation.EvseStatus = (int)_confirm.status;
-								operation.EvseValue = _confirm.status.ToString();
-								await db.SaveChangesAsync();
-							}
-
-						}
-					}
-					break;
-				case Actions.ChangeConfiguration:
-					{
-						ChangeConfigurationConfirmation _confirm = confirm as ChangeConfigurationConfirmation;
-						ChangeConfigurationRequest _request = _confirm.GetRequest() as ChangeConfigurationRequest;
-
-						using (var db = await maindbContextFactory.CreateDbContextAsync())
-						{
-							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-					 x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-
-							if (operation != null)
-							{
-								operation.FinishedOn = DateTime.UtcNow;
-								operation.Status = 1;//電樁有回覆
-								operation.EvseStatus = (int)_confirm.status;
-								operation.EvseValue = _confirm.status.ToString();
-
-							}
-
-							if (_confirm.status == Packet.Messages.SubTypes.ConfigurationStatus.Accepted || _confirm.status == Packet.Messages.SubTypes.ConfigurationStatus.RebootRequired)
-							{
-								var configure = await db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId).ToListAsync();
-
-								var foundConfig = configure.Find(x => x.ConfigureName == _request.key);
-								if (foundConfig != null)
-								{
-									foundConfig.ReadOnly = false;
-									foundConfig.ConfigureSetting = _request.value;
-								}
-								else
-								{
-									await db.MachineConfigurations.AddAsync(new MachineConfigurations()
-									{
-										ChargeBoxId = session.ChargeBoxId,
-										ConfigureName = _request.key,
-										ReadOnly = false,
-										ConfigureSetting = _request.value
-									});
-								}
-							}
-							await db.SaveChangesAsync();
-						}
-
-					}
-					break;
-				case Actions.GetConfiguration:
-					{
-
-						try
-						{
-							GetConfigurationConfirmation _confirm = confirm as GetConfigurationConfirmation;
-							//  GetConfigurationRequest _request = _confirm.GetRequest() as GetConfigurationRequest;
-
-							List<Task> updateTasks = new List<Task>();
-                            List<MachineConfigurations> configure = await mainDbService.GetMachineConfiguration(session.ChargeBoxId);
-
-                            if (_confirm.configurationKey != null)
-                            {
-                                foreach (var item in _confirm.configurationKey)
-                                {
-                                    string oldValue = string.Empty;
-                                    if (item.key == null)
-                                    {
-                                        logger.LogTrace("*********************");
-                                    }
-
-                                    var foundConfig = configure.Find(x => x.ConfigureName == item.key);
-                                    var updateValue = string.IsNullOrEmpty(item.value) ? string.Empty : item.value;
-
-                                    if (foundConfig == null)
-                                    {
-                                        await mainDbService.AddMachineConfiguration(session.ChargeBoxId, item.key, updateValue, item.IsReadOnly);
-                                    }
-                                    else if (item.value != foundConfig.ConfigureSetting)
-                                    {
-                                        if (foundConfig.ConfigureName == null)
-                                        {
-                                            logger.LogTrace("*********************");
-                                        }
-
-                                        if (foundConfig.ConfigureName == "SecurityProfile")
-                                        {
-                                            oldValue = foundConfig.ConfigureSetting;
-                                        }
-
-                                        await mainDbService.UpdateMachineConfiguration(session.ChargeBoxId, item.key, updateValue, item.IsReadOnly);
-                                    }
-                                }
-                            }
-                            if (_confirm.unknownKey != null)
-                            {
-
-                                foreach (var item in _confirm.unknownKey)
-                                {
-                                    var foundConfig = configure.Find(x => x.ConfigureName == item);
-                                    if (foundConfig != null)
-                                    {
-                                        await mainDbService.UpdateMachineConfiguration(session.ChargeBoxId, item, string.Empty, isReadOnly: true, isExists: false);
-                                    }
-                                    else
-                                    {
-                                        await mainDbService.AddMachineConfiguration(session.ChargeBoxId, item, string.Empty, isReadOnly: true, isExist: false);
-                                    }
-                                }
-                            }
-
-                            using (var db = await maindbContextFactory.CreateDbContextAsync())
-                            {
-                                var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-							   x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-
-								if (operation != null)
-								{
-									operation.FinishedOn = DateTime.UtcNow;
-									operation.Status = 1;//電樁有回覆
-									operation.EvseStatus = 1;
-									operation.EvseValue = JsonConvert.SerializeObject(_confirm.configurationKey, Formatting.None);
-
-								}
-
-								await db.SaveChangesAsync();
-
-							}
-						}
-						catch (Exception ex)
-						{
-							logger.LogError(ex.ToString());
-						}
-
-					}
-					break;
-				case Actions.UnlockConnector:
-					{
-						UnlockConnectorConfirmation _confirm = confirm as UnlockConnectorConfirmation;
-						UnlockConnectorRequest _request = _confirm.GetRequest() as UnlockConnectorRequest;
-
-						using (var db = await maindbContextFactory.CreateDbContextAsync())
-						{
-							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-							if (operation != null)
-							{
-								operation.FinishedOn = DateTime.UtcNow;
-								operation.Status = 1;//電樁有回覆
-								operation.EvseStatus = (int)_confirm.status;
-								operation.EvseValue = _confirm.status.ToString();
-								await db.SaveChangesAsync();
-							}
-
-						}
-					}
-					break;
-				default:
-					{
-						logger.LogWarning(string.Format("Not Implement {0} Logic(ExecuteCoreConfirm)", confirm.GetType().ToString().Replace("OCPPPackage.Messages.Core.", "")));
-					}
-					break;
-			}
-		}
-		catch (Exception ex)
-		{
-			logger.LogDebug("123 " + action + " " + ex.ToString());
-		}
-
-
-
-		return result;
-	}
-
-
-	internal async Task<MessageResult> ReceivedCoreError(Actions action, string errorMsg, WsClientData session, string requestId)
-	{
-		MessageResult result = new MessageResult() { Success = true };
-
-		switch (action)
-		{
-			case Actions.ChangeAvailability:
-			case Actions.ChangeConfiguration:
-			case Actions.ClearCache:
-			case Actions.RemoteStartTransaction:
-			case Actions.RemoteStopTransaction:
-			case Actions.Reset:
-			case Actions.GetConfiguration:
-			case Actions.UnlockConnector:
-			case Actions.DataTransfer:
-				{
-					if (action == Actions.DataTransfer)
-					{
-						logger.LogDebug(string.Format("DataTransfer Error {0}: {1}", session.ChargeBoxId, requestId));
-					}
-
-					using (var db = await maindbContextFactory.CreateDbContextAsync())
-					{
-						var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-						x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-						if (operation != null)
-						{
-							operation.FinishedOn = DateTime.UtcNow;
-							operation.Status = 1;//電樁有回覆
-							operation.EvseStatus = (int)255;//錯誤
-							operation.EvseValue = errorMsg;
-							await db.SaveChangesAsync();
-						}
-
-					}
-
-				}
-				break;
-
-			default:
-				{
-					logger.LogWarning(string.Format("Not Implement {0} Logic(ReceivedCoreError)", action));
-				}
-				break;
-		}
-		return result;
-
-	}
-
-
-	/// <summary>
-	/// 依據幣值處理4捨5入
-	/// </summary>
-	/// <param name="money"></param>
-	/// <param name="currency"></param>
-	/// <returns></returns>
-	private decimal DollarRounding(decimal money, string currency)
-	{
-
-
-		if (currency == "USD" || currency == "EUR")
-		{
-
-			//0.4867
-			if ((double)((int)(money * 100) + 0.5) <= (double)(money * 100))
-			{
-
-				//money = Decimal.Add(money, (decimal)0.01);//0.4967
-
-			}
-			money = Math.Round(money, 2, MidpointRounding.AwayFromZero);
-			money = Decimal.Parse(money.ToString("0.00"));
-
-
-		}
-		else
-		{
-			if ((double)((int)(money) + 0.5) <= (double)money)
-			{
-				//  money = (int) money + 1;
-			}
-			money = Math.Round(money, 0, MidpointRounding.AwayFromZero);
-			money = Decimal.Parse(money.ToString("0"));
-		}
-
-		return money;
-	}
-
-	/// <summary>
-	/// PeriodEnergy處理4捨5入
-	/// </summary>
-	/// <param name="money"></param>
-	/// <param name="currency"></param>
-	/// <returns></returns>
-	private decimal PeriodEnergyRounding(decimal energy)
-	{
-		energy = Math.Round(energy, 4, MidpointRounding.AwayFromZero);
-		energy = Decimal.Parse(energy.ToString("0.0000"));
-		return energy;
-	}
-
-    private static bool CheckNeedAddMachineError(StatusNotificationRequest _request, ConnectorStatus _oldStatus)
-    {
-		if (_request.status != ChargePointStatus.Faulted)
-		{
-			return false;
-		}
-
-        bool isNeedAddMachineError = false;
-        if (_oldStatus == null)
-            isNeedAddMachineError = true;
-
-        if (_oldStatus != null)
-        {
-            if (_oldStatus.Status != (int)ChargePointStatus.Faulted)
-            {
-                isNeedAddMachineError = true;
-            }
-            else if (_oldStatus.ChargePointErrorCodeId != (int)_request.errorCode && _oldStatus.VendorErrorCode != _request.vendorErrorCode)
-            {
-                isNeedAddMachineError = true;
-            }
-        }
-
-        return isNeedAddMachineError;
-    }
-}
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.Core;
+using EVCB_OCPP.Packet.Messages.SubTypes;
+using EVCB_OCPP.WSServer.Dto;
+using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Service;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+using System.Data;
+using System.Diagnostics;
+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;
+
+public class ID_CreditDeductResult
+{
+	public int txId { set; get; }
+
+	public string creditNo { set; get; }
+
+
+	public bool deductResult { set; get; }
+
+	public bool isDonateInvoice { set; get; }
+
+
+	public decimal amount { set; get; }
+
+	public string approvalNo { set; get; }
+
+}
+
+public class ID_ReaderStatus
+{
+	public int ConnectorId { set; get; }
+
+	public string creditNo { set; get; }
+
+
+	public string SerialNo { set; get; }
+
+	public int readerStatus { set; get; }
+
+	public string VEMData { set; get; }
+
+	public DateTime Timestamp { set; get; }
+
+
+
+
+}
+
+internal partial class ProfileHandler
+{
+	private readonly ILogger logger;
+	//private readonly BlockingTreePrintService blockingTreePrintService;
+	//private readonly GoogleGetTimePrintService googleGetTimePrintService;
+	private readonly ServerMessageService messageService;
+
+	//private readonly string webConnectionString;// = ConfigurationManager.ConnectionStrings[].ConnectionString;
+	private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+	private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
+	private readonly MeterValueDbService meterValueDbService;
+
+	//private readonly IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory;
+	private readonly IBusinessServiceFactory businessServiceFactory;
+	private readonly IMainDbService mainDbService;
+	private OuterHttpClient httpClient;
+
+	public ProfileHandler(
+		IConfiguration configuration,
+		IDbContextFactory<MainDBContext> maindbContextFactory,
+		ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory,
+		//IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory,
+		MeterValueDbService meterValueDbService,
+		IBusinessServiceFactory businessServiceFactory,
+		IMainDbService mainDbService,
+		ILogger<ProfileHandler> logger,
+		//BlockingTreePrintService blockingTreePrintService,
+		//GoogleGetTimePrintService googleGetTimePrintService,
+		ServerMessageService messageService,
+		OuterHttpClient httpClient)
+	{
+		//webConnectionString = configuration.GetConnectionString("WebDBContext");
+
+		this.logger = logger;
+		//this.blockingTreePrintService = blockingTreePrintService;
+		//this.googleGetTimePrintService = googleGetTimePrintService;
+		this.messageService = messageService;
+		this.maindbContextFactory = maindbContextFactory;
+		this.webDbConnectionFactory = webDbConnectionFactory;
+		this.meterValueDbService = meterValueDbService;
+		this.mainDbService = mainDbService;
+		//this.metervaluedbContextFactory = metervaluedbContextFactory;
+		this.businessServiceFactory = businessServiceFactory;
+		this.httpClient = httpClient;
+	}
+
+	async internal Task<MessageResult> ExecuteCoreRequest(Actions action, WsClientData session, IRequest request)
+	{
+		Stopwatch watch = new Stopwatch();
+		//if (action == Actions.Heartbeat || action == Actions.StopTransaction)
+		//{
+		//    watch.Start();
+		//}
+		watch.Start();
+		MessageResult result = new MessageResult() { Success = false };
+
+		try
+		{
+			switch (action)
+			{
+				case Actions.DataTransfer:
+					{
+						DataTransferRequest _request = request as DataTransferRequest;
+						var confirm = new DataTransferConfirmation() { status = DataTransferStatus.UnknownMessageId };
+
+						if (_request.messageId == "ID_CreditDeductResult")
+						{
+
+							var creditDeductResult = JsonConvert.DeserializeObject<ID_CreditDeductResult>(_request.data);
+							if (session.CustomerId == new Guid("009E603C-79CD-4620-A2B8-D9349C0E8AD8"))
+							{
+								var report = new
+								{
+									ChargeBoxId = session.ChargeBoxId,
+									IsDonateInvoice = creditDeductResult.isDonateInvoice,
+									CreditNo = creditDeductResult.creditNo,
+									DeductResult = creditDeductResult.deductResult,
+									SessionId = creditDeductResult.txId,
+									ApprovalNo = creditDeductResult.approvalNo,
+									TotalCost = creditDeductResult.amount,
+
+								};
+
+								var response = await httpClient.Post(GlobalConfig.TCC_API_URL + "prepare_issue_invoice", new Dictionary<string, string>()
+									{
+										{ "PartnerId",session.CustomerId.ToString()}
+
+									}, report, GlobalConfig.TCC_SALTKEY);
+
+								logger.LogDebug(JsonConvert.SerializeObject(response));
+							}
+
+
+							confirm.status = DataTransferStatus.Accepted;
+							confirm.data = JsonConvert.SerializeObject(new { txId = creditDeductResult.txId, creditNo = creditDeductResult.creditNo, msgId = _request.messageId });
+						}
+						if (_request.messageId == "ID_ReaderStatus")
+						{
+							if (session.CustomerId == new Guid("009E603C-79CD-4620-A2B8-D9349C0E8AD8"))
+							{
+								var preauth_status = JsonConvert.DeserializeObject<ID_ReaderStatus>(_request.data);
+								var report = new
+								{
+									ChargeBoxId = session.ChargeBoxId,
+									ConnectorId = preauth_status.ConnectorId,
+									CreditNo = preauth_status.creditNo,
+									ReaderStatus = preauth_status.readerStatus,
+									SerialNo = preauth_status.SerialNo,
+									VEMData = preauth_status.VEMData,
+									Timestamp = preauth_status.Timestamp
+
+								};
+
+								var response = await httpClient.Post(GlobalConfig.TCC_API_URL + "preauth_status", new Dictionary<string, string>()
+									{
+										{ "PartnerId",session.CustomerId.ToString()}
+
+									}, report, GlobalConfig.TCC_SALTKEY);
+
+
+								confirm.status = DataTransferStatus.Accepted;
+							}
+						}
+						if (_request.messageId == "ID_OCMF")
+						{
+							JObject jo = JObject.Parse(_request.data);
+
+							logger.LogDebug("{0}\r\n{1}\r\n{2}", jo["txId"].Value<int>(), jo["dataString"].Value<string>(), jo["publicKey"].Value<string>());
+
+							await mainDbService.AddOCMF(new Ocmf()
+							{
+								TransactionId = jo["txId"].Value<int>(),
+								DataString = jo["dataString"].Value<string>(),
+								PublicKey = jo["publicKey"].Value<string>()
+							});
+
+							confirm.status = DataTransferStatus.Accepted;
+							confirm.data = JsonConvert.SerializeObject(new { txId = jo["txId"].Value<int>(), msgId = _request.messageId });
+						}
+
+						if (_request.messageId == "Authorize")
+						{
+							string iso15118_token = string.Empty;
+							JObject jo = JObject.Parse(_request.data);
+							if (jo.ContainsKey("idToken"))
+							{
+								iso15118_token = jo["idToken"]["idToken"].Value<string>();
+
+							}
+
+							confirm.status = DataTransferStatus.Accepted;
+							confirm.data = JsonConvert.SerializeObject(
+								new
+								{
+									certificateStatus = iso15118_token == "12345678901234" ? "Accepted" : "CertificateExpired",
+									idTokenInfo = new
+									{
+										status = iso15118_token == "12345678901234" ? "Accepted" : "Invalid"
+									}
+								});
+						}
+						result.Message = confirm;
+						result.Success = true;
+					}
+					break;
+				case Actions.BootNotification:
+					{
+						BootNotificationRequest _request = request as BootNotificationRequest;
+						int heartbeat_interval = GlobalConfig.GetHEARTBEAT_INTERVAL();
+						//var _machine = db.Machine.FirstOrDefault(x => x.ChargeBoxId == session.ChargeBoxId);
+
+						if (session.BootStatus == BootStatus.Startup)
+						{
+                            Machine _machine = new();
+                            _machine.ChargeBoxSerialNumber = string.IsNullOrEmpty(_request.chargeBoxSerialNumber) ? string.Empty : _request.chargeBoxSerialNumber;
+                            _machine.ChargePointSerialNumber = string.IsNullOrEmpty(_request.chargePointSerialNumber) ? string.Empty : _request.chargePointSerialNumber;
+                            _machine.ChargePointModel = string.IsNullOrEmpty(_request.chargePointModel) ? string.Empty : _request.chargePointModel;
+                            _machine.ChargePointVendor = string.IsNullOrEmpty(_request.chargePointVendor) ? string.Empty : _request.chargePointVendor;
+                            _machine.FwCurrentVersion = string.IsNullOrEmpty(_request.firmwareVersion) ? string.Empty : _request.firmwareVersion;
+                            _machine.Iccid = string.IsNullOrEmpty(_request.iccid) ? string.Empty : _request.iccid;
+                            //_machine.Iccid = DateTime.UtcNow.ToString("yy-MM-dd HH:mm");
+                            _machine.Imsi = string.IsNullOrEmpty(_request.imsi) ? string.Empty : _request.imsi;
+                            _machine.MeterSerialNumber = string.IsNullOrEmpty(_request.meterSerialNumber) ? string.Empty : _request.meterSerialNumber;
+                            _machine.MeterType = string.IsNullOrEmpty(_request.meterType) ? string.Empty : _request.meterType;
+
+                            await mainDbService.UpdateMachineBasicInfo(session.ChargeBoxId, _machine);
+                        }
+
+						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:
+                                var configValue = await mainDbService.GetMachineHeartbeatInterval(session.ChargeBoxId);
+
+                                if (configValue != null)
+                                {
+                                    int.TryParse(configValue, out heartbeat_interval);
+                                    heartbeat_interval = heartbeat_interval == 0 ? GlobalConfig.GetHEARTBEAT_INTERVAL() : heartbeat_interval;
+                                }
+
+                                toReturnInterval = heartbeat_interval;
+                                toReturnRegistrationStatus = RegistrationStatus.Accepted;
+                                session.BootStatus = BootStatus.Accepted;
+                                break;
+                        }
+
+                        var confirm = new BootNotificationConfirmation()
+						{
+							currentTime = DateTime.UtcNow,
+                            interval = toReturnInterval,
+                            status = toReturnRegistrationStatus
+                        };
+
+						result.Message = confirm;
+						result.Success = true;
+					}
+					break;
+				case Actions.StatusNotification:
+                    {
+                        var statusNotificationTimer = Stopwatch.StartNew();
+                        long s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0;
+                        //只保留最新上報狀況
+                        StatusNotificationRequest _request = request as StatusNotificationRequest;
+                        int preStatus = 0;
+                        ConnectorStatus _oldStatus;
+
+                        _oldStatus = await mainDbService.GetConnectorStatus(session.ChargeBoxId, _request.connectorId);
+
+                        s1 = statusNotificationTimer.ElapsedMilliseconds;
+
+                        if (_oldStatus != null && (_request.status != (ChargePointStatus)_oldStatus.Status || _request.status == ChargePointStatus.Faulted))
+                        {
+                            preStatus = _oldStatus.Status;
+
+                            await mainDbService.UpdateConnectorStatus(_oldStatus.Id, new ConnectorStatus()
+                            {
+                                CreatedOn = _request.timestamp.HasValue ? _request.timestamp.Value : DateTime.UtcNow,
+                                Status = (int)_request.status,
+                                ChargePointErrorCodeId = (int)_request.errorCode,
+                                ErrorInfo = string.IsNullOrEmpty(_request.info) ? string.Empty : _request.info,
+                                VendorId = string.IsNullOrEmpty(_request.vendorId) ? string.Empty : _request.vendorId,
+                                VendorErrorCode = string.IsNullOrEmpty(_request.vendorErrorCode) ? string.Empty : _request.vendorErrorCode
+                            });
+
+                            if (preStatus == (int)ChargePointStatus.Faulted)
+                            {
+                                if (_request.status != ChargePointStatus.Faulted ||
+                                    (_oldStatus.ChargePointErrorCodeId != (int)_request.errorCode && _oldStatus.VendorErrorCode != _request.vendorErrorCode))
+                                {
+                                    await mainDbService.FillupFinishedTimetoMachineError(
+                                        ChargeBoxId: session.ChargeBoxId,
+                                        ConnectorId: (byte)_request.connectorId,
+                                        FinishedOn: _request.timestamp.HasValue ? _request.timestamp.Value : DateTime.UtcNow
+                                        );
+                                }
+
+                            }
+                        }
+
+                        s2 = statusNotificationTimer.ElapsedMilliseconds;
+
+                        if (_oldStatus == null)
+                        {
+                            await mainDbService.AddConnectorStatus(
+                                ChargeBoxId: session.ChargeBoxId,
+                                ConnectorId: (byte)_request.connectorId,
+                                CreatedOn: _request.timestamp.HasValue ? _request.timestamp.Value : DateTime.UtcNow,
+                                Status: (int)_request.status,
+                                ChargePointErrorCodeId: (int)_request.errorCode,
+                                ErrorInfo: string.IsNullOrEmpty(_request.info) ? string.Empty : _request.info,
+                                VendorId: string.IsNullOrEmpty(_request.vendorId) ? string.Empty : _request.vendorId,
+                                VendorErrorCode: string.IsNullOrEmpty(_request.vendorErrorCode) ? string.Empty : _request.vendorErrorCode);
+                        }
+                        s3 = statusNotificationTimer.ElapsedMilliseconds;
+
+                        bool isNeedAddMachineError = CheckNeedAddMachineError(_request, _oldStatus);
+
+                        //if (_request.status == Packet.Messages.SubTypes.ChargePointStatus.Faulted &&
+                        //(_oldStatus != null && 
+                        //(_oldStatus.ChargePointErrorCodeId != (int)_request.errorCode && _oldStatus.VendorErrorCode != _request.vendorErrorCode)))
+                        if (isNeedAddMachineError)
+                        {
+                            await mainDbService.AddMachineError(ConnectorId: (byte)_request.connectorId,
+                                    CreatedOn: _request.timestamp.HasValue ? _request.timestamp.Value : DateTime.UtcNow,
+                                    Status: (int)_request.status,
+                                    ChargeBoxId: session.ChargeBoxId,
+                                    ErrorCodeId: (int)_request.errorCode,
+                                    ErrorInfo: string.IsNullOrEmpty(_request.info) ? string.Empty : _request.info,
+                                    PreStatus: _oldStatus == null ? -1 : preStatus,
+                                    VendorErrorCode: string.IsNullOrEmpty(_request.vendorErrorCode) ? string.Empty : _request.vendorErrorCode,
+                                    VendorId: string.IsNullOrEmpty(_request.vendorId) ? string.Empty : _request.vendorId);
+                        }
+
+                        s4 = statusNotificationTimer.ElapsedMilliseconds;
+                        if (_request.status == Packet.Messages.SubTypes.ChargePointStatus.Faulted)
+                        {
+                            //var businessService = BusinessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
+                            //var businessService = await serviceProvider.GetService<BusinessServiceFactory>().CreateBusinessService(session.CustomerId.ToString());
+                            var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
+                            var notification = businessService.NotifyFaultStatus(new ErrorDetails()
+                            {
+                                ChargeBoxId = session.ChargeBoxId,
+                                ConnectorId = _request.connectorId,
+                                ErrorCode = _request.errorCode,
+                                Info = string.IsNullOrEmpty(_request.info) ? string.Empty : _request.info,
+                                OCcuredOn = _request.timestamp ?? DateTime.UtcNow,
+                                VendorErrorCode = string.IsNullOrEmpty(_request.vendorErrorCode) ? string.Empty : _request.vendorErrorCode,
+
+                            });
+                        }
+                        s5 = statusNotificationTimer.ElapsedMilliseconds;
+
+                        var confirm = new StatusNotificationConfirmation() { };
+                        result.Message = confirm;
+                        result.Success = true;
+
+                        statusNotificationTimer.Stop();
+                        if (statusNotificationTimer.ElapsedMilliseconds / 1000 > 1)
+                        {
+                            logger.LogCritical(string.Format("StatusNotification took {0}/{1}/{2}/{3}/{4}", s1, s2, s3, s4, s5));
+                        }
+                    }
+                    break;
+				case Actions.Heartbeat:
+					{
+
+						var confirm = new HeartbeatConfirmation() { currentTime = DateTime.UtcNow };
+						result.Message = confirm;
+						result.Success = true;
+					}
+					break;
+				case Actions.MeterValues:
+					{
+						var meterValueTimer = Stopwatch.StartNew();
+						long s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, insertTasksCnt = 0;
+						MeterValuesRequest _request = request as MeterValuesRequest;
+
+						if (_request.meterValue.Count > 0)
+						{
+							s1 = meterValueTimer.ElapsedMilliseconds;
+							foreach (var item in _request.meterValue)
+							{
+								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;
+
+
+										using (var maindb = await maindbContextFactory.CreateDbContextAsync())
+										{
+											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()
+										});
+									}
+
+								}
+
+							}
+
+							s2 = meterValueTimer.ElapsedMilliseconds;
+							//List<Task> insertTasks = new();
+							List<InsertMeterValueParam> datas = new();
+							foreach (var item in _request.meterValue)
+							{
+								foreach (var sampleVaule in item.sampledValue)
+								{
+									if (sampleVaule.format == ValueFormat.SignedData) continue;
+
+									decimal value = Convert.ToDecimal(sampleVaule.value);
+									datas.Add(new InsertMeterValueParam(
+										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));
+									//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);
+
+									//var task = Task.Delay(2_000); 
+									//insertTasks.Add(task);
+								}
+							}
+							//insertTasksCnt = insertTasks.Count;
+							insertTasksCnt = datas.Count;
+							s3 = meterValueTimer.ElapsedMilliseconds;
+							//await Task.WhenAll(insertTasks);
+							await meterValueDbService.InsertBundleAsync(datas);
+							s4 = meterValueTimer.ElapsedMilliseconds;
+						}
+
+						//  if (energy_kwh > 0)
+						if (_request.transactionId != null)
+						{
+							try
+							{
+								if (session.IsBilling)
+								{
+									await messageService.SendDataTransferRequest(
+										session.ChargeBoxId,
+										messageId: "ID_TxEnergy",
+										vendorId: "Phihong Technology",
+										data: JsonConvert.SerializeObject(new { txId = _request.transactionId, ConnectorId = _request.connectorId })
+										);
+								}
+							}
+							catch (Exception ex)
+							{
+
+								logger.LogTrace(string.Format("{0} :{1}", session.ChargeBoxId + " RunningCost", ex.Message));
+
+							}
+
+						}
+
+						s5 = meterValueTimer.ElapsedMilliseconds;
+						meterValueTimer.Stop();
+						if (meterValueTimer.ElapsedMilliseconds / 1000 > 1)
+						{
+
+							logger.LogCritical(string.Format("MeterValues took {0}/{1}/{2}/{3}/{4}:{5}", s1 / 1000, s2 / 1000, s3 / 1000, s4 / 1000, s5 / 1000, insertTasksCnt));
+						}
+
+						var confirm = new MeterValuesConfirmation() { };
+						result.Message = confirm;
+						result.Success = true;
+					}
+					break;
+				case Actions.StartTransaction:
+					{
+						var timer = Stopwatch.StartNew();
+						long t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0;
+
+						StartTransactionRequest _request = request as StartTransactionRequest;
+
+						int _transactionId = -1;
+
+						var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
+						t0 = timer.ElapsedMilliseconds;
+
+						var _idTagInfo = new IdTagInfo() { expiryDate = DateTime.UtcNow.AddDays(1), status = AuthorizationStatus.Accepted };
+
+						#region PnC 邏輯
+
+						if (!string.IsNullOrEmpty(_request.idTag))
+						{
+							_request.idTag = _request.idTag.StartsWith("vid:") ? _request.idTag.Replace("vid:", "") : _request.idTag;
+						}
+
+						#endregion
+
+						if (_request.idTag != "Backend")
+						{
+							var authorization_result = await businessService.Authorize(session.ChargeBoxId, _request.idTag, source: Actions.StartTransaction.ToString());
+							_idTagInfo = authorization_result.IdTagInfo;
+							t1 = timer.ElapsedMilliseconds;
+
+							if (_idTagInfo.status == AuthorizationStatus.Accepted && authorization_result.ChargePointFee != null)
+							{
+								var price = authorization_result.ChargePointFee.Where(x => x.IsAC == session.IsAC).First();
+								if (price != null)
+								{
+									session.UserPrices[_request.idTag] = price.PerkWhFee.HasValue ? JsonConvert.SerializeObject(new List<ChargingPrice>() { new ChargingPrice() { StartTime = "00:00", EndTime = "23:59", Fee = price.PerkWhFee.Value } }) : price.PerHourFee.Value.ToString();
+									session.UserPrices[_request.idTag] += "|+" + authorization_result.AccountBalance + "+" + "&" + price.ParkingFee + "&|" + price.Currency;
+								}
+							}
+
+						}
+
+						//特例****飛宏客戶旗下的電樁,若遇到Portal沒回應的狀況 ~允許充電
+						if (session.CustomerId.ToString().ToUpper() == "8456AED9-6DD9-4BF3-A94C-9F5DCB9506F7" && _idTagInfo.status == AuthorizationStatus.ConcurrentTx)
+						{
+							_idTagInfo = new IdTagInfo() { expiryDate = DateTime.UtcNow.AddDays(1), status = AuthorizationStatus.Accepted };
+						}
+
+						string accountBalance = "0";
+						if (session.CustomerId.ToString().ToUpper() == "10C7F5BD-C89A-4E2A-8611-B617E0B41A73")
+						{
+							using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
+							{
+								var parameters = new DynamicParameters();
+								parameters.Add("@IdTag", _request.idTag, DbType.String, ParameterDirection.Input, 50);
+								string strSql = "select parentIdTag from [dbo].[LocalListDetail]  where ListId = 27 and IdTag=@IdTag; ";
+								accountBalance = await conn.ExecuteScalarAsync<string>(strSql, parameters);
+							}
+						}
+
+						var _CustomerId = await mainDbService.GetCustomerIdByChargeBoxId(session.ChargeBoxId);
+						t2 = timer.ElapsedMilliseconds;
+
+						var _existedTx = await mainDbService.TryGetDuplicatedTransactionId(session.ChargeBoxId, _CustomerId, _request.connectorId, _request.timestamp);
+						t3 = timer.ElapsedMilliseconds;
+
+						if (_existedTx != null)
+						{
+							_transactionId = _existedTx.Value;
+							logger.LogError("Duplication ***************************************************** " + _existedTx);
+						}
+						else
+						{
+							TransactionRecord _newTransaction;//= new TransactionRecord();
+							_newTransaction = new TransactionRecord()
+							{
+								ChargeBoxId = session.ChargeBoxId,
+								ConnectorId = (byte)_request.connectorId,
+								CreatedOn = DateTime.UtcNow,
+								StartIdTag = _request.idTag,
+								MeterStart = _request.meterStart,
+								CustomerId = _CustomerId,
+								StartTime = _request.timestamp.ToUniversalTime(),
+								ReservationId = _request.reservationId.HasValue ? _request.reservationId.Value : 0,
+							};
+
+							if (session.UserPrices.ContainsKey(_request.idTag))
+							{
+								_newTransaction.Fee = !session.IsBilling ? string.Empty : session.UserPrices[_request.idTag];
+
+							}
+							else
+							{
+								_newTransaction.Fee = !session.IsBilling ? string.Empty : session.BillingMethod == 1 ? JsonConvert.SerializeObject(session.ChargingPrices) : session.ChargingFeebyHour.ToString();
+								_newTransaction.Fee += !session.IsBilling ? string.Empty : "|+" + accountBalance + "+" + "&" + session.ParkingFee + "&|" + session.Currency;
+							}
+
+							//using (var db = await maindbContextFactory.CreateDbContextAsync())
+							//{
+							//    await db.TransactionRecord.AddAsync(_newTransaction);
+
+							//    await db.SaveChangesAsync();
+
+							//    _transactionId = _newTransaction.Id;
+							//}
+
+							_transactionId = await mainDbService.AddNewTransactionRecord(_newTransaction);
+							t4 = timer.ElapsedMilliseconds;
+
+							logger.LogInformation("***************************************************** ");
+							logger.LogInformation(string.Format("{0} :TransactionId {1} ", session.ChargeBoxId, _transactionId));
+							logger.LogInformation("***************************************************** ");
+						}
+
+
+						var confirm = new StartTransactionConfirmation()
+						{
+							idTagInfo = _idTagInfo,
+							transactionId = _transactionId
+						};
+
+
+						result.Message = confirm;
+						result.Success = true;
+
+						timer.Stop();
+						t5 = timer.ElapsedMilliseconds;
+						if (t5 > 1000)
+						{
+							logger.Log(LogLevel.Critical, "{action} {ChargeBoxId} time {t0}/{t1}/{t2}/{t3}/{t4}/{totalTime}", action.ToString(), session.ChargeBoxId, t0, t1, t2, t3, t4, t5);
+						}
+					}
+					break;
+				case Actions.StopTransaction:
+					{
+						StopTransactionRequest _request = request as StopTransactionRequest;
+
+						//遠傳太久以前的停止充電 或 電樁上傳TransactionId=0 直接拒絕 避免電樁持續重送~~~~~~~
+						if (_request.timestamp < new DateTime(2021, 11, 1) || _request.transactionId == 0)
+						{
+							var confirm = new StopTransactionConfirmation()
+							{
+								idTagInfo = new IdTagInfo()
+								{
+									status = AuthorizationStatus.Invalid
+								}
+							};
+
+							result.Message = confirm;
+							result.Success = true;
+							return result;
+						}
+
+						long getDateTimeTime, getServiceTime, getTagInfoTime, dbOpTime = 0, meterValueTime = 0;
+						var stopTrasactionTimer = Stopwatch.StartNew();
+
+						int _ConnectorId = 0;
+						var utcNow = DateTime.UtcNow;
+
+						getDateTimeTime = stopTrasactionTimer.ElapsedMilliseconds;
+						var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
+						getServiceTime = stopTrasactionTimer.ElapsedMilliseconds;
+
+						TransactionRecord transaction;
+						transaction = await mainDbService.GetTransactionForStopTransaction(_request.transactionId, session.ChargeBoxId);
+
+						var _idTagInfo = string.IsNullOrEmpty(_request.idTag) ? null : (
+							_request.idTag == "Backend" ?
+								new IdTagInfo()
+								{
+									expiryDate = utcNow.AddDays(1),
+									status = AuthorizationStatus.Accepted
+								} :
+								(await businessService.Authorize(session.ChargeBoxId, _request.idTag, transaction?.ConnectorId, source: Actions.StopTransaction.ToString())).IdTagInfo
+							);
+						getTagInfoTime = stopTrasactionTimer.ElapsedMilliseconds;
+
+						//特例****飛宏客戶旗下的電樁,若遇到Portal沒回應的狀況 ~允許充電
+						if (session.CustomerId.ToString().ToUpper() == "8456AED9-6DD9-4BF3-A94C-9F5DCB9506F7" && _idTagInfo != null && _idTagInfo.status == AuthorizationStatus.ConcurrentTx)
+						{
+							_idTagInfo = new IdTagInfo() { expiryDate = utcNow.AddDays(1), status = AuthorizationStatus.Accepted };
+						}
+
+						#region PnC 邏輯
+						if (!string.IsNullOrEmpty(_request.idTag))
+						{
+							_request.idTag = _request.idTag.StartsWith("vid:") ? _request.idTag.Replace("vid:", "") : _request.idTag;
+						}
+						#endregion
+
+						try
+						{
+							if (transaction is null)
+							{
+								result.Exception = new Exception("Can't find transactionId " + _request.transactionId);
+							}
+							else
+							{
+								#region 加入Transaction Start/StopSOC
+								if (!session.IsAC && _request.transactionId > 0)
+								{
+									var SearchTime = transaction.StartTime;
+									var txStopTime = _request.timestamp;
+									List<int> SOCCollection = new List<int>();
+
+									while (SearchTime.Date <= txStopTime.Date)
+									{
+
+										var searchResults = await meterValueDbService.GetTransactionSOC(transaction.Id, SearchTime.Date);
+										SOCCollection.AddRange(searchResults);
+										SearchTime = SearchTime.AddDays(1);
+									}
+
+									SOCCollection.Sort();
+									logger.LogDebug(string.Format("SOCCollection:" + String.Join(",", SOCCollection.Select(x => x.ToString()).ToArray())));
+
+									await mainDbService.UpdateTransactionSOC(
+										transaction.Id,
+										startsoc: SOCCollection.Count == 0 ? "" : SOCCollection.First().ToString("0"),
+										stopsoc: SOCCollection.Count == 0 ? "" : SOCCollection.Last().ToString("0")
+										);
+								}
+								#endregion
+
+								_ConnectorId = transaction.ConnectorId;
+
+								var confirm = new StopTransactionConfirmation()
+								{
+									idTagInfo = _idTagInfo
+
+								};
+
+								//Avoid rewrite transaction data
+								if (transaction.StopTime != GlobalConfig.DefaultNullTime)
+								{
+									result.Message = confirm;
+									result.Success = true;
+									return result;
+								}
+
+								await mainDbService.UpdateTransaction(_request.transactionId,
+									meterStop: _request.meterStop,
+									stopTime: _request.timestamp.ToUniversalTime(),
+									stopReasonId: _request.reason.HasValue ? (int)_request.reason.Value : 0,
+									stopReason: _request.reason.HasValue ? _request.reason.Value.ToString() : Reason.Local.ToString(),
+									stopIdTag: _request.idTag,
+									receipt: string.Empty,
+									cost: session.IsBilling ? -1 : 0);
+
+								if (_request.transactionData == null || _request.transactionData.Count == 0)
+								{
+									_request.transactionData = new List<MeterValue>()
+											{
+												new MeterValue() {  timestamp= _request.timestamp, sampledValue=new List<SampledValue>()}
+											};
+								}
+
+								if (_request.transactionData != null && _request.transactionData.Count > 0)
+								{
+									//清除 StopTransaction TransactionData 
+									_request.transactionData[0].sampledValue.Clear();
+
+									_request.transactionData[0].sampledValue.Add(new SampledValue()
+									{
+										context = ReadingContext.Transaction_End,
+										format = ValueFormat.Raw,
+										location = Location.Outlet,
+										phase = _request.transactionData[0].sampledValue.Where(x => x.context.HasValue).Select(x => x.phase).FirstOrDefault(),
+										unit = UnitOfMeasure.Wh,
+										measurand = Measurand.TotalEnergy,
+										value = decimal.Subtract(transaction.MeterStop, transaction.MeterStart).ToString()
+									});
+								}
+
+								if (session.IsBilling)
+								{
+									await messageService.SendDataTransferRequest(
+										session.ChargeBoxId,
+										messageId: "ID_TxEnergy",
+										vendorId: "Phihong Technology",
+										data: JsonConvert.SerializeObject(new { txId = _request.transactionId, ConnectorId = transaction.ConnectorId })
+										);
+								}
+
+								result.Message = confirm;
+								result.Success = true;
+
+							}
+							dbOpTime = watch.ElapsedMilliseconds;
+
+							#region Save MeterValue
+
+							if (_request.transactionData != null &&
+								_request.transactionData.Count > 0)
+							{
+								//List<Task> insertTasks = new();
+								List<InsertMeterValueParam> datas = new();
+								foreach (var item in _request.transactionData)
+								{
+									foreach (var sampleVaule in item.sampledValue)
+									{
+										decimal value = Convert.ToDecimal(sampleVaule.value);
+										datas.Add(new InsertMeterValueParam(
+											chargeBoxId: session.ChargeBoxId
+											, connectorId: (byte)_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));
+										//var task = meterValueDbService.InsertAsync(
+										//    chargeBoxId: session.ChargeBoxId
+										//        , connectorId: (byte)_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);
+										//insertTasks.Add(task);
+									}
+								}
+								//await Task.WhenAll(insertTasks);
+								await meterValueDbService.InsertBundleAsync(datas);
+							}
+							#endregion
+
+							meterValueTime = watch.ElapsedMilliseconds;
+						}
+						catch (Exception ex)
+						{
+							result.Exception = new Exception("TransactionId " + _request.transactionId + " " + ex.Message);
+							result.CallErrorMsg = "Reject Response Message";
+							result.Success = false;
+
+							logger.LogCritical("StopTransaction {msg} trace:{trace}", ex.Message, ex.StackTrace);
+							// return result;
+						}
+
+						stopTrasactionTimer.Stop();
+
+						if (stopTrasactionTimer.ElapsedMilliseconds > 1000)
+						{
+							logger.Log(LogLevel.Critical, "ExecuteCoreRequest {action} {ChargeBoxId} took {time} sec", action.ToString(), session.ChargeBoxId, stopTrasactionTimer.ElapsedMilliseconds / 1000);
+							logger.Log(LogLevel.Critical, "{action} {ChargeBoxId} time {getDateTime}/{serviceTime}/{tagInfoTime}/{dbOpTime}/{meterValueTime}", action.ToString(), session.ChargeBoxId, getDateTimeTime, getServiceTime, getTagInfoTime, dbOpTime, meterValueTime);
+						}
+
+					}
+					break;
+				case Actions.Authorize:
+					{
+						AuthorizeRequest _request = request as AuthorizeRequest;
+
+						var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
+						var confirm = new AuthorizeConfirmation()
+						{
+							idTagInfo = new IdTagInfo() { expiryDate = DateTime.UtcNow.AddDays(1), status = AuthorizationStatus.Accepted }
+						};
+						if (_request.idTag != "Backend")
+						{
+							var authorization_result = await businessService.Authorize(session.ChargeBoxId, _request.idTag, source: Actions.Authorize.ToString());
+							confirm.idTagInfo = authorization_result.IdTagInfo;
+
+							if (confirm.idTagInfo.status == AuthorizationStatus.Accepted && authorization_result.ChargePointFee != null)
+							{
+								var price = authorization_result.ChargePointFee.Where(x => x.IsAC == session.IsAC).First();
+								if (price != null)
+								{
+									session.UserPrices[_request.idTag] = price.PerkWhFee.HasValue ? JsonConvert.SerializeObject(new List<ChargingPrice>() { new ChargingPrice() { StartTime = "00:00", EndTime = "23:59", Fee = price.PerkWhFee.Value } }) : price.PerHourFee.Value.ToString();
+									session.UserPrices[_request.idTag] += "|+" + authorization_result.AccountBalance + "+" + "&" + price.ParkingFee + "&|" + price.Currency;
+
+									session.UserDisplayPrices[_request.idTag] = price.DisplayMessage;
+								}
+							}
+
+						}
+						//特例****飛宏客戶旗下的電樁,若遇到Portal沒回應的狀況 ~允許充電
+						if (session.CustomerId.ToString().ToUpper() == "8456AED9-6DD9-4BF3-A94C-9F5DCB9506F7" && confirm.idTagInfo.status == AuthorizationStatus.ConcurrentTx)
+						{
+							confirm.idTagInfo = new IdTagInfo() { expiryDate = DateTime.UtcNow.AddDays(1), status = AuthorizationStatus.Accepted };
+						}
+
+						result.Message = confirm;
+						result.Success = true;
+					}
+					break;
+				default:
+					{
+						logger.LogWarning(string.Format("Not Implement {0} Logic(ExecuteCoreRequest)", request.GetType().ToString().Replace("OCPPPackage.Messages.Core.", "")));
+					}
+					break;
+			}
+		}
+		catch (Exception ex)
+		{
+			logger.LogCritical("chargeBoxId:{0} {1}", session?.ChargeBoxId, action);
+			logger.LogCritical("Data {0}", request?.ToString());
+			logger.LogCritical("Error {0}", ex.ToString());
+			result.Exception = ex;
+		}
+
+
+		//if (action == Actions.Heartbeat)
+		//{
+		watch.Stop();
+		if (watch.ElapsedMilliseconds / 1000 > 3)
+		{
+			logger.LogError("Processing " + action.ToString() + " costs " + watch.ElapsedMilliseconds / 1000 + " seconds"); ;
+		}
+		//}
+
+		if (watch.ElapsedMilliseconds > 5_000)
+		{
+			//ThreadPool.GetAvailableThreads(out int workerThreads,out int completionThreads);
+			//logger.LogInformation($"ThreadPool workerThreads:{workerThreads} completionThreads:{completionThreads}");
+
+			//await blockingTreePrintService.PrintDbBlockingTree();
+			//await googleGetTimePrintService.Print();
+		}
+
+		return result;
+	}
+
+    async internal Task<MessageResult> ExecuteCoreConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
+	{
+		MessageResult result = new MessageResult() { Success = true };
+
+		try
+		{
+			switch (action)
+			{
+				case Actions.DataTransfer:
+					{
+						DataTransferConfirmation _confirm = confirm as DataTransferConfirmation;
+						DataTransferRequest _request = _confirm.GetRequest() as DataTransferRequest;
+						using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+							if (operation != null)
+							{
+								operation.FinishedOn = DateTime.UtcNow;
+								operation.Status = 1;//電樁有回覆
+								operation.EvseStatus = (int)_confirm.status;
+								operation.EvseValue = string.IsNullOrEmpty(_confirm.data) ? "" : _confirm.data;
+								await db.SaveChangesAsync();
+							}
+
+							if (_request.messageId == "ID_FirmwareVersion")
+							{
+								var machine = new Machine() { Id = session.MachineId };
+								if (machine != null)
+								{
+									db.ChangeTracker.AutoDetectChangesEnabled = false;
+									//db.Configuration.ValidateOnSaveEnabled = false;
+									db.Machine.Attach(machine);
+									machine.BoardVersions = _confirm.data;
+									db.Entry(machine).Property(x => x.BoardVersions).IsModified = true;
+									await db.SaveChangesAsync();
+								}
+							}
+
+							if (_request.messageId == "ID_TxEnergy") //計費
+							{
+								if (_confirm.status == DataTransferStatus.Accepted)
+								{
+									decimal couponPoint = 0m;
+									string farewellMessage = string.Empty;
+									string receipt = string.Empty;
+									List<ChargingBill> bill = new List<ChargingBill>();
+									List<ChargingPrice> chargingPrices = new List<ChargingPrice>();
+									var txEnergy = JsonConvert.DeserializeObject<TransactionEnergy>(_confirm.data);
+									var feedto = await db.TransactionRecord.Where(x => x.Id == txEnergy.TxId).Select(x => new { Id = x.Id, ConnectorId = x.ConnectorId, Fee = x.Fee, StopTime = x.StopTime, StartTime = x.StartTime, NotifyPnC = x.NotifyPnC }).FirstOrDefaultAsync();
+									decimal chargedEnergy = 0m;
+									if (feedto == null || string.IsNullOrEmpty(feedto.Fee)) return result;
+
+									if (!feedto.NotifyPnC && !string.IsNullOrEmpty(txEnergy.EVCCID))
+									{
+										//send PnC notification....
+										var customerInfo = db.Customer.Where(x => x.Id == session.CustomerId).Select(x => new { x.InstantStopTxReport, x.ApiUrl, x.ApiKey }).FirstOrDefault();
+
+										var request = new
+										{
+											SessionId = feedto.Id,
+											ChargeBoxId = session.ChargeBoxId,
+											EVCCID = txEnergy.EVCCID
+
+										};
+
+										logger.LogDebug(customerInfo.ApiUrl + "session_pncinfo=>" + JsonConvert.SerializeObject(request));
+
+										var response = await httpClient.Post(customerInfo.ApiUrl + "session_pncinfo", new Dictionary<string, string>()
+												{
+													{ "PartnerId",session.CustomerId.ToString()}
+
+												}, request, customerInfo.ApiKey);
+
+										if (response.Status == System.Net.HttpStatusCode.OK)
+										{
+											var _httpResult = JsonConvert.DeserializeObject<CPOOuterResponse>(response.Response);
+											logger.LogDebug("session_pncinfo Response" + JsonConvert.SerializeObject(response));
+											var pnc_info = db.TransactionRecord.Where(x => x.Id == txEnergy.TxId).FirstOrDefault();
+											if (response.Status == System.Net.HttpStatusCode.OK)
+											{
+												pnc_info.NotifyPnC = true;
+											}
+
+											pnc_info.Evccid = txEnergy.EVCCID;
+											db.ChangeTracker.AutoDetectChangesEnabled = false;
+											db.TransactionRecord.Attach(pnc_info);
+											db.Entry(pnc_info).Property(x => x.Evccid).IsModified = true;
+											db.Entry(pnc_info).Property(x => x.NotifyPnC).IsModified = true;
+
+											await db.SaveChangesAsync();
+										}
+									}
+
+									string currency = feedto.Fee.Substring(feedto.Fee.Length - 3);
+									decimal chargingCost = 0;
+									if (feedto.Fee.Length > 58)
+									{
+										chargingPrices = JsonConvert.DeserializeObject<List<ChargingPrice>>(feedto.Fee.Split('|')[0]);
+										foreach (var item in txEnergy.PeriodEnergy)
+										{
+											DateTime dt = new DateTime(2021, 01, 01, int.Parse(item.Key), 0, 0, DateTimeKind.Utc);
+											string startTime = dt.ToString("hh:mm tt", new CultureInfo("en-us"));
+											decimal perfee = 0;
+
+											// 小數點第5位4捨五入
+											var periodEnergy = PeriodEnergyRounding(item.Value);
+
+											chargedEnergy += periodEnergy;
+											if (chargingPrices.Count == 1)
+											{
+												perfee = Decimal.Multiply(periodEnergy, chargingPrices[0].Fee);
+												if (bill.Count == 0)
+												{
+													bill.Add(new ChargingBill()
+													{
+														StartTime = "12:00 AM",
+														EndTime = "11:59 PM",
+														Fee = chargingPrices[0].Fee
+													});
+												}
+
+												bill[0].PeriodEnergy += periodEnergy;
+
+											}
+											else
+											{
+												var price = chargingPrices.Where(x => x.StartTime == startTime).FirstOrDefault();
+												perfee = Decimal.Multiply(periodEnergy, price.Fee);
+
+												bill.Add(new ChargingBill()
+												{
+													StartTime = price.StartTime,
+													EndTime = price.EndTime,
+													PeriodEnergy = periodEnergy,
+													Fee = price.Fee,
+
+												});
+											}
+											if (bill.Count > 0)
+											{
+												bill[bill.Count - 1].Total += DollarRounding(perfee, session.Currency);
+												chargingCost += bill[bill.Count - 1].Total;
+
+												if (bill.Count == 1)
+												{
+
+													bill[bill.Count - 1].Total = DollarRounding(Decimal.Multiply(bill[0].PeriodEnergy, bill[0].Fee), session.Currency);
+													chargingCost = bill[bill.Count - 1].Total;
+												}
+											}
+										}
+									}
+									else
+									{
+										//以小時計費
+										foreach (var item in txEnergy.PeriodEnergy)
+										{
+											// 小數點第5位4捨五入
+											var periodEnergy = PeriodEnergyRounding(item.Value);
+											chargedEnergy += periodEnergy;
+										}
+
+										var fee = decimal.Parse(feedto.Fee.Split('|')[0]);
+										var charging_stoptime = feedto.StopTime == GlobalConfig.DefaultNullTime ? DateTime.Parse(DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm")) : DateTime.Parse(feedto.StopTime.ToString("yyyy/MM/dd HH:mm"));
+										var charging_starttime = DateTime.Parse(feedto.StartTime.ToString("yyyy/MM/dd HH:mm"));
+										chargingCost = Decimal.Multiply((decimal)charging_stoptime.Subtract(charging_starttime).TotalHours, fee);
+										chargingCost = DollarRounding(chargingCost, session.Currency);
+									}
+
+									// 計算停車費
+									var parkingFee = decimal.Parse(feedto.Fee.Split('&')[1]);
+									var stoptime = feedto.StopTime == GlobalConfig.DefaultNullTime ? DateTime.Parse(DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm")) : DateTime.Parse(feedto.StopTime.ToString("yyyy/MM/dd HH:mm"));
+									var starttime = DateTime.Parse(feedto.StartTime.ToString("yyyy/MM/dd HH:mm"));
+									var totalHours = stoptime.Subtract(starttime).TotalHours;
+									var parkingCost = Decimal.Multiply((decimal)totalHours, parkingFee);
+									parkingCost = DollarRounding(parkingCost, session.Currency);
+
+									if (feedto.StopTime != GlobalConfig.DefaultNullTime)
+									{
+										//var customerInfo = await db.Customer
+										//    .Where(x => x.Id == session.CustomerId).Select(x => new { x.InstantStopTxReport, x.ApiUrl, x.ApiKey })
+										//    .FirstOrDefaultAsync();
+										var customerInfo = await mainDbService.GetCustomer(session.CustomerId);
+
+										decimal accountBalance = 0;
+										decimal.TryParse(feedto.Fee.Split('+')[1], out accountBalance);
+
+										var tx = await db.TransactionRecord.Where(x => x.Id == txEnergy.TxId).FirstOrDefaultAsync();
+										if (tx == null)
+										{
+											logger.LogWarning("Tx is empty");
+											return result;
+										}
+
+										if (tx.BillingDone) return result;
+
+
+										var startTime = new DateTime(tx.StartTime.Year, tx.StartTime.Month, tx.StartTime.Day, tx.StartTime.Hour, 0, 0);
+										List<ChargingBill> confirmbill = new List<ChargingBill>();
+										receipt = string.Format("({0} )Energy:", chargedEnergy);
+
+										while (startTime < tx.StopTime)
+										{
+											if (bill.Count == 1)
+											{
+												confirmbill = bill;
+												receipt += string.Format("| {0} - {1}:| {2} kWh @ ${3}/kWh= ${4}", tx.StartTime.ToString("hh:mm tt", new CultureInfo("en-us")), tx.StopTime.ToString("hh:mm tt", new CultureInfo("en-us")),
+													confirmbill[0].PeriodEnergy.ToString("0.0000"), bill[0].Fee, bill[0].Total);
+
+												break;
+											}
+											if (bill.Count == 0)
+											{
+												receipt += string.Format("| {0} - {1} @ ${2}/hr= ${3}", feedto.StartTime.ToString("hh:mm tt", new CultureInfo("en-us")),
+												   feedto.StopTime.ToString("hh:mm tt", new CultureInfo("en-us")), feedto.Fee.Split('|')[0], chargingCost);
+
+												break;
+											}
+											if (bill.Count > 1)
+											{
+												var time = startTime.ToString("hh:mm tt", new CultureInfo("en-us"));
+												var tt = bill.Where(x => x.StartTime == time).FirstOrDefault();
+												confirmbill.Add(tt);
+												if (confirmbill.Count == 1)
+												{
+													confirmbill[0].StartTime = tx.StartTime.ToString("hh:mm tt", new CultureInfo("en-us"));
+												}
+
+
+												var stopTimeText = tx.StopTime.ToString("hh:mm tt", new CultureInfo("en-us"));
+												if (confirmbill[confirmbill.Count - 1].StartTime.Contains(stopTimeText.Split(' ')[1]))
+												{
+													var subHourText = (int.Parse(stopTimeText.Split(':')[0])).ToString();
+													subHourText = subHourText.Length == 1 ? "0" + subHourText : subHourText;
+													if (confirmbill[confirmbill.Count - 1].StartTime.Contains(subHourText))
+													{
+														confirmbill[confirmbill.Count - 1].EndTime = stopTimeText;
+													}
+
+												}
+												receipt += string.Format("| {0} - {1}:| {2} kWh @ ${3}/kWh= ${4}", confirmbill[confirmbill.Count - 1].StartTime, confirmbill[confirmbill.Count - 1].EndTime,
+													confirmbill[confirmbill.Count - 1].PeriodEnergy.ToString("0.0000"), confirmbill[confirmbill.Count - 1].Fee, confirmbill[confirmbill.Count - 1].Total);
+
+												if (confirmbill.Count == 24) break;
+
+											}
+											startTime = startTime.AddHours(1);
+
+										}
+
+										chargingCost = confirmbill.Count > 0 ? confirmbill.Sum(x => x.Total) : chargingCost;
+										receipt += string.Format("|Total Energy Fee : ${0}", chargingCost);
+
+										receipt += string.Format("|Parking Fee: | {0} - {1}: | {2} @ ${3}/hr= ${4}", feedto.StartTime.ToString("hh:mm tt", new CultureInfo("en-us")),
+										feedto.StopTime.ToString("hh:mm tt", new CultureInfo("en-us")), (totalHours / 1 >= 1) ? string.Format("{0} hours {1} minutes", (int)totalHours / 1, ((totalHours % 1) * 60).ToString("0.0")) : string.Format("{0} minutes", ((totalHours % 1) * 60).ToString("0.0")), parkingFee, parkingCost);
+										receipt += string.Format("|Stop Reason: {0}", tx.StopReason);
+
+										tx.Cost = chargingCost + parkingCost;
+
+
+										if (customerInfo != null && customerInfo.InstantStopTxReport)
+										{
+
+											var request = new
+											{
+												ChargeBoxId = tx.ChargeBoxId,
+												ConnectorId = tx.ConnectorId,
+												SessionId = tx.Id,
+												MeterStart = tx.MeterStart,
+												MeterStop = tx.MeterStop,
+												IdTag = tx.StartIdTag,
+												StartTime = tx.StartTime.ToString(GlobalConfig.UTC_DATETIMEFORMAT),
+												StopTime = tx.StopTime.ToString(GlobalConfig.UTC_DATETIMEFORMAT),
+												StopReason = tx.StopReasonId < 1 ? "Unknown" : (tx.StopReasonId > 12 ? "Unknown" : ((Reason)tx.StopReasonId).ToString()),
+												Receipt = tx.Receipt,
+												TotalCost = tx.Cost,
+												Fee = tx.Fee
+
+											};
+
+											logger.LogDebug("completed_session " + JsonConvert.SerializeObject(request));
+											var response = await httpClient.Post(customerInfo.ApiUrl + "completed_session", new Dictionary<string, string>()
+											{
+												{ "PartnerId",session.CustomerId.ToString()}
+
+											}, request, customerInfo.ApiKey);
+
+
+											logger.LogDebug("completed_session Response" + response.Response);
+
+											if (response.Success && !string.IsNullOrEmpty(response.Response))
+											{
+												var _httpResult = JsonConvert.DeserializeObject<CPOOuterResponse>(response.Response);
+												logger.LogDebug("completed_session Response" + response.Response);
+												JObject jo = JObject.Parse(_httpResult.Data);
+												if (jo.ContainsKey("CouponPoint"))
+												{
+													couponPoint = jo["CouponPoint"].Value<Decimal>();
+
+												}
+
+												if (jo.ContainsKey("FarewellMessage"))
+												{
+													farewellMessage = jo["FarewellMessage"].Value<string>();
+												}
+											}
+										}
+
+										tx.Receipt = receipt;
+										tx.BillingDone = true;
+										db.ChangeTracker.AutoDetectChangesEnabled = false;
+										//db.Configuration.ValidateOnSaveEnabled = false;
+										db.TransactionRecord.Attach(tx);
+										db.Entry(tx).Property(x => x.Cost).IsModified = true;
+										db.Entry(tx).Property(x => x.Receipt).IsModified = true;
+										db.Entry(tx).Property(x => x.BillingDone).IsModified = true;
+
+										await db.SaveChangesAsync();
+
+										await messageService.SendDataTransferRequest(
+											session.ChargeBoxId,
+											messageId: "FinalCost",
+											vendorId: "Phihong Technology",
+											data: JsonConvert.SerializeObject(new
+											{
+												txId = txEnergy.TxId,
+												description = JsonConvert.SerializeObject(new
+												{
+													chargedEnergy = chargedEnergy,
+													chargingFee = chargingCost,
+													parkTime = (int)stoptime.Subtract(starttime).TotalSeconds,
+													parkingFee = parkingCost,
+													currency = currency,
+													couponPoint = couponPoint,
+													accountBalance = accountBalance - tx.Cost,
+													farewellMessage = farewellMessage
+												})
+											})
+											);
+
+										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 = await webDbConnectionFactory.CreateAsync())
+										{
+											var parameters = new DynamicParameters();
+											parameters.Add("@IdTag", tx.StartIdTag, DbType.String, ParameterDirection.Input, 50);
+											parameters.Add("@parentIdTag", accountBalance - tx.Cost, DbType.String, ParameterDirection.Input, 50);
+											string strSql = "update [dbo].[LocalListDetail] set parentIdTag =@parentIdTag  where ListId = 27 and IdTag=@IdTag; ";
+											await conn.ExecuteAsync(strSql, parameters);
+
+										}
+
+										#region 提供給PHA 過CDFA認證 使用
+										if (tx.CustomerId == Guid.Parse("10C7F5BD-C89A-4E2A-8611-B617E0B41A73"))
+										{
+											var mail_response = httpClient.PostFormDataAsync("https://charge.zerovatech.com/CDFA/" + tx.Id, new Dictionary<string, string>()
+											{
+												{ "email","2"},
+												{ "to","wonderj@phihongusa.com;jessica_tseng@phihong.com.tw"}
+                                                //{ "to","jessica_tseng@phihong.com.tw"}
+                                           }, null);
+
+											logger.LogTrace(JsonConvert.SerializeObject(mail_response));
+
+										}
+										#endregion
+
+									}
+									else
+									{
+										await messageService.SendDataTransferRequest(
+											session.ChargeBoxId,
+											messageId: "RunningCost",
+											vendorId: "Phihong Technology",
+											data: JsonConvert.SerializeObject(new
+											{
+												txId = txEnergy.TxId,
+												description = JsonConvert.SerializeObject(new
+												{
+													chargedEnergy = chargedEnergy,
+													chargingFee = chargingCost,
+													parkTime = (int)stoptime.Subtract(starttime).TotalSeconds,
+													parkingFee = parkingCost,
+													currency = currency
+												})
+
+											})
+											);
+
+										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
+											);
+									}
+								}
+
+							}
+
+							#region 台泥
+							if (_request.messageId == "ID_GetTxUserInfo")
+							{
+								var txUserInfo = JsonConvert.DeserializeObject<ID_GetTxUserInfo>(_confirm.data);
+								if (session.CustomerId == new Guid("009E603C-79CD-4620-A2B8-D9349C0E8AD8"))
+								{
+									var request = new
+									{
+										ChargeBoxId = session.ChargeBoxId,
+										ConnectorId = txUserInfo.ConnectorId,
+										SessionId = txUserInfo.TxId,
+										SerialNo = txUserInfo.SerialNo,
+										StartTime = txUserInfo.StartTime.ToString(GlobalConfig.UTC_DATETIMEFORMAT),
+										VEMData = txUserInfo.VEMData
+									};
+
+									var response = httpClient.Post(GlobalConfig.TCC_API_URL + "start_session", new Dictionary<string, string>()
+									{
+										{ "PartnerId",session.CustomerId.ToString()}
+
+									}, request, GlobalConfig.TCC_SALTKEY);
+
+									logger.LogDebug(JsonConvert.SerializeObject(response));
+								}
+
+							}
+
+							#endregion
+						}
+					}
+					break;
+				case Actions.ChangeAvailability:
+					{
+						ChangeAvailabilityConfirmation _confirm = confirm as ChangeAvailabilityConfirmation;
+						ChangeAvailabilityRequest _request = _confirm.GetRequest() as ChangeAvailabilityRequest;
+
+						using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+							if (operation != null)
+							{
+								operation.FinishedOn = DateTime.UtcNow;
+								operation.Status = 1;//電樁有回覆
+								operation.EvseStatus = (int)_confirm.status;
+								operation.EvseValue = _confirm.status.ToString();
+								await db.SaveChangesAsync();
+							}
+
+						}
+					}
+					break;
+				case Actions.ClearCache:
+					{
+						ClearCacheConfirmation _confirm = confirm as ClearCacheConfirmation;
+						ClearCacheRequest _request = _confirm.GetRequest() as ClearCacheRequest;
+
+						using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+							if (operation != null)
+							{
+								operation.FinishedOn = DateTime.UtcNow;
+								operation.Status = 1;//電樁有回覆
+								operation.EvseStatus = (int)_confirm.status;
+								operation.EvseValue = _confirm.status.ToString();
+								await db.SaveChangesAsync();
+							}
+
+						}
+					}
+					break;
+				case Actions.RemoteStartTransaction:
+					{
+						RemoteStartTransactionConfirmation _confirm = confirm as RemoteStartTransactionConfirmation;
+						RemoteStartTransactionRequest _request = _confirm.GetRequest() as RemoteStartTransactionRequest;
+
+						using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+							if (operation != null)
+							{
+								operation.FinishedOn = DateTime.UtcNow;
+								operation.Status = 1;//電樁有回覆
+								operation.EvseStatus = (int)_confirm.status;
+								operation.EvseValue = _confirm.status.ToString();
+								await db.SaveChangesAsync();
+							}
+
+						}
+					}
+					break;
+				case Actions.RemoteStopTransaction:
+					{
+						RemoteStopTransactionConfirmation _confirm = confirm as RemoteStopTransactionConfirmation;
+						RemoteStopTransactionRequest _request = _confirm.GetRequest() as RemoteStopTransactionRequest;
+
+						using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+							if (operation != null)
+							{
+								operation.FinishedOn = DateTime.UtcNow;
+								operation.Status = 1;//電樁有回覆
+								operation.EvseStatus = (int)_confirm.status;
+								operation.EvseValue = _confirm.status.ToString();
+								await db.SaveChangesAsync();
+							}
+
+						}
+					}
+					break;
+				case Actions.Reset:
+					{
+						ResetConfirmation _confirm = confirm as ResetConfirmation;
+						ResetRequest _request = _confirm.GetRequest() as ResetRequest;
+
+						using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+							if (operation != null)
+							{
+								operation.FinishedOn = DateTime.UtcNow;
+								operation.Status = 1;//電樁有回覆
+								operation.EvseStatus = (int)_confirm.status;
+								operation.EvseValue = _confirm.status.ToString();
+								await db.SaveChangesAsync();
+							}
+
+						}
+					}
+					break;
+				case Actions.ChangeConfiguration:
+					{
+						ChangeConfigurationConfirmation _confirm = confirm as ChangeConfigurationConfirmation;
+						ChangeConfigurationRequest _request = _confirm.GetRequest() as ChangeConfigurationRequest;
+
+						using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+					 x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+
+							if (operation != null)
+							{
+								operation.FinishedOn = DateTime.UtcNow;
+								operation.Status = 1;//電樁有回覆
+								operation.EvseStatus = (int)_confirm.status;
+								operation.EvseValue = _confirm.status.ToString();
+
+							}
+
+							if (_confirm.status == Packet.Messages.SubTypes.ConfigurationStatus.Accepted || _confirm.status == Packet.Messages.SubTypes.ConfigurationStatus.RebootRequired)
+							{
+								var configure = await db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId).ToListAsync();
+
+								var foundConfig = configure.Find(x => x.ConfigureName == _request.key);
+								if (foundConfig != null)
+								{
+									foundConfig.ReadOnly = false;
+									foundConfig.ConfigureSetting = _request.value;
+								}
+								else
+								{
+									await db.MachineConfigurations.AddAsync(new MachineConfigurations()
+									{
+										ChargeBoxId = session.ChargeBoxId,
+										ConfigureName = _request.key,
+										ReadOnly = false,
+										ConfigureSetting = _request.value
+									});
+								}
+							}
+							await db.SaveChangesAsync();
+						}
+
+					}
+					break;
+				case Actions.GetConfiguration:
+					{
+
+						try
+						{
+							GetConfigurationConfirmation _confirm = confirm as GetConfigurationConfirmation;
+							//  GetConfigurationRequest _request = _confirm.GetRequest() as GetConfigurationRequest;
+
+							List<Task> updateTasks = new List<Task>();
+                            List<MachineConfigurations> configure = await mainDbService.GetMachineConfiguration(session.ChargeBoxId);
+
+                            if (_confirm.configurationKey != null)
+                            {
+                                foreach (var item in _confirm.configurationKey)
+                                {
+                                    string oldValue = string.Empty;
+                                    if (item.key == null)
+                                    {
+                                        logger.LogTrace("*********************");
+                                    }
+
+                                    var foundConfig = configure.Find(x => x.ConfigureName == item.key);
+                                    var updateValue = string.IsNullOrEmpty(item.value) ? string.Empty : item.value;
+
+                                    if (foundConfig == null)
+                                    {
+                                        await mainDbService.AddMachineConfiguration(session.ChargeBoxId, item.key, updateValue, item.IsReadOnly);
+                                    }
+                                    else if (item.value != foundConfig.ConfigureSetting)
+                                    {
+                                        if (foundConfig.ConfigureName == null)
+                                        {
+                                            logger.LogTrace("*********************");
+                                        }
+
+                                        if (foundConfig.ConfigureName == "SecurityProfile")
+                                        {
+                                            oldValue = foundConfig.ConfigureSetting;
+                                        }
+
+                                        await mainDbService.UpdateMachineConfiguration(session.ChargeBoxId, item.key, updateValue, item.IsReadOnly);
+                                    }
+                                }
+                            }
+                            if (_confirm.unknownKey != null)
+                            {
+
+                                foreach (var item in _confirm.unknownKey)
+                                {
+                                    var foundConfig = configure.Find(x => x.ConfigureName == item);
+                                    if (foundConfig != null)
+                                    {
+                                        await mainDbService.UpdateMachineConfiguration(session.ChargeBoxId, item, string.Empty, isReadOnly: true, isExists: false);
+                                    }
+                                    else
+                                    {
+                                        await mainDbService.AddMachineConfiguration(session.ChargeBoxId, item, string.Empty, isReadOnly: true, isExist: false);
+                                    }
+                                }
+                            }
+
+                            using (var db = await maindbContextFactory.CreateDbContextAsync())
+                            {
+                                var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+							   x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+
+								if (operation != null)
+								{
+									operation.FinishedOn = DateTime.UtcNow;
+									operation.Status = 1;//電樁有回覆
+									operation.EvseStatus = 1;
+									operation.EvseValue = JsonConvert.SerializeObject(_confirm.configurationKey, Formatting.None);
+
+								}
+
+								await db.SaveChangesAsync();
+
+							}
+						}
+						catch (Exception ex)
+						{
+							logger.LogError(ex.ToString());
+						}
+
+					}
+					break;
+				case Actions.UnlockConnector:
+					{
+						UnlockConnectorConfirmation _confirm = confirm as UnlockConnectorConfirmation;
+						UnlockConnectorRequest _request = _confirm.GetRequest() as UnlockConnectorRequest;
+
+						using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+							var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+							x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+							if (operation != null)
+							{
+								operation.FinishedOn = DateTime.UtcNow;
+								operation.Status = 1;//電樁有回覆
+								operation.EvseStatus = (int)_confirm.status;
+								operation.EvseValue = _confirm.status.ToString();
+								await db.SaveChangesAsync();
+							}
+
+						}
+					}
+					break;
+				default:
+					{
+						logger.LogWarning(string.Format("Not Implement {0} Logic(ExecuteCoreConfirm)", confirm.GetType().ToString().Replace("OCPPPackage.Messages.Core.", "")));
+					}
+					break;
+			}
+		}
+		catch (Exception ex)
+		{
+			logger.LogDebug("123 " + action + " " + ex.ToString());
+		}
+
+
+
+		return result;
+	}
+
+
+	internal async Task<MessageResult> ReceivedCoreError(Actions action, string errorMsg, WsClientData session, string requestId)
+	{
+		MessageResult result = new MessageResult() { Success = true };
+
+		switch (action)
+		{
+			case Actions.ChangeAvailability:
+			case Actions.ChangeConfiguration:
+			case Actions.ClearCache:
+			case Actions.RemoteStartTransaction:
+			case Actions.RemoteStopTransaction:
+			case Actions.Reset:
+			case Actions.GetConfiguration:
+			case Actions.UnlockConnector:
+			case Actions.DataTransfer:
+				{
+					if (action == Actions.DataTransfer)
+					{
+						logger.LogDebug(string.Format("DataTransfer Error {0}: {1}", session.ChargeBoxId, requestId));
+					}
+
+					using (var db = await maindbContextFactory.CreateDbContextAsync())
+					{
+						var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+						x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+						if (operation != null)
+						{
+							operation.FinishedOn = DateTime.UtcNow;
+							operation.Status = 1;//電樁有回覆
+							operation.EvseStatus = (int)255;//錯誤
+							operation.EvseValue = errorMsg;
+							await db.SaveChangesAsync();
+						}
+
+					}
+
+				}
+				break;
+
+			default:
+				{
+					logger.LogWarning(string.Format("Not Implement {0} Logic(ReceivedCoreError)", action));
+				}
+				break;
+		}
+		return result;
+
+	}
+
+
+	/// <summary>
+	/// 依據幣值處理4捨5入
+	/// </summary>
+	/// <param name="money"></param>
+	/// <param name="currency"></param>
+	/// <returns></returns>
+	private decimal DollarRounding(decimal money, string currency)
+	{
+
+
+		if (currency == "USD" || currency == "EUR")
+		{
+
+			//0.4867
+			if ((double)((int)(money * 100) + 0.5) <= (double)(money * 100))
+			{
+
+				//money = Decimal.Add(money, (decimal)0.01);//0.4967
+
+			}
+			money = Math.Round(money, 2, MidpointRounding.AwayFromZero);
+			money = Decimal.Parse(money.ToString("0.00"));
+
+
+		}
+		else
+		{
+			if ((double)((int)(money) + 0.5) <= (double)money)
+			{
+				//  money = (int) money + 1;
+			}
+			money = Math.Round(money, 0, MidpointRounding.AwayFromZero);
+			money = Decimal.Parse(money.ToString("0"));
+		}
+
+		return money;
+	}
+
+	/// <summary>
+	/// PeriodEnergy處理4捨5入
+	/// </summary>
+	/// <param name="money"></param>
+	/// <param name="currency"></param>
+	/// <returns></returns>
+	private decimal PeriodEnergyRounding(decimal energy)
+	{
+		energy = Math.Round(energy, 4, MidpointRounding.AwayFromZero);
+		energy = Decimal.Parse(energy.ToString("0.0000"));
+		return energy;
+	}
+
+    private static bool CheckNeedAddMachineError(StatusNotificationRequest _request, ConnectorStatus _oldStatus)
+    {
+		if (_request.status != ChargePointStatus.Faulted)
+		{
+			return false;
+		}
+
+        bool isNeedAddMachineError = false;
+        if (_oldStatus == null)
+            isNeedAddMachineError = true;
+
+        if (_oldStatus != null)
+        {
+            if (_oldStatus.Status != (int)ChargePointStatus.Faulted)
+            {
+                isNeedAddMachineError = true;
+            }
+            else if (_oldStatus.ChargePointErrorCodeId != (int)_request.errorCode && _oldStatus.VendorErrorCode != _request.vendorErrorCode)
+            {
+                isNeedAddMachineError = true;
+            }
+        }
+
+        return isNeedAddMachineError;
+    }
+}

+ 233 - 233
EVCB_OCPP.WSServer/Message/FirmwareManagementProfileHandler.cs

@@ -1,233 +1,233 @@
-using EVCB_OCPP.Domain;
-
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.FirmwareManagement;
-using Newtonsoft.Json;
-
-using System;
-using System.Linq;
-using Microsoft.Extensions.Logging;
-using System.Threading.Tasks;
-using Microsoft.EntityFrameworkCore;
-using EVCB_OCPP.WSServer.Service.WsService;
-using EVCB_OCPP.Domain.Models.MainDb;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    internal partial class ProfileHandler
-    {
-        internal async Task<MessageResult> ExecuteFirmwareManagementRequest(Actions action, WsClientData session, IRequest request)
-        {
-            MessageResult result = new MessageResult() { Success = false };
-            try
-            {
-                switch (action)
-                {
-
-                    case Actions.FirmwareStatusNotification:
-                        {
-                            FirmwareStatusNotificationRequest _request = request as FirmwareStatusNotificationRequest;
-
-                            if (_request.status == Packet.Messages.SubTypes.FirmwareStatus.Idle)
-                            {
-                                string requestId = Guid.NewGuid().ToString();
-                                using (var db = await maindbContextFactory.CreateDbContextAsync())
-                                {
-                                    var machine = await db.Machine.Where(x => x.FwAssignedVersion.HasValue == true && x.FwAssignedVersion.HasValue
-                                       && x.FwAssignedVersion != x.FwVersionReport && x.ChargeBoxId == session.ChargeBoxId)
-                                        .Select(x => new { x.Id, x.FwAssignedVersion }).AsNoTracking().FirstOrDefaultAsync();
-
-                                    if (machine != null)
-                                    {
-                                        var mv = db.MachineVersionFile.Include(c => c.UploadFile)
-                                         .Where(c => c.Id == machine.FwAssignedVersion.Value).First();
-
-                                        string downloadUrl = mv.UploadFile.FileUrl;
-
-                                        var _updateFWrequest = new UpdateFirmwareRequest()
-                                        {
-                                            location = new Uri(downloadUrl),
-                                            retries = 3,
-                                            retrieveDate = DateTime.UtcNow,
-                                            retryInterval = 10
-                                        };
-
-                                        await db.MachineOperateRecord.AddAsync(new MachineOperateRecord()
-                                        {
-                                            CreatedOn = DateTime.UtcNow,
-                                            ChargeBoxId = session.ChargeBoxId,
-                                            SerialNo = requestId,
-                                            RequestContent = JsonConvert.SerializeObject(_updateFWrequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
-											EvseStatus = 0,
-											EvseValue = "Fw Version:" + machine.FwAssignedVersion,
-                                            Status = 0,
-                                            RequestType = 0,
-                                            Action = _updateFWrequest.Action.ToString()
-
-                                        });
-
-                                        await db.SaveChangesAsync();
-
-                                        await mainDbService.AddServerMessage(
-                                            ChargeBoxId: session.ChargeBoxId,
-                                            OutAction: _updateFWrequest.Action.ToString(),
-                                            OutRequest: _updateFWrequest,
-                                            SerialNo: requestId);
-
-                                        var clearMachine = await db.Machine.Where(x => x.Id == machine.Id).FirstOrDefaultAsync();
-                                        clearMachine.FwAssignedVersion = null;
-                                        await db.SaveChangesAsync();
-                                    }
-
-                                }
-                            }
-                            else
-                            {
-                                using (var db = await maindbContextFactory.CreateDbContextAsync())
-                                {
-                                    var item = await db.MachineOperateRecord.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.Action == "UpdateFirmware" && x.RequestType == 0)
-                                        .OrderByDescending(x => x.CreatedOn).FirstOrDefaultAsync();
-                                    if (item != null)
-                                    {
-                                        item.EvseStatus = (int)_request.status;
-                                        item.FinishedOn = DateTime.UtcNow;
-                                    }
-
-                                    await db.SaveChangesAsync();
-                                }
-                            }
-
-                            var confirm = new FirmwareStatusNotificationConfirmation() { };
-
-                            result.Message = confirm;
-                            result.Success = true;
-                        }
-                        break;
-                    case Actions.DiagnosticsStatusNotification:
-                        {
-                            DiagnosticsStatusNotificationRequest _request = request as DiagnosticsStatusNotificationRequest;
-
-                            if (_request.status != Packet.Messages.SubTypes.DiagnosticsStatus.Idle)
-                            {
-                                using (var db = await maindbContextFactory.CreateDbContextAsync())
-                                {
-                                    var item = db.MachineOperateRecord.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.Action == "GetDiagnostics" && x.RequestType == 1)
-                                        .OrderByDescending(x => x.CreatedOn).FirstOrDefault();
-                                    if (item != null)
-                                    {
-                                        item.EvseStatus = (int)_request.status;
-                                        item.FinishedOn = DateTime.UtcNow;
-                                    }
-
-                                    await db.SaveChangesAsync();
-                                }
-
-                            }
-
-                            var confirm = new DiagnosticsStatusNotificationConfirmation() { };
-
-                            result.Message = confirm;
-                            result.Success = true;
-
-                        }
-                        break;
-
-                    default:
-                        {
-                            logger.LogWarning(string.Format("Not Implement {0} Logic", request.GetType().ToString().Replace("OCPPPackage.Messages.FirmwareManagement.", "")));
-                        }
-                        break;
-                }
-            }
-            catch (Exception ex)
-            {
-                logger.LogCritical("chargeBoxId:{0} {1}", session.ChargeBoxId, action);
-                logger.LogCritical("Data {0}", request.ToString());
-                logger.LogCritical("Error {0}", ex.ToString());
-                result.Exception = ex;
-            }
-            return result;
-        }
-
-        internal async Task<MessageResult> ExecuteFirmwareManagementConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.UpdateFirmware:
-                case Actions.GetDiagnostics:
-                    {
-                        string evse_rep = string.Empty;
-                        if (confirm is GetDiagnosticsConfirmation)
-                        {
-                            var confirmation = confirm as GetDiagnosticsConfirmation;
-                            evse_rep = confirmation.fileName;
-                        }
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord
-                                .Where(x => x.SerialNo == requestId && x.ChargeBoxId == session.ChargeBoxId && x.Status == 0)
-                                .FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseValue = string.IsNullOrEmpty(evse_rep) ? operation.EvseValue : evse_rep;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.FirmwareManagement.", "")));
-                    }
-                    break;
-            }
-            return result;
-        }
-
-
-        internal async Task<MessageResult> ReceivedFirmwareManagementError(Actions action, string errorMsg, WsClientData session, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.FirmwareStatusNotification:
-                case Actions.UpdateFirmware:
-                case Actions.GetDiagnostics:
-                case Actions.DiagnosticsStatusNotification:
-                    {
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord
-                                .Where(x => x.SerialNo == requestId && x.ChargeBoxId == session.ChargeBoxId && x.Status == 0)
-                                .FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)255;//錯誤
-                                operation.EvseValue = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
-                    }
-                    break;
-            }
-            return result;
-
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.FirmwareManagement;
+using Newtonsoft.Json;
+
+using System;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using EVCB_OCPP.WSServer.Service.WsService;
+using EVCB_OCPP.Domain.Models.MainDb;
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    internal partial class ProfileHandler
+    {
+        internal async Task<MessageResult> ExecuteFirmwareManagementRequest(Actions action, WsClientData session, IRequest request)
+        {
+            MessageResult result = new MessageResult() { Success = false };
+            try
+            {
+                switch (action)
+                {
+
+                    case Actions.FirmwareStatusNotification:
+                        {
+                            FirmwareStatusNotificationRequest _request = request as FirmwareStatusNotificationRequest;
+
+                            if (_request.status == Packet.Messages.SubTypes.FirmwareStatus.Idle)
+                            {
+                                string requestId = Guid.NewGuid().ToString();
+                                using (var db = await maindbContextFactory.CreateDbContextAsync())
+                                {
+                                    var machine = await db.Machine.Where(x => x.FwAssignedVersion.HasValue == true && x.FwAssignedVersion.HasValue
+                                       && x.FwAssignedVersion != x.FwVersionReport && x.ChargeBoxId == session.ChargeBoxId)
+                                        .Select(x => new { x.Id, x.FwAssignedVersion }).AsNoTracking().FirstOrDefaultAsync();
+
+                                    if (machine != null)
+                                    {
+                                        var mv = db.MachineVersionFile.Include(c => c.UploadFile)
+                                         .Where(c => c.Id == machine.FwAssignedVersion.Value).First();
+
+                                        string downloadUrl = mv.UploadFile.FileUrl;
+
+                                        var _updateFWrequest = new UpdateFirmwareRequest()
+                                        {
+                                            location = new Uri(downloadUrl),
+                                            retries = 3,
+                                            retrieveDate = DateTime.UtcNow,
+                                            retryInterval = 10
+                                        };
+
+                                        await db.MachineOperateRecord.AddAsync(new MachineOperateRecord()
+                                        {
+                                            CreatedOn = DateTime.UtcNow,
+                                            ChargeBoxId = session.ChargeBoxId,
+                                            SerialNo = requestId,
+                                            RequestContent = JsonConvert.SerializeObject(_updateFWrequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
+											EvseStatus = 0,
+											EvseValue = "Fw Version:" + machine.FwAssignedVersion,
+                                            Status = 0,
+                                            RequestType = 0,
+                                            Action = _updateFWrequest.Action.ToString()
+
+                                        });
+
+                                        await db.SaveChangesAsync();
+
+                                        await mainDbService.AddServerMessage(
+                                            ChargeBoxId: session.ChargeBoxId,
+                                            OutAction: _updateFWrequest.Action.ToString(),
+                                            OutRequest: _updateFWrequest,
+                                            SerialNo: requestId);
+
+                                        var clearMachine = await db.Machine.Where(x => x.Id == machine.Id).FirstOrDefaultAsync();
+                                        clearMachine.FwAssignedVersion = null;
+                                        await db.SaveChangesAsync();
+                                    }
+
+                                }
+                            }
+                            else
+                            {
+                                using (var db = await maindbContextFactory.CreateDbContextAsync())
+                                {
+                                    var item = await db.MachineOperateRecord.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.Action == "UpdateFirmware" && x.RequestType == 0)
+                                        .OrderByDescending(x => x.CreatedOn).FirstOrDefaultAsync();
+                                    if (item != null)
+                                    {
+                                        item.EvseStatus = (int)_request.status;
+                                        item.FinishedOn = DateTime.UtcNow;
+                                    }
+
+                                    await db.SaveChangesAsync();
+                                }
+                            }
+
+                            var confirm = new FirmwareStatusNotificationConfirmation() { };
+
+                            result.Message = confirm;
+                            result.Success = true;
+                        }
+                        break;
+                    case Actions.DiagnosticsStatusNotification:
+                        {
+                            DiagnosticsStatusNotificationRequest _request = request as DiagnosticsStatusNotificationRequest;
+
+                            if (_request.status != Packet.Messages.SubTypes.DiagnosticsStatus.Idle)
+                            {
+                                using (var db = await maindbContextFactory.CreateDbContextAsync())
+                                {
+                                    var item = db.MachineOperateRecord.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.Action == "GetDiagnostics" && x.RequestType == 1)
+                                        .OrderByDescending(x => x.CreatedOn).FirstOrDefault();
+                                    if (item != null)
+                                    {
+                                        item.EvseStatus = (int)_request.status;
+                                        item.FinishedOn = DateTime.UtcNow;
+                                    }
+
+                                    await db.SaveChangesAsync();
+                                }
+
+                            }
+
+                            var confirm = new DiagnosticsStatusNotificationConfirmation() { };
+
+                            result.Message = confirm;
+                            result.Success = true;
+
+                        }
+                        break;
+
+                    default:
+                        {
+                            logger.LogWarning(string.Format("Not Implement {0} Logic", request.GetType().ToString().Replace("OCPPPackage.Messages.FirmwareManagement.", "")));
+                        }
+                        break;
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.LogCritical("chargeBoxId:{0} {1}", session.ChargeBoxId, action);
+                logger.LogCritical("Data {0}", request.ToString());
+                logger.LogCritical("Error {0}", ex.ToString());
+                result.Exception = ex;
+            }
+            return result;
+        }
+
+        internal async Task<MessageResult> ExecuteFirmwareManagementConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.UpdateFirmware:
+                case Actions.GetDiagnostics:
+                    {
+                        string evse_rep = string.Empty;
+                        if (confirm is GetDiagnosticsConfirmation)
+                        {
+                            var confirmation = confirm as GetDiagnosticsConfirmation;
+                            evse_rep = confirmation.fileName;
+                        }
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord
+                                .Where(x => x.SerialNo == requestId && x.ChargeBoxId == session.ChargeBoxId && x.Status == 0)
+                                .FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseValue = string.IsNullOrEmpty(evse_rep) ? operation.EvseValue : evse_rep;
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.FirmwareManagement.", "")));
+                    }
+                    break;
+            }
+            return result;
+        }
+
+
+        internal async Task<MessageResult> ReceivedFirmwareManagementError(Actions action, string errorMsg, WsClientData session, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.FirmwareStatusNotification:
+                case Actions.UpdateFirmware:
+                case Actions.GetDiagnostics:
+                case Actions.DiagnosticsStatusNotification:
+                    {
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord
+                                .Where(x => x.SerialNo == requestId && x.ChargeBoxId == session.ChargeBoxId && x.Status == 0)
+                                .FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)255;//錯誤
+                                operation.EvseValue = errorMsg;
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
+                    }
+                    break;
+            }
+            return result;
+
+        }
+    }
+}

+ 108 - 108
EVCB_OCPP.WSServer/Message/LocalAuthListManagementProfileHandler.cs

@@ -1,108 +1,108 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.LocalAuthListManagement;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    internal partial class ProfileHandler
-    {
-        internal async Task<MessageResult> ExecuteLocalAuthListManagementConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.GetLocalListVersion:
-                    {
-                        GetLocalListVersionConfirmation _confirm = confirm as GetLocalListVersionConfirmation;
-                        GetLocalListVersionRequest _request = _confirm.GetRequest() as GetLocalListVersionRequest;
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = 1;//OK
-                                operation.EvseValue = _confirm.listVersion.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                case Actions.SendLocalList:
-                    {
-                        SendLocalListConfirmation _confirm = confirm as SendLocalListConfirmation;
-                        SendLocalListRequest _request = _confirm.GetRequest() as SendLocalListRequest;
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)_confirm.status;//OK     
-                                operation.EvseValue = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
-                    }
-                    break;
-            }
-            return result;
-        }
-
-
-        internal async Task<MessageResult> ReceivedLocalAuthListManagementError(Actions action, string errorMsg, WsClientData session, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.SendLocalList:
-                case Actions.GetLocalListVersion:
-                    {
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)255;//錯誤
-                                operation.EvseValue = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
-                    }
-                    break;
-            }
-            return result;
-
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.LocalAuthListManagement;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    internal partial class ProfileHandler
+    {
+        internal async Task<MessageResult> ExecuteLocalAuthListManagementConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.GetLocalListVersion:
+                    {
+                        GetLocalListVersionConfirmation _confirm = confirm as GetLocalListVersionConfirmation;
+                        GetLocalListVersionRequest _request = _confirm.GetRequest() as GetLocalListVersionRequest;
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = 1;//OK
+                                operation.EvseValue = _confirm.listVersion.ToString();
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                case Actions.SendLocalList:
+                    {
+                        SendLocalListConfirmation _confirm = confirm as SendLocalListConfirmation;
+                        SendLocalListRequest _request = _confirm.GetRequest() as SendLocalListRequest;
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)_confirm.status;//OK     
+                                operation.EvseValue = _confirm.status.ToString();
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
+                    }
+                    break;
+            }
+            return result;
+        }
+
+
+        internal async Task<MessageResult> ReceivedLocalAuthListManagementError(Actions action, string errorMsg, WsClientData session, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.SendLocalList:
+                case Actions.GetLocalListVersion:
+                    {
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)255;//錯誤
+                                operation.EvseValue = errorMsg;
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
+                    }
+                    break;
+            }
+            return result;
+
+        }
+    }
+}

+ 349 - 349
EVCB_OCPP.WSServer/Message/OCPP16MessageHandler.cs

@@ -1,349 +1,349 @@
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.Basic;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-
-using System;
-using System.Collections.Generic;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    /// <summary>
-    /// 實現 OCPP16 基本傳送規範,
-    /// 1.訊息 基本格式,將訊息包裝成 Call 、CallResult、CallError 三種格式
-    /// 2.OCPP 定義的傳送規則:交易相關的訊息必須依照時序性傳送,一個傳完才能接著送下一個(忽略規則 由Center System定義)
-    /// </summary>
-    internal class OCPP16MessageHandler
-    {
-        static protected ILogger logger;
-
-        #region 傳送 or 解析訊息需要欄位
-        private const int INDEX_MESSAGEID = 0;
-        private const int INDEX_UNIQUEID = 1;
-        internal const int TYPENUMBER_CALL = 2;
-        private const int INDEX_CALL_ACTION = 2;
-        private const int INDEX_CALL_PAYLOAD = 3;
-
-        internal const int TYPENUMBER_CALLRESULT = 3;
-        private const int INDEX_CALLRESULT_PAYLOAD = 2;
-
-        internal const int TYPENUMBER_CALLERROR = 4;
-        private const int INDEX_CALLERROR_ERRORCODE = 2;
-        private const int INDEX_CALLERROR_DESCRIPTION = 3;
-        private const int INDEX_CALLERROR_PAYLOAD = 4;
-
-        private const string CALL_FORMAT = "[2,\"{0}\",\"{1}\",{2}]";
-        private const string CALLRESULT_FORMAT = "[3,\"{0}\",{1}]";
-        private const string CALLERROR_FORMAT = "[4,\"{0}\",\"{1}\",\"{2}\",{3}]";
-        private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
-        private const string DATE_FORMAT_WITH_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
-        #endregion
-
-        private List<Profile> profiles = new List<Profile>()
-        {
-             new CoreProfile(),
-             new FirmwareManagementProfile(),
-             new ReservationProfile(),
-             new RemoteTriggerProfile(),
-             new SmartChargingProfile(),
-             new LocalAuthListManagementProfile(),
-             new SecurityProfile()
-        };
-
-
-        /// <summary>
-        /// 將收到的封包做基本的拆解分成 Call 、CallResult、CallError
-        /// </summary>
-        /// <param name="client"></param>
-        /// <param name="data"></param>
-        /// <returns></returns>
-        internal MessageResult AnalysisReceiveData(WsClientData client, string data)
-        {
-            MessageResult result = new MessageResult();
-            try
-            {
-
-                var msg = Parse(data);
-                if (msg != null)
-                {
-
-                    result.UUID = msg.Id;
-                    switch (msg.TypeId)
-                    {
-                        case TYPENUMBER_CALL:
-                            {
-                                //只有CallMessage 才有在RawData有Action
-                                BasicMessageResult baseResult = UnPackPayloadbyCall(msg.Action, msg.Payload.ToString());
-                                Actions action = Actions.None;
-                                Enum.TryParse<Actions>(msg.Action, out action);
-                                result.Action = msg.Action;
-                                if (baseResult.Request != null)
-                                {
-                                    if (baseResult.Request.Validate())
-                                    {
-                                        result.Id = TYPENUMBER_CALL;
-                                        result.Message = baseResult.Request;
-
-                                    }
-                                    else
-                                    {
-
-                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
-                                             OCPPErrorDescription.OccurenceConstraintViolation);
-                                        result.Id = TYPENUMBER_CALL;
-                                        result.Message = baseResult.Request;
-                                        result.Success = false;
-                                        result.CallErrorMsg = replyMsg;
-                                        result.Exception = new Exception("Validation Failed");
-
-                                    }
-                                }
-                                else
-                                {
-
-                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation, OCPPErrorDescription.OccurenceConstraintViolation);
-                                    result.Id = TYPENUMBER_CALL;
-                                    result.Message = baseResult.Request;
-                                    result.Success = false;
-                                    result.CallErrorMsg = replyMsg;
-                                    result.Exception = baseResult.Exception;
-                                }
-
-                            }
-                            break;
-                        case TYPENUMBER_CALLRESULT:
-                            {
-                                BasicMessageResult baseResult = UnPackPayloadbyCallResult(client.queue, msg.Id, msg.Payload.ToString());
-
-                                if (baseResult.Confirmation != null)
-                                {
-
-                                    if (baseResult.Confirmation.Validate())
-                                    {
-                                        result.Id = TYPENUMBER_CALLRESULT;
-                                        result.Message = baseResult.Confirmation;
-                                        result.Action = baseResult.Confirmation.GetRequest().Action;
-                                        //return data
-                                    }
-                                    else
-                                    {
-                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
-                                      OCPPErrorDescription.OccurenceConstraintViolation);
-                                        result.Id = TYPENUMBER_CALLRESULT;
-                                        result.Message = baseResult.Confirmation;
-                                        result.Success = false;
-                                        result.CallErrorMsg = replyMsg;
-                                        result.Exception = new Exception("Validate Failed");
-                                    }
-                                }
-                                else
-                                {
-                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
-                                   OCPPErrorDescription.OccurenceConstraintViolation);
-                                    result.Id = TYPENUMBER_CALLRESULT;
-                                    result.Message = baseResult.Confirmation;
-                                    result.Success = false;
-                                    result.CallErrorMsg = replyMsg;
-                                    result.Exception = baseResult.Exception;
-                                }
-
-                            }
-                            break;
-                        case TYPENUMBER_CALLERROR:
-                            {
-                                result.Id = TYPENUMBER_CALLERROR;
-                                var sentRequest = UnPackPayloadbyCallError(client.queue, msg.Id);
-
-                                if (sentRequest != null)
-                                {
-                                    IRequest request = sentRequest as IRequest;
-                                    result.Action = request.Action;
-
-                                    result.Message = sentRequest;
-                                    result.ReceivedErrorCode = string.Format("ErrorMsg {0}:{1}", ((CallErrorMessage)msg).ErrorCode, ((CallErrorMessage)msg).ErrorDescription);
-                                }
-
-
-                            }
-                            break;
-                        default:
-                            break;
-
-                    }
-
-                    // if (msg != null) Console.WriteLine(string.Format("Receieved Message : {0}", msg.ToString()));
-
-                }
-            }
-            catch (Exception ex)
-            {
-                if (string.IsNullOrEmpty(result.UUID))
-                {
-                    result.UUID = data.Substring(4, 39);
-                    result.UUID = result.UUID.Split(new string[] { "\"," }, StringSplitOptions.None)[0];
-                }
-                result.Success = false;
-                result.Exception = ex;
-            }
-            return result;
-        }
-
-        #region 解析收到的訊息
-        /// <summary>
-        /// Parse data to OCPP Basic Message
-        /// </summary>
-        /// <param name="message"></param>
-        /// <returns></returns>
-        private BaseMessage Parse(string message)
-        {
-            try
-            {
-                if (message.StartsWith("[4,\""))
-                {
-                    message = message.Replace('{', '"');
-                    message = message.Replace('}', '"');
-                }
-                var array = JsonConvert.DeserializeObject<JArray>(message);
-                BaseMessage msg = null;
-                switch ((int)array[INDEX_MESSAGEID])
-                {
-                    case TYPENUMBER_CALL:
-                        {
-                            CallMessage call = new CallMessage();
-                            call.Action = array[INDEX_CALL_ACTION].ToString();
-                            call.Payload = array[INDEX_CALL_PAYLOAD].ToString().Replace("\r\n", "");
-                            msg = call;
-                        }
-                        break;
-                    case TYPENUMBER_CALLRESULT:
-                        {
-                            CallResultMessage callResult = new CallResultMessage();
-                            callResult.Payload = array[INDEX_CALLRESULT_PAYLOAD].ToString().Replace("\r\n", "");
-                            msg = callResult;
-                        }
-                        break;
-                    case TYPENUMBER_CALLERROR:
-                        {
-                            CallErrorMessage callError = new CallErrorMessage();
-                            callError.ErrorCode = array[INDEX_CALLERROR_ERRORCODE].ToString();
-                            callError.ErrorDescription = array[INDEX_CALLERROR_DESCRIPTION].ToString();
-                            callError.ErrorDetails = array[INDEX_CALLERROR_PAYLOAD].ToString().Replace("\r\n", "");
-                            msg = callError;
-                        }
-                        break;
-                    default:
-                        throw new Exception("Message Type notSupported");
-
-
-                }
-                msg.Id = array[INDEX_UNIQUEID].ToString();
-                return msg;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(string.Format("Parse Error=>  Problem: {0}", ex.Message));
-
-            }
-
-
-
-        }
-
-
-        private BasicMessageResult UnPackPayloadbyCall(string action, string payload)
-        {
-            BasicMessageResult result = new BasicMessageResult();
-            try
-            {
-                Feature feature = null;
-                foreach (var profile in profiles)
-                {
-                    feature = profile.GetFeaturebyAction(action);
-                    if (feature == null)
-                    {
-                        continue;
-                    }
-                    else
-                    {
-                        break;
-                    }
-                }
-
-                result.Request = JsonConvert.DeserializeObject(payload, feature.GetRequestType()) as IRequest;
-
-            }
-            catch (Exception ex)
-            {
-                result.Exception = ex;
-                logger.LogError(string.Format("[{0}]UnPackPayloadbyCall Ex: {1}", action, ex.Message), "UnPack");
-
-            }
-
-            return result;
-        }
-
-        private BasicMessageResult UnPackPayloadbyCallResult(Queue requestQueue, string uniqueId, string payload)
-        {
-            int i = 1;
-            BasicMessageResult result = new BasicMessageResult();
-            try
-            {
-                i = -1 ;
-                IRequest request = requestQueue.RestoreRequest(uniqueId);
-
-                if (request == null)
-                {
-                    result.Exception = new Exception("Can't find request");
-                    return result;
-                }
-                i = 2;
-                Feature feature = null;
-                foreach (var profile in profiles)
-                {
-                    i = 3;
-                    feature = profile.GetFeaturebyType(request.GetType());
-                    i = 4;
-                    if (feature == null)
-                    {
-                        continue;
-                    }
-                    else
-                    {
-                        break;
-                    }
-                }
-                i = 5;
-                IConfirmation confrim = JsonConvert.DeserializeObject(payload, feature.GetConfirmationType()) as IConfirmation;
-                i = 6;
-                confrim.SetRequest(request);
-                i = 7;
-                result.Confirmation = confrim;
-
-            }
-            catch (Exception ex)
-            {
-                result.Exception = ex;
-                logger.LogError(string.Format(i+"UnPackPayloadbyCallResult Data:[{0},{1}] Ex: {2}", uniqueId, payload, ex.ToString()), "UnPack");
-
-            }
-            return result;
-
-        }
-
-        private IRequest UnPackPayloadbyCallError(Queue requestQueue, string uniqueId)
-        {
-            IRequest sentMsg = requestQueue.RestoreRequest(uniqueId);
-
-            return sentMsg;
-
-        }
-        #endregion
-
-
-    }
-}
-
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.Basic;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+
+using System;
+using System.Collections.Generic;
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    /// <summary>
+    /// 實現 OCPP16 基本傳送規範,
+    /// 1.訊息 基本格式,將訊息包裝成 Call 、CallResult、CallError 三種格式
+    /// 2.OCPP 定義的傳送規則:交易相關的訊息必須依照時序性傳送,一個傳完才能接著送下一個(忽略規則 由Center System定義)
+    /// </summary>
+    internal class OCPP16MessageHandler
+    {
+        static protected ILogger logger;
+
+        #region 傳送 or 解析訊息需要欄位
+        private const int INDEX_MESSAGEID = 0;
+        private const int INDEX_UNIQUEID = 1;
+        internal const int TYPENUMBER_CALL = 2;
+        private const int INDEX_CALL_ACTION = 2;
+        private const int INDEX_CALL_PAYLOAD = 3;
+
+        internal const int TYPENUMBER_CALLRESULT = 3;
+        private const int INDEX_CALLRESULT_PAYLOAD = 2;
+
+        internal const int TYPENUMBER_CALLERROR = 4;
+        private const int INDEX_CALLERROR_ERRORCODE = 2;
+        private const int INDEX_CALLERROR_DESCRIPTION = 3;
+        private const int INDEX_CALLERROR_PAYLOAD = 4;
+
+        private const string CALL_FORMAT = "[2,\"{0}\",\"{1}\",{2}]";
+        private const string CALLRESULT_FORMAT = "[3,\"{0}\",{1}]";
+        private const string CALLERROR_FORMAT = "[4,\"{0}\",\"{1}\",\"{2}\",{3}]";
+        private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+        private const string DATE_FORMAT_WITH_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+        #endregion
+
+        private List<Profile> profiles = new List<Profile>()
+        {
+             new CoreProfile(),
+             new FirmwareManagementProfile(),
+             new ReservationProfile(),
+             new RemoteTriggerProfile(),
+             new SmartChargingProfile(),
+             new LocalAuthListManagementProfile(),
+             new SecurityProfile()
+        };
+
+
+        /// <summary>
+        /// 將收到的封包做基本的拆解分成 Call 、CallResult、CallError
+        /// </summary>
+        /// <param name="client"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        internal MessageResult AnalysisReceiveData(WsClientData client, string data)
+        {
+            MessageResult result = new MessageResult();
+            try
+            {
+
+                var msg = Parse(data);
+                if (msg != null)
+                {
+
+                    result.UUID = msg.Id;
+                    switch (msg.TypeId)
+                    {
+                        case TYPENUMBER_CALL:
+                            {
+                                //只有CallMessage 才有在RawData有Action
+                                BasicMessageResult baseResult = UnPackPayloadbyCall(msg.Action, msg.Payload.ToString());
+                                Actions action = Actions.None;
+                                Enum.TryParse<Actions>(msg.Action, out action);
+                                result.Action = msg.Action;
+                                if (baseResult.Request != null)
+                                {
+                                    if (baseResult.Request.Validate())
+                                    {
+                                        result.Id = TYPENUMBER_CALL;
+                                        result.Message = baseResult.Request;
+
+                                    }
+                                    else
+                                    {
+
+                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
+                                             OCPPErrorDescription.OccurenceConstraintViolation);
+                                        result.Id = TYPENUMBER_CALL;
+                                        result.Message = baseResult.Request;
+                                        result.Success = false;
+                                        result.CallErrorMsg = replyMsg;
+                                        result.Exception = new Exception("Validation Failed");
+
+                                    }
+                                }
+                                else
+                                {
+
+                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation, OCPPErrorDescription.OccurenceConstraintViolation);
+                                    result.Id = TYPENUMBER_CALL;
+                                    result.Message = baseResult.Request;
+                                    result.Success = false;
+                                    result.CallErrorMsg = replyMsg;
+                                    result.Exception = baseResult.Exception;
+                                }
+
+                            }
+                            break;
+                        case TYPENUMBER_CALLRESULT:
+                            {
+                                BasicMessageResult baseResult = UnPackPayloadbyCallResult(client.queue, msg.Id, msg.Payload.ToString());
+
+                                if (baseResult.Confirmation != null)
+                                {
+
+                                    if (baseResult.Confirmation.Validate())
+                                    {
+                                        result.Id = TYPENUMBER_CALLRESULT;
+                                        result.Message = baseResult.Confirmation;
+                                        result.Action = baseResult.Confirmation.GetRequest().Action;
+                                        //return data
+                                    }
+                                    else
+                                    {
+                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
+                                      OCPPErrorDescription.OccurenceConstraintViolation);
+                                        result.Id = TYPENUMBER_CALLRESULT;
+                                        result.Message = baseResult.Confirmation;
+                                        result.Success = false;
+                                        result.CallErrorMsg = replyMsg;
+                                        result.Exception = new Exception("Validate Failed");
+                                    }
+                                }
+                                else
+                                {
+                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
+                                   OCPPErrorDescription.OccurenceConstraintViolation);
+                                    result.Id = TYPENUMBER_CALLRESULT;
+                                    result.Message = baseResult.Confirmation;
+                                    result.Success = false;
+                                    result.CallErrorMsg = replyMsg;
+                                    result.Exception = baseResult.Exception;
+                                }
+
+                            }
+                            break;
+                        case TYPENUMBER_CALLERROR:
+                            {
+                                result.Id = TYPENUMBER_CALLERROR;
+                                var sentRequest = UnPackPayloadbyCallError(client.queue, msg.Id);
+
+                                if (sentRequest != null)
+                                {
+                                    IRequest request = sentRequest as IRequest;
+                                    result.Action = request.Action;
+
+                                    result.Message = sentRequest;
+                                    result.ReceivedErrorCode = string.Format("ErrorMsg {0}:{1}", ((CallErrorMessage)msg).ErrorCode, ((CallErrorMessage)msg).ErrorDescription);
+                                }
+
+
+                            }
+                            break;
+                        default:
+                            break;
+
+                    }
+
+                    // if (msg != null) Console.WriteLine(string.Format("Receieved Message : {0}", msg.ToString()));
+
+                }
+            }
+            catch (Exception ex)
+            {
+                if (string.IsNullOrEmpty(result.UUID))
+                {
+                    result.UUID = data.Substring(4, 39);
+                    result.UUID = result.UUID.Split(new string[] { "\"," }, StringSplitOptions.None)[0];
+                }
+                result.Success = false;
+                result.Exception = ex;
+            }
+            return result;
+        }
+
+        #region 解析收到的訊息
+        /// <summary>
+        /// Parse data to OCPP Basic Message
+        /// </summary>
+        /// <param name="message"></param>
+        /// <returns></returns>
+        private BaseMessage Parse(string message)
+        {
+            try
+            {
+                if (message.StartsWith("[4,\""))
+                {
+                    message = message.Replace('{', '"');
+                    message = message.Replace('}', '"');
+                }
+                var array = JsonConvert.DeserializeObject<JArray>(message);
+                BaseMessage msg = null;
+                switch ((int)array[INDEX_MESSAGEID])
+                {
+                    case TYPENUMBER_CALL:
+                        {
+                            CallMessage call = new CallMessage();
+                            call.Action = array[INDEX_CALL_ACTION].ToString();
+                            call.Payload = array[INDEX_CALL_PAYLOAD].ToString().Replace("\r\n", "");
+                            msg = call;
+                        }
+                        break;
+                    case TYPENUMBER_CALLRESULT:
+                        {
+                            CallResultMessage callResult = new CallResultMessage();
+                            callResult.Payload = array[INDEX_CALLRESULT_PAYLOAD].ToString().Replace("\r\n", "");
+                            msg = callResult;
+                        }
+                        break;
+                    case TYPENUMBER_CALLERROR:
+                        {
+                            CallErrorMessage callError = new CallErrorMessage();
+                            callError.ErrorCode = array[INDEX_CALLERROR_ERRORCODE].ToString();
+                            callError.ErrorDescription = array[INDEX_CALLERROR_DESCRIPTION].ToString();
+                            callError.ErrorDetails = array[INDEX_CALLERROR_PAYLOAD].ToString().Replace("\r\n", "");
+                            msg = callError;
+                        }
+                        break;
+                    default:
+                        throw new Exception("Message Type notSupported");
+
+
+                }
+                msg.Id = array[INDEX_UNIQUEID].ToString();
+                return msg;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(string.Format("Parse Error=>  Problem: {0}", ex.Message));
+
+            }
+
+
+
+        }
+
+
+        private BasicMessageResult UnPackPayloadbyCall(string action, string payload)
+        {
+            BasicMessageResult result = new BasicMessageResult();
+            try
+            {
+                Feature feature = null;
+                foreach (var profile in profiles)
+                {
+                    feature = profile.GetFeaturebyAction(action);
+                    if (feature == null)
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+
+                result.Request = JsonConvert.DeserializeObject(payload, feature.GetRequestType()) as IRequest;
+
+            }
+            catch (Exception ex)
+            {
+                result.Exception = ex;
+                logger.LogError(string.Format("[{0}]UnPackPayloadbyCall Ex: {1}", action, ex.Message), "UnPack");
+
+            }
+
+            return result;
+        }
+
+        private BasicMessageResult UnPackPayloadbyCallResult(Queue requestQueue, string uniqueId, string payload)
+        {
+            int i = 1;
+            BasicMessageResult result = new BasicMessageResult();
+            try
+            {
+                i = -1 ;
+                IRequest request = requestQueue.RestoreRequest(uniqueId);
+
+                if (request == null)
+                {
+                    result.Exception = new Exception("Can't find request");
+                    return result;
+                }
+                i = 2;
+                Feature feature = null;
+                foreach (var profile in profiles)
+                {
+                    i = 3;
+                    feature = profile.GetFeaturebyType(request.GetType());
+                    i = 4;
+                    if (feature == null)
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+                i = 5;
+                IConfirmation confrim = JsonConvert.DeserializeObject(payload, feature.GetConfirmationType()) as IConfirmation;
+                i = 6;
+                confrim.SetRequest(request);
+                i = 7;
+                result.Confirmation = confrim;
+
+            }
+            catch (Exception ex)
+            {
+                result.Exception = ex;
+                logger.LogError(string.Format(i+"UnPackPayloadbyCallResult Data:[{0},{1}] Ex: {2}", uniqueId, payload, ex.ToString()), "UnPack");
+
+            }
+            return result;
+
+        }
+
+        private IRequest UnPackPayloadbyCallError(Queue requestQueue, string uniqueId)
+        {
+            IRequest sentMsg = requestQueue.RestoreRequest(uniqueId);
+
+            return sentMsg;
+
+        }
+        #endregion
+
+
+    }
+}
+

+ 298 - 298
EVCB_OCPP.WSServer/Message/OCPP20MessageHandler.cs

@@ -1,298 +1,298 @@
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.Basic;
-using EVCB_OCPP.WSServer.Service.WsService;
-using EVCB_OCPP20.Packet.Features;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-using System;
-using I20Confirmation = EVCB_OCPP20.Packet.Messages.IConfirmation;
-using I20Request = EVCB_OCPP20.Packet.Messages.IRequest;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    /// <summary>
-    /// 實現 OCPP20 基本傳送規範,
-    /// 1.訊息 基本格式,將訊息包裝成 Call 、CallResult、CallError 三種格式
-    /// 2.OCPP 定義的傳送規則:交易相關的訊息必須依照時序性傳送,一個傳完才能接著送下一個(忽略規則 由Center System定義)
-    /// </summary>
-    internal class OCPP20MessageHandler
-    {
-        static protected ILogger logger;
-
-        #region 傳送 or 解析訊息需要欄位
-        private const int INDEX_MESSAGEID = 0;
-        private const int INDEX_UNIQUEID = 1;
-        internal const int TYPENUMBER_CALL = 2;
-        private const int INDEX_CALL_ACTION = 2;
-        private const int INDEX_CALL_PAYLOAD = 3;
-
-        internal const int TYPENUMBER_CALLRESULT = 3;
-        private const int INDEX_CALLRESULT_PAYLOAD = 2;
-
-        internal const int TYPENUMBER_CALLERROR = 4;
-        private const int INDEX_CALLERROR_ERRORCODE = 2;
-        private const int INDEX_CALLERROR_DESCRIPTION = 3;
-        private const int INDEX_CALLERROR_PAYLOAD = 4;
-
-        private const string CALL_FORMAT = "[2,\"{0}\",\"{1}\",{2}]";
-        private const string CALLRESULT_FORMAT = "[3,\"{0}\",{1}]";
-        private const string CALLERROR_FORMAT = "[4,\"{0}\",\"{1}\",\"{2}\",{3}]";
-        private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
-        private const string DATE_FORMAT_WITH_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
-        #endregion
-
-
-
-
-        /// <summary>
-        /// 將收到的封包做基本的拆解分成 Call 、CallResult、CallError
-        /// </summary>
-        /// <param name="client"></param>
-        /// <param name="data"></param>
-        /// <returns></returns>
-        internal MessageResult AnalysisReceiveData(WsClientData client, string data)
-        {
-            MessageResult result = new MessageResult();
-            try
-            {
-
-                var msg = Parse(data);
-                if (msg != null)
-                {
-
-                    result.UUID = msg.Id;
-                    switch (msg.TypeId)
-                    {
-                        case TYPENUMBER_CALL:
-                            {
-                                //只有CallMessage 才有在RawData有Action
-                                BasicMessageResult baseResult = UnPackPayloadbyCall(msg.Action, msg.Payload.ToString());
-                                Actions action = Actions.None;
-                                Enum.TryParse<Actions>(msg.Action, out action);
-                                result.Action = msg.Action;
-                                if (baseResult.Request20 != null)
-                                {
-                                    if (baseResult.Request20.Validate())
-                                    {
-                                        result.Id = TYPENUMBER_CALL;
-                                        result.Message = baseResult.Request20;
-
-                                    }
-                                    else
-                                    {
-
-                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
-                                             OCPPErrorDescription.OccurenceConstraintViolation);
-                                        result.Id = TYPENUMBER_CALL;
-                                        result.Message = baseResult.Request20;
-                                        result.Success = false;
-                                        result.CallErrorMsg = replyMsg;
-                                        result.Exception = new Exception("Validation Failed");
-
-                                    }
-                                }
-                                else
-                                {
-
-                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation, OCPPErrorDescription.OccurenceConstraintViolation);
-                                    result.Id = TYPENUMBER_CALL;
-                                    result.Message = baseResult.Request20;
-                                    result.Success = false;
-                                    result.CallErrorMsg = replyMsg;
-                                    result.Exception = baseResult.Exception;
-                                }
-
-                            }
-                            break;
-                        case TYPENUMBER_CALLRESULT:
-                            {
-                                BasicMessageResult baseResult = UnPackPayloadbyCallResult(client.queue20, msg.Id, msg.Payload.ToString());
-
-                                if (baseResult.Confirmation20 != null)
-                                {
-
-                                    if (baseResult.Confirmation20.Validate())
-                                    {
-                                        result.Id = TYPENUMBER_CALLRESULT;
-                                        result.Message = baseResult.Confirmation20;
-                                        result.Action = baseResult.Confirmation20.GetRequest().Action;
-                                        //return data
-                                    }
-                                    else
-                                    {
-                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
-                                      OCPPErrorDescription.OccurenceConstraintViolation);
-                                        result.Id = TYPENUMBER_CALLRESULT;
-                                        result.Message = baseResult.Confirmation20;
-                                        result.Success = false;
-                                        result.CallErrorMsg = replyMsg;
-                                        result.Exception = new Exception("Validate Failed");
-                                    }
-                                }
-                                else
-                                {
-                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
-                                   OCPPErrorDescription.OccurenceConstraintViolation);
-                                    result.Id = TYPENUMBER_CALLRESULT;
-                                    result.Message = baseResult.Confirmation20;
-                                    result.Success = false;
-                                    result.CallErrorMsg = replyMsg;
-                                    result.Exception = baseResult.Exception;
-                                }
-
-                            }
-                            break;
-                        case TYPENUMBER_CALLERROR:
-                            {
-                                result.Id = TYPENUMBER_CALLERROR;
-                                // var sentRequest = UnPackPayloadbyCallError(client.queue, msg.Id);
-
-                                //if (sentRequest != null)
-                                //{
-                                //    I20Request request = sentRequest as I20Request;
-                                //    result.Action = request.Action;
-
-                                //    result.Message = sentRequest;
-                                //    result.ReceivedErrorCode = string.Format("ErrorMsg {0}:{1}", ((CallErrorMessage)msg).ErrorCode, ((CallErrorMessage)msg).ErrorDescription);
-                                //}
-
-
-                            }
-                            break;
-                        default:
-                            break;
-
-                    }
-
-                    // if (msg != null) Console.WriteLine(string.Format("Receieved Message : {0}", msg.ToString()));
-
-                }
-            }
-            catch (Exception ex)
-            {
-                if (string.IsNullOrEmpty(result.UUID))
-                {
-                    result.UUID = data.Substring(4, 39);
-                    result.UUID = result.UUID.Split(new string[] { "\"," }, StringSplitOptions.None)[0];
-                }
-                result.Success = false;
-                result.Exception = ex;
-            }
-            return result;
-        }
-
-        #region 解析收到的訊息
-        /// <summary>
-        /// Parse data to OCPP Basic Message
-        /// </summary>
-        /// <param name="message"></param>
-        /// <returns></returns>
-        private BaseMessage Parse(string message)
-        {
-            try
-            {
-                if (message.StartsWith("[4,\""))
-                {
-                    message = message.Replace('{', '"');
-                    message = message.Replace('}', '"');
-                }
-                var array = JsonConvert.DeserializeObject<JArray>(message);
-                BaseMessage msg = null;
-                switch ((int)array[INDEX_MESSAGEID])
-                {
-                    case TYPENUMBER_CALL:
-                        {
-                            CallMessage call = new CallMessage();
-                            call.Action = array[INDEX_CALL_ACTION].ToString();
-                            call.Payload = array[INDEX_CALL_PAYLOAD].ToString().Replace("\r\n", "");
-                            msg = call;
-                        }
-                        break;
-                    case TYPENUMBER_CALLRESULT:
-                        {
-                            CallResultMessage callResult = new CallResultMessage();
-                            callResult.Payload = array[INDEX_CALLRESULT_PAYLOAD].ToString().Replace("\r\n", "");
-                            msg = callResult;
-                        }
-                        break;
-                    case TYPENUMBER_CALLERROR:
-                        {
-                            CallErrorMessage callError = new CallErrorMessage();
-                            callError.ErrorCode = array[INDEX_CALLERROR_ERRORCODE].ToString();
-                            callError.ErrorDescription = array[INDEX_CALLERROR_DESCRIPTION].ToString();
-                            callError.ErrorDetails = array[INDEX_CALLERROR_PAYLOAD].ToString().Replace("\r\n", "");
-                            msg = callError;
-                        }
-                        break;
-                    default:
-                        throw new Exception("Message Type notSupported");
-
-
-                }
-                msg.Id = array[INDEX_UNIQUEID].ToString();
-                return msg;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(string.Format("Parse Error=> {0} Problem: {0}", message, ex.Message));
-
-            }
-
-
-
-        }
-
-
-        private BasicMessageResult UnPackPayloadbyCall(string action, string payload)
-        {
-
-            BasicMessageResult result = new BasicMessageResult();
-            try
-            {
-
-                result.Request20 = (I20Request)(JsonConvert.DeserializeObject(payload, Type.GetType("EVCB_OCPP20.Packet.Messages." + action + "Request,EVCB_OCPP20.Packet")));
-
-            }
-            catch (Exception ex)
-            {
-                result.Exception = ex;
-                logger.LogError(string.Format("[{0}]UnPackPayloadbyCall Ex: {1}", action, ex.Message), "UnPack");
-
-            }
-
-            return result;
-
-
-        }
-
-        private BasicMessageResult UnPackPayloadbyCallResult(EVCB_OCPP20.Packet.Messages.Basic.Queue requestQueue, string uniqueId, string payload)
-        {
-            BasicMessageResult result = new BasicMessageResult();
-            try
-            {
-                I20Request request = requestQueue.RestoreRequest(uniqueId);
-                I20Confirmation confrim = JsonConvert.DeserializeObject(payload, Type.GetType("EVCB_OCPP20.Packet.Messages." + request.Action + "Response,EVCB_OCPP20.Packet")) as I20Confirmation;
-                confrim.SetRequest(request);
-                result.Confirmation20 = confrim;
-
-            }
-            catch (Exception ex)
-            {
-                result.Exception = ex;
-                logger.LogError(string.Format("UnPackPayloadbyCallResult Data:[{0},{1}] Ex: {2}", uniqueId, payload, ex.ToString()), "UnPack");
-
-            }
-            return result;
-
-        }
-
-
-
-        #endregion
-
-
-
-    }
-}
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.Basic;
+using EVCB_OCPP.WSServer.Service.WsService;
+using EVCB_OCPP20.Packet.Features;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+using System;
+using I20Confirmation = EVCB_OCPP20.Packet.Messages.IConfirmation;
+using I20Request = EVCB_OCPP20.Packet.Messages.IRequest;
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    /// <summary>
+    /// 實現 OCPP20 基本傳送規範,
+    /// 1.訊息 基本格式,將訊息包裝成 Call 、CallResult、CallError 三種格式
+    /// 2.OCPP 定義的傳送規則:交易相關的訊息必須依照時序性傳送,一個傳完才能接著送下一個(忽略規則 由Center System定義)
+    /// </summary>
+    internal class OCPP20MessageHandler
+    {
+        static protected ILogger logger;
+
+        #region 傳送 or 解析訊息需要欄位
+        private const int INDEX_MESSAGEID = 0;
+        private const int INDEX_UNIQUEID = 1;
+        internal const int TYPENUMBER_CALL = 2;
+        private const int INDEX_CALL_ACTION = 2;
+        private const int INDEX_CALL_PAYLOAD = 3;
+
+        internal const int TYPENUMBER_CALLRESULT = 3;
+        private const int INDEX_CALLRESULT_PAYLOAD = 2;
+
+        internal const int TYPENUMBER_CALLERROR = 4;
+        private const int INDEX_CALLERROR_ERRORCODE = 2;
+        private const int INDEX_CALLERROR_DESCRIPTION = 3;
+        private const int INDEX_CALLERROR_PAYLOAD = 4;
+
+        private const string CALL_FORMAT = "[2,\"{0}\",\"{1}\",{2}]";
+        private const string CALLRESULT_FORMAT = "[3,\"{0}\",{1}]";
+        private const string CALLERROR_FORMAT = "[4,\"{0}\",\"{1}\",\"{2}\",{3}]";
+        private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+        private const string DATE_FORMAT_WITH_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+        #endregion
+
+
+
+
+        /// <summary>
+        /// 將收到的封包做基本的拆解分成 Call 、CallResult、CallError
+        /// </summary>
+        /// <param name="client"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        internal MessageResult AnalysisReceiveData(WsClientData client, string data)
+        {
+            MessageResult result = new MessageResult();
+            try
+            {
+
+                var msg = Parse(data);
+                if (msg != null)
+                {
+
+                    result.UUID = msg.Id;
+                    switch (msg.TypeId)
+                    {
+                        case TYPENUMBER_CALL:
+                            {
+                                //只有CallMessage 才有在RawData有Action
+                                BasicMessageResult baseResult = UnPackPayloadbyCall(msg.Action, msg.Payload.ToString());
+                                Actions action = Actions.None;
+                                Enum.TryParse<Actions>(msg.Action, out action);
+                                result.Action = msg.Action;
+                                if (baseResult.Request20 != null)
+                                {
+                                    if (baseResult.Request20.Validate())
+                                    {
+                                        result.Id = TYPENUMBER_CALL;
+                                        result.Message = baseResult.Request20;
+
+                                    }
+                                    else
+                                    {
+
+                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
+                                             OCPPErrorDescription.OccurenceConstraintViolation);
+                                        result.Id = TYPENUMBER_CALL;
+                                        result.Message = baseResult.Request20;
+                                        result.Success = false;
+                                        result.CallErrorMsg = replyMsg;
+                                        result.Exception = new Exception("Validation Failed");
+
+                                    }
+                                }
+                                else
+                                {
+
+                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation, OCPPErrorDescription.OccurenceConstraintViolation);
+                                    result.Id = TYPENUMBER_CALL;
+                                    result.Message = baseResult.Request20;
+                                    result.Success = false;
+                                    result.CallErrorMsg = replyMsg;
+                                    result.Exception = baseResult.Exception;
+                                }
+
+                            }
+                            break;
+                        case TYPENUMBER_CALLRESULT:
+                            {
+                                BasicMessageResult baseResult = UnPackPayloadbyCallResult(client.queue20, msg.Id, msg.Payload.ToString());
+
+                                if (baseResult.Confirmation20 != null)
+                                {
+
+                                    if (baseResult.Confirmation20.Validate())
+                                    {
+                                        result.Id = TYPENUMBER_CALLRESULT;
+                                        result.Message = baseResult.Confirmation20;
+                                        result.Action = baseResult.Confirmation20.GetRequest().Action;
+                                        //return data
+                                    }
+                                    else
+                                    {
+                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
+                                      OCPPErrorDescription.OccurenceConstraintViolation);
+                                        result.Id = TYPENUMBER_CALLRESULT;
+                                        result.Message = baseResult.Confirmation20;
+                                        result.Success = false;
+                                        result.CallErrorMsg = replyMsg;
+                                        result.Exception = new Exception("Validate Failed");
+                                    }
+                                }
+                                else
+                                {
+                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
+                                   OCPPErrorDescription.OccurenceConstraintViolation);
+                                    result.Id = TYPENUMBER_CALLRESULT;
+                                    result.Message = baseResult.Confirmation20;
+                                    result.Success = false;
+                                    result.CallErrorMsg = replyMsg;
+                                    result.Exception = baseResult.Exception;
+                                }
+
+                            }
+                            break;
+                        case TYPENUMBER_CALLERROR:
+                            {
+                                result.Id = TYPENUMBER_CALLERROR;
+                                // var sentRequest = UnPackPayloadbyCallError(client.queue, msg.Id);
+
+                                //if (sentRequest != null)
+                                //{
+                                //    I20Request request = sentRequest as I20Request;
+                                //    result.Action = request.Action;
+
+                                //    result.Message = sentRequest;
+                                //    result.ReceivedErrorCode = string.Format("ErrorMsg {0}:{1}", ((CallErrorMessage)msg).ErrorCode, ((CallErrorMessage)msg).ErrorDescription);
+                                //}
+
+
+                            }
+                            break;
+                        default:
+                            break;
+
+                    }
+
+                    // if (msg != null) Console.WriteLine(string.Format("Receieved Message : {0}", msg.ToString()));
+
+                }
+            }
+            catch (Exception ex)
+            {
+                if (string.IsNullOrEmpty(result.UUID))
+                {
+                    result.UUID = data.Substring(4, 39);
+                    result.UUID = result.UUID.Split(new string[] { "\"," }, StringSplitOptions.None)[0];
+                }
+                result.Success = false;
+                result.Exception = ex;
+            }
+            return result;
+        }
+
+        #region 解析收到的訊息
+        /// <summary>
+        /// Parse data to OCPP Basic Message
+        /// </summary>
+        /// <param name="message"></param>
+        /// <returns></returns>
+        private BaseMessage Parse(string message)
+        {
+            try
+            {
+                if (message.StartsWith("[4,\""))
+                {
+                    message = message.Replace('{', '"');
+                    message = message.Replace('}', '"');
+                }
+                var array = JsonConvert.DeserializeObject<JArray>(message);
+                BaseMessage msg = null;
+                switch ((int)array[INDEX_MESSAGEID])
+                {
+                    case TYPENUMBER_CALL:
+                        {
+                            CallMessage call = new CallMessage();
+                            call.Action = array[INDEX_CALL_ACTION].ToString();
+                            call.Payload = array[INDEX_CALL_PAYLOAD].ToString().Replace("\r\n", "");
+                            msg = call;
+                        }
+                        break;
+                    case TYPENUMBER_CALLRESULT:
+                        {
+                            CallResultMessage callResult = new CallResultMessage();
+                            callResult.Payload = array[INDEX_CALLRESULT_PAYLOAD].ToString().Replace("\r\n", "");
+                            msg = callResult;
+                        }
+                        break;
+                    case TYPENUMBER_CALLERROR:
+                        {
+                            CallErrorMessage callError = new CallErrorMessage();
+                            callError.ErrorCode = array[INDEX_CALLERROR_ERRORCODE].ToString();
+                            callError.ErrorDescription = array[INDEX_CALLERROR_DESCRIPTION].ToString();
+                            callError.ErrorDetails = array[INDEX_CALLERROR_PAYLOAD].ToString().Replace("\r\n", "");
+                            msg = callError;
+                        }
+                        break;
+                    default:
+                        throw new Exception("Message Type notSupported");
+
+
+                }
+                msg.Id = array[INDEX_UNIQUEID].ToString();
+                return msg;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(string.Format("Parse Error=> {0} Problem: {0}", message, ex.Message));
+
+            }
+
+
+
+        }
+
+
+        private BasicMessageResult UnPackPayloadbyCall(string action, string payload)
+        {
+
+            BasicMessageResult result = new BasicMessageResult();
+            try
+            {
+
+                result.Request20 = (I20Request)(JsonConvert.DeserializeObject(payload, Type.GetType("EVCB_OCPP20.Packet.Messages." + action + "Request,EVCB_OCPP20.Packet")));
+
+            }
+            catch (Exception ex)
+            {
+                result.Exception = ex;
+                logger.LogError(string.Format("[{0}]UnPackPayloadbyCall Ex: {1}", action, ex.Message), "UnPack");
+
+            }
+
+            return result;
+
+
+        }
+
+        private BasicMessageResult UnPackPayloadbyCallResult(EVCB_OCPP20.Packet.Messages.Basic.Queue requestQueue, string uniqueId, string payload)
+        {
+            BasicMessageResult result = new BasicMessageResult();
+            try
+            {
+                I20Request request = requestQueue.RestoreRequest(uniqueId);
+                I20Confirmation confrim = JsonConvert.DeserializeObject(payload, Type.GetType("EVCB_OCPP20.Packet.Messages." + request.Action + "Response,EVCB_OCPP20.Packet")) as I20Confirmation;
+                confrim.SetRequest(request);
+                result.Confirmation20 = confrim;
+
+            }
+            catch (Exception ex)
+            {
+                result.Exception = ex;
+                logger.LogError(string.Format("UnPackPayloadbyCallResult Data:[{0},{1}] Ex: {2}", uniqueId, payload, ex.ToString()), "UnPack");
+
+            }
+            return result;
+
+        }
+
+
+
+        #endregion
+
+
+
+    }
+}

+ 90 - 90
EVCB_OCPP.WSServer/Message/RemoteTriggerHandler.cs

@@ -1,90 +1,90 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.RemoteTrigger;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    internal partial class ProfileHandler
-    {
-
-
-        internal async Task<MessageResult> ExecuteRemoteTriggerConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.TriggerMessage:
-                    {
-                        TriggerMessageConfirmation _confirm = confirm as TriggerMessageConfirmation;
-                        TriggerMessageRequest _request = _confirm.GetRequest() as TriggerMessageRequest;
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)_confirm.status;//OK
-                                operation.EvseValue = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
-                    }
-                    break;
-            }
-            return result;
-        }
-
-
-        internal async Task<MessageResult> ReceivedRemoteTriggerError(Actions action, string errorMsg, WsClientData session, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.TriggerMessage:
-                    {
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)255;//錯誤
-                                operation.EvseValue = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
-                    }
-                    break;
-            }
-            return result;
-
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.RemoteTrigger;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    internal partial class ProfileHandler
+    {
+
+
+        internal async Task<MessageResult> ExecuteRemoteTriggerConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.TriggerMessage:
+                    {
+                        TriggerMessageConfirmation _confirm = confirm as TriggerMessageConfirmation;
+                        TriggerMessageRequest _request = _confirm.GetRequest() as TriggerMessageRequest;
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)_confirm.status;//OK
+                                operation.EvseValue = _confirm.status.ToString();
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
+                    }
+                    break;
+            }
+            return result;
+        }
+
+
+        internal async Task<MessageResult> ReceivedRemoteTriggerError(Actions action, string errorMsg, WsClientData session, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.TriggerMessage:
+                    {
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)255;//錯誤
+                                operation.EvseValue = errorMsg;
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
+                    }
+                    break;
+            }
+            return result;
+
+        }
+    }
+}

+ 129 - 129
EVCB_OCPP.WSServer/Message/ReservationProfileHandler.cs

@@ -1,129 +1,129 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.Reservation;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    internal partial class ProfileHandler
-    {
-
-
-        internal async Task<MessageResult> ExecuteReservationConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.ReserveNow:
-                    {
-                        ReserveNowConfirmation _confirm = confirm as ReserveNowConfirmation;
-                        ReserveNowRequest _request = _confirm.GetRequest() as ReserveNowRequest;
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)_confirm.status;//OK
-                                operation.EvseValue = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                case Actions.CancelReservation:
-                    {
-                        CancelReservationConfirmation _confirm = confirm as CancelReservationConfirmation;
-                        CancelReservationRequest _request = _confirm.GetRequest() as CancelReservationRequest;
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)_confirm.status;//OK
-                                operation.EvseValue = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
-                    }
-                    break;
-            }
-            return result;
-        }
-
-
-        internal async Task<MessageResult> ExecuteReservationError(Actions action, string errorMsg, WsClientData session, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.ReserveNow:
-                    {
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)255;//錯誤
-                                operation.EvseValue = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                case Actions.CancelReservation:
-                    {
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)255;//錯誤
-                                operation.EvseValue = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
-                    }
-                    break;
-            }
-            return result;
-
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.Reservation;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    internal partial class ProfileHandler
+    {
+
+
+        internal async Task<MessageResult> ExecuteReservationConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.ReserveNow:
+                    {
+                        ReserveNowConfirmation _confirm = confirm as ReserveNowConfirmation;
+                        ReserveNowRequest _request = _confirm.GetRequest() as ReserveNowRequest;
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)_confirm.status;//OK
+                                operation.EvseValue = _confirm.status.ToString();
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                case Actions.CancelReservation:
+                    {
+                        CancelReservationConfirmation _confirm = confirm as CancelReservationConfirmation;
+                        CancelReservationRequest _request = _confirm.GetRequest() as CancelReservationRequest;
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)_confirm.status;//OK
+                                operation.EvseValue = _confirm.status.ToString();
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
+                    }
+                    break;
+            }
+            return result;
+        }
+
+
+        internal async Task<MessageResult> ExecuteReservationError(Actions action, string errorMsg, WsClientData session, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.ReserveNow:
+                    {
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)255;//錯誤
+                                operation.EvseValue = errorMsg;
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                case Actions.CancelReservation:
+                    {
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)255;//錯誤
+                                operation.EvseValue = errorMsg;
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+
+
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
+                    }
+                    break;
+            }
+            return result;
+
+        }
+    }
+}

+ 75 - 75
EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs

@@ -1,75 +1,75 @@
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-
-using System;
-using Microsoft.Extensions.Logging;
-using EVCB_OCPP.WSServer.Service.WsService;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    internal partial class ProfileHandler
-    {
-        internal MessageResult ExecuteSecurityRequest(Actions action, WsClientData session, IRequest request)
-        {
-            MessageResult result = new MessageResult() { Success = false };
-
-            try
-            {
-                switch (action)
-                {
-
-
-                    default:
-                        {
-                            logger.LogWarning(string.Format("Not Implement {0} Logic(ExecuteCoreRequest)", request.GetType().ToString().Replace("OCPPPackage.Messages.Core.", "")));
-                        }
-                        break;
-                }
-            }
-            catch (Exception ex)
-            {
-                logger.LogCritical("chargeBoxId:{0} {1}", session.ChargeBoxId, action);
-                logger.LogCritical("Data {0}", request.ToString());
-                logger.LogCritical("Error {0}", ex.ToString());
-                result.Exception = ex;
-            }
-
-
-
-            return result;
-        }
-        internal MessageResult ExecuteSecurityConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = false };
-
-            switch (action)
-            {
-
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
-                    }
-                    break;
-            }
-            return result;
-        }
-
-
-        internal MessageResult ReceivedSecurityError(Actions action, string errorMsg, WsClientData session, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
-                    }
-                    break;
-            }
-            return result;
-
-        }
-    }
-}
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+
+using System;
+using Microsoft.Extensions.Logging;
+using EVCB_OCPP.WSServer.Service.WsService;
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    internal partial class ProfileHandler
+    {
+        internal MessageResult ExecuteSecurityRequest(Actions action, WsClientData session, IRequest request)
+        {
+            MessageResult result = new MessageResult() { Success = false };
+
+            try
+            {
+                switch (action)
+                {
+
+
+                    default:
+                        {
+                            logger.LogWarning(string.Format("Not Implement {0} Logic(ExecuteCoreRequest)", request.GetType().ToString().Replace("OCPPPackage.Messages.Core.", "")));
+                        }
+                        break;
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.LogCritical("chargeBoxId:{0} {1}", session.ChargeBoxId, action);
+                logger.LogCritical("Data {0}", request.ToString());
+                logger.LogCritical("Error {0}", ex.ToString());
+                result.Exception = ex;
+            }
+
+
+
+            return result;
+        }
+        internal MessageResult ExecuteSecurityConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = false };
+
+            switch (action)
+            {
+
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
+                    }
+                    break;
+            }
+            return result;
+        }
+
+
+        internal MessageResult ReceivedSecurityError(Actions action, string errorMsg, WsClientData session, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
+                    }
+                    break;
+            }
+            return result;
+
+        }
+    }
+}

+ 190 - 190
EVCB_OCPP.WSServer/Message/SmartChargingProfileHandler.cs

@@ -1,190 +1,190 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.MainDb;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.SmartCharging;
-using EVCB_OCPP.Packet.Messages.SubTypes;
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    internal partial class ProfileHandler
-    {
-
-
-        internal async void SetChargingProfile(string chargeBoxId, decimal value, ChargingRateUnitType unit)
-        {
-            var _setProfileRequest = new SetChargingProfileRequest()
-            {
-                connectorId = 0,
-                csChargingProfiles = new Packet.Messages.SubTypes.csChargingProfiles()
-                {
-                    chargingProfileId = 1,
-                    chargingProfileKind = Packet.Messages.SubTypes.ChargingProfileKindType.Recurring,
-                    chargingProfilePurpose = Packet.Messages.SubTypes.ChargingProfilePurposeType.ChargePointMaxProfile,
-                    chargingSchedule = new Packet.Messages.SubTypes.ChargingSchedule()
-                    {
-                        chargingRateUnit = unit,
-                        chargingSchedulePeriod = new List<Packet.Messages.SubTypes.ChargingSchedulePeriod>()
-                                                    {
-                                                        new Packet.Messages.SubTypes.ChargingSchedulePeriod(){  startPeriod=0, limit=value}
-                                                    },
-                        duration = 86400,
-
-                    },
-                    recurrencyKind = Packet.Messages.SubTypes.RecurrencyKindType.Daily,
-                    stackLevel = 1,
-
-                }
-            };
-
-            await mainDbService.AddServerMessage(
-                ChargeBoxId: chargeBoxId,
-                OutAction: _setProfileRequest.Action.ToString(),
-                OutRequest: _setProfileRequest
-                );
-        }
-
-
-        internal async void ClearChargingProfile(string chargeBoxId)
-        {
-            var _clearProfileRequest = new ClearChargingProfileRequest()
-            {
-                connectorId = 0,
-                chargingProfilePurpose = ChargingProfilePurposeType.ChargePointMaxProfile,
-
-            };
-
-            await mainDbService.AddServerMessage(new ServerMessage()
-            {
-                ChargeBoxId = chargeBoxId,
-                OutAction = _clearProfileRequest.Action.ToString(),
-                OutRequest = JsonConvert.SerializeObject(_clearProfileRequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None })
-            });
-        }
-
-        internal async Task<MessageResult> ExecuteSmartChargingConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.ClearChargingProfile:
-
-                    {
-                        ClearChargingProfileConfirmation _confirm = confirm as ClearChargingProfileConfirmation;
-                        ClearChargingProfileRequest _request = _confirm.GetRequest() as ClearChargingProfileRequest;
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)_confirm.status;//OK
-                                operation.EvseValue = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                case Actions.SetChargingProfile:
-                    {
-                        SetChargingProfileConfirmation _confirm = confirm as SetChargingProfileConfirmation;
-                        SetChargingProfileRequest _request = _confirm.GetRequest() as SetChargingProfileRequest;
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
-                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)_confirm.status;//OK
-                                operation.EvseValue = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                case Actions.GetCompositeSchedule:
-                    {
-                        GetCompositeScheduleConfirmation _confirm = confirm as GetCompositeScheduleConfirmation;
-                        GetCompositeScheduleRequest _request = _confirm.GetRequest() as GetCompositeScheduleRequest;
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord
-                                .Where(x => x.SerialNo == requestId && x.ChargeBoxId == session.ChargeBoxId && x.Status == 0)
-                                .FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)_confirm.status;//OK
-                                operation.EvseValue = JsonConvert.SerializeObject(_confirm.chargingSchedule, Formatting.None);
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
-                    }
-                    break;
-            }
-            return result;
-        }
-
-
-        internal async Task<MessageResult> ReceivedSmartChargingError(Actions action, string errorMsg, WsClientData session, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-                case Actions.ClearChargingProfile:
-                case Actions.SetChargingProfile:
-                case Actions.GetCompositeSchedule:
-                    {
-                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                        {
-                            var operation = await db.MachineOperateRecord
-                                .Where(x => x.SerialNo == requestId && x.ChargeBoxId == session.ChargeBoxId && x.Status == 0)
-                                .FirstOrDefaultAsync();
-                            if (operation != null)
-                            {
-                                operation.FinishedOn = DateTime.UtcNow;
-                                operation.Status = 1;//電樁有回覆
-                                operation.EvseStatus = (int)255;//錯誤
-                                operation.EvseValue = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-                default:
-                    {
-                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
-                    }
-                    break;
-            }
-            return result;
-
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.MainDb;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.SmartCharging;
+using EVCB_OCPP.Packet.Messages.SubTypes;
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Message
+{
+    internal partial class ProfileHandler
+    {
+
+
+        internal async void SetChargingProfile(string chargeBoxId, decimal value, ChargingRateUnitType unit)
+        {
+            var _setProfileRequest = new SetChargingProfileRequest()
+            {
+                connectorId = 0,
+                csChargingProfiles = new Packet.Messages.SubTypes.csChargingProfiles()
+                {
+                    chargingProfileId = 1,
+                    chargingProfileKind = Packet.Messages.SubTypes.ChargingProfileKindType.Recurring,
+                    chargingProfilePurpose = Packet.Messages.SubTypes.ChargingProfilePurposeType.ChargePointMaxProfile,
+                    chargingSchedule = new Packet.Messages.SubTypes.ChargingSchedule()
+                    {
+                        chargingRateUnit = unit,
+                        chargingSchedulePeriod = new List<Packet.Messages.SubTypes.ChargingSchedulePeriod>()
+                                                    {
+                                                        new Packet.Messages.SubTypes.ChargingSchedulePeriod(){  startPeriod=0, limit=value}
+                                                    },
+                        duration = 86400,
+
+                    },
+                    recurrencyKind = Packet.Messages.SubTypes.RecurrencyKindType.Daily,
+                    stackLevel = 1,
+
+                }
+            };
+
+            await mainDbService.AddServerMessage(
+                ChargeBoxId: chargeBoxId,
+                OutAction: _setProfileRequest.Action.ToString(),
+                OutRequest: _setProfileRequest
+                );
+        }
+
+
+        internal async void ClearChargingProfile(string chargeBoxId)
+        {
+            var _clearProfileRequest = new ClearChargingProfileRequest()
+            {
+                connectorId = 0,
+                chargingProfilePurpose = ChargingProfilePurposeType.ChargePointMaxProfile,
+
+            };
+
+            await mainDbService.AddServerMessage(new ServerMessage()
+            {
+                ChargeBoxId = chargeBoxId,
+                OutAction = _clearProfileRequest.Action.ToString(),
+                OutRequest = JsonConvert.SerializeObject(_clearProfileRequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None })
+            });
+        }
+
+        internal async Task<MessageResult> ExecuteSmartChargingConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.ClearChargingProfile:
+
+                    {
+                        ClearChargingProfileConfirmation _confirm = confirm as ClearChargingProfileConfirmation;
+                        ClearChargingProfileRequest _request = _confirm.GetRequest() as ClearChargingProfileRequest;
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)_confirm.status;//OK
+                                operation.EvseValue = _confirm.status.ToString();
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                case Actions.SetChargingProfile:
+                    {
+                        SetChargingProfileConfirmation _confirm = confirm as SetChargingProfileConfirmation;
+                        SetChargingProfileRequest _request = _confirm.GetRequest() as SetChargingProfileRequest;
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)_confirm.status;//OK
+                                operation.EvseValue = _confirm.status.ToString();
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                case Actions.GetCompositeSchedule:
+                    {
+                        GetCompositeScheduleConfirmation _confirm = confirm as GetCompositeScheduleConfirmation;
+                        GetCompositeScheduleRequest _request = _confirm.GetRequest() as GetCompositeScheduleRequest;
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord
+                                .Where(x => x.SerialNo == requestId && x.ChargeBoxId == session.ChargeBoxId && x.Status == 0)
+                                .FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)_confirm.status;//OK
+                                operation.EvseValue = JsonConvert.SerializeObject(_confirm.chargingSchedule, Formatting.None);
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
+                    }
+                    break;
+            }
+            return result;
+        }
+
+
+        internal async Task<MessageResult> ReceivedSmartChargingError(Actions action, string errorMsg, WsClientData session, string requestId)
+        {
+            MessageResult result = new MessageResult() { Success = true };
+
+            switch (action)
+            {
+                case Actions.ClearChargingProfile:
+                case Actions.SetChargingProfile:
+                case Actions.GetCompositeSchedule:
+                    {
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+                        {
+                            var operation = await db.MachineOperateRecord
+                                .Where(x => x.SerialNo == requestId && x.ChargeBoxId == session.ChargeBoxId && x.Status == 0)
+                                .FirstOrDefaultAsync();
+                            if (operation != null)
+                            {
+                                operation.FinishedOn = DateTime.UtcNow;
+                                operation.Status = 1;//電樁有回覆
+                                operation.EvseStatus = (int)255;//錯誤
+                                operation.EvseValue = errorMsg;
+                                await db.SaveChangesAsync();
+                            }
+
+                        }
+                    }
+                    break;
+
+                default:
+                    {
+                        logger.LogWarning(string.Format("Not Implement {0} Logic", action));
+                    }
+                    break;
+            }
+            return result;
+
+        }
+    }
+}

+ 96 - 96
EVCB_OCPP.WSServer/Program.cs

@@ -1,96 +1,96 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.WSServer.Message;
-using EVCB_OCPP.WSServer.Service;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using System;
-using NLog;
-using NLog.Web;
-using NLog.Extensions.Logging;
-using System.IO;
-using System.Data.Common;
-using Microsoft.Data.SqlClient;
-using EVCB_OCPP.WSServer.Helper;
-using Quartz;
-using EVCB_OCPP.WSServer.Jobs;
-using Microsoft.AspNetCore.Builder;
-using EVCB_OCPP.WSServer.Service.WsService;
-using EVCB_OCPP.Service;
-
-namespace EVCB_OCPP.WSServer
-{
-   
-    class Program
-    {
-        static void Main(string[] args)
-        {
-            AppContext.SetData("System.Net.Security.TlsCacheSize", 200);
-
-            Console.WriteLine("====================================================================================================");
-            Console.WriteLine("=================" +
-                "===================================================================================");
-            Console.WriteLine("==                                                                                                ==");
-            Console.WriteLine("==       ------------               -----------      -------------         -------------          ==");
-            Console.WriteLine("==    ---            ---       ----                  ----------------      ----------------       ==");
-            Console.WriteLine("==    ---            ---     ----                    ----            ---   ----            ---    ==");
-            Console.WriteLine("==    ---            ---    ----                     ----            ---   ----            ---    ==");
-            Console.WriteLine("==    ---            ---    ----                     ---- -------------    ---- -------------     ==");
-            Console.WriteLine("==    ---            ---    ----                     ---- -----------      ---- -----------       ==");
-            Console.WriteLine("==    ---            ---      ----                   ----                  ----                   ==");
-            Console.WriteLine("==    ---            ---        ----                 ----                  ----                   ==");
-            Console.WriteLine("==       -----------                -----------      ----                  ----                   ==");
-            Console.WriteLine("==                                                                                                ==");
-            Console.WriteLine("====================================================================================================");
-            Console.WriteLine("====================================================================================================");
-
-            ThreadPool.GetMaxThreads(out var workerThreads, out var completionThreads);
-            Console.WriteLine($"Max ThreadPool workerThreads:{workerThreads} completionThreads:{completionThreads}");
-            ThreadPool.SetMinThreads((int)(10), (int)(0));
-
-            var builder = WebApplication.CreateBuilder(args);
-            //IHost host = Host.CreateDefaultBuilder(args)
-            //.UseEnvironment("Development")
-            //.ConfigureLogging((context, builder) => { 
-            //    builder.ClearProviders();
-            //    builder.AddAzureWebAppDiagnostics();
-            //    NLog.LogManager.Configuration = new NLogLoggingConfiguration(context.Configuration.GetSection("NLog"));
-            //})
-            //.UseNLog()
-            builder.Host
-                .AddLogServcie()
-                .ConfigureServices((hostContext, services) =>
-                {
-                    //services.AddSingleton<MeterValueGroupSingleHandler>();
-                    //services.AddSingleton<IHostLifetime, DummyHostLifeTime>();
-
-                    services.AddProtalServer(hostContext.Configuration);
-
-                    //services.AddTransient<BlockingTreePrintService>();
-                    //services.AddTransient<GoogleGetTimePrintService>();
-                });
-                //.Build();
-            var app = builder.Build();
-            app.UseHeaderRecordService();
-            app.UseOcppWsService();
-            app.MapApiServce();
-            app.InitStationConfigService();
-            app.Run();
-            //host.Run();
-        }
-
-        public static object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
-        {
-            DateTime? timevalue = null;
-            if (reader.Value != null)
-            {
-                DateTime date = ((DateTime)reader.Value).ToLocalTime();
-                timevalue = new DateTime(date.Year, date.Month, date.Day, date.TimeOfDay.Hours, date.TimeOfDay.Minutes, date.TimeOfDay.Seconds, 000);
-            }
-            return timevalue;
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.WSServer.Message;
+using EVCB_OCPP.WSServer.Service;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using System;
+using NLog;
+using NLog.Web;
+using NLog.Extensions.Logging;
+using System.IO;
+using System.Data.Common;
+using Microsoft.Data.SqlClient;
+using EVCB_OCPP.WSServer.Helper;
+using Quartz;
+using EVCB_OCPP.WSServer.Jobs;
+using Microsoft.AspNetCore.Builder;
+using EVCB_OCPP.WSServer.Service.WsService;
+using EVCB_OCPP.Service;
+
+namespace EVCB_OCPP.WSServer
+{
+   
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            AppContext.SetData("System.Net.Security.TlsCacheSize", 200);
+
+            Console.WriteLine("====================================================================================================");
+            Console.WriteLine("=================" +
+                "===================================================================================");
+            Console.WriteLine("==                                                                                                ==");
+            Console.WriteLine("==       ------------               -----------      -------------         -------------          ==");
+            Console.WriteLine("==    ---            ---       ----                  ----------------      ----------------       ==");
+            Console.WriteLine("==    ---            ---     ----                    ----            ---   ----            ---    ==");
+            Console.WriteLine("==    ---            ---    ----                     ----            ---   ----            ---    ==");
+            Console.WriteLine("==    ---            ---    ----                     ---- -------------    ---- -------------     ==");
+            Console.WriteLine("==    ---            ---    ----                     ---- -----------      ---- -----------       ==");
+            Console.WriteLine("==    ---            ---      ----                   ----                  ----                   ==");
+            Console.WriteLine("==    ---            ---        ----                 ----                  ----                   ==");
+            Console.WriteLine("==       -----------                -----------      ----                  ----                   ==");
+            Console.WriteLine("==                                                                                                ==");
+            Console.WriteLine("====================================================================================================");
+            Console.WriteLine("====================================================================================================");
+
+            ThreadPool.GetMaxThreads(out var workerThreads, out var completionThreads);
+            Console.WriteLine($"Max ThreadPool workerThreads:{workerThreads} completionThreads:{completionThreads}");
+            ThreadPool.SetMinThreads((int)(10), (int)(0));
+
+            var builder = WebApplication.CreateBuilder(args);
+            //IHost host = Host.CreateDefaultBuilder(args)
+            //.UseEnvironment("Development")
+            //.ConfigureLogging((context, builder) => { 
+            //    builder.ClearProviders();
+            //    builder.AddAzureWebAppDiagnostics();
+            //    NLog.LogManager.Configuration = new NLogLoggingConfiguration(context.Configuration.GetSection("NLog"));
+            //})
+            //.UseNLog()
+            builder.Host
+                .AddLogServcie()
+                .ConfigureServices((hostContext, services) =>
+                {
+                    //services.AddSingleton<MeterValueGroupSingleHandler>();
+                    //services.AddSingleton<IHostLifetime, DummyHostLifeTime>();
+
+                    services.AddProtalServer(hostContext.Configuration);
+
+                    //services.AddTransient<BlockingTreePrintService>();
+                    //services.AddTransient<GoogleGetTimePrintService>();
+                });
+                //.Build();
+            var app = builder.Build();
+            app.UseHeaderRecordService();
+            app.UseOcppWsService();
+            app.MapApiServce();
+            app.InitStationConfigService();
+            app.Run();
+            //host.Run();
+        }
+
+        public static object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+        {
+            DateTime? timevalue = null;
+            if (reader.Value != null)
+            {
+                DateTime date = ((DateTime)reader.Value).ToLocalTime();
+                timevalue = new DateTime(date.Year, date.Month, date.Day, date.TimeOfDay.Hours, date.TimeOfDay.Minutes, date.TimeOfDay.Seconds, 000);
+            }
+            return timevalue;
+        }
+    }
+}

+ 12 - 12
EVCB_OCPP.WSServer/Properties/PublishProfiles/FolderProfile1.pubxml

@@ -1,13 +1,13 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-https://go.microsoft.com/fwlink/?LinkID=208121.
--->
-<Project>
-  <PropertyGroup>
-    <Configuration>Release</Configuration>
-    <Platform>Any CPU</Platform>
-    <PublishDir>bin\Release\net7.0\publish_dll\</PublishDir>
-    <PublishProtocol>FileSystem</PublishProtocol>
-    <_TargetId>Folder</_TargetId>
-  </PropertyGroup>
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <Configuration>Release</Configuration>
+    <Platform>Any CPU</Platform>
+    <PublishDir>bin\Release\net7.0\publish_dll\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+  </PropertyGroup>
 </Project>

+ 1194 - 1194
EVCB_OCPP.WSServer/ProtalServer.cs

@@ -1,1195 +1,1195 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.Basic;
-using EVCB_OCPP.Packet.Messages.Core;
-using EVCB_OCPP.WSServer.Dto;
-using EVCB_OCPP.WSServer.Helper;
-using EVCB_OCPP.WSServer.Message;
-using EVCB_OCPP.WSServer.Service;
-using Microsoft.Extensions.Hosting;
-using Newtonsoft.Json;
-
-
-using System.Data;
-using System.Diagnostics;
-using System.Security.Authentication;
-using System.Xml.Linq;
-using NLog;
-using Microsoft.Extensions.Configuration;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.AspNetCore.Builder;
-using NLog.Extensions.Logging;
-using Microsoft.Data.SqlClient;
-using System.Collections.Concurrent;
-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
-{
-    public class DestroyRequest : IRequest
-    {
-        public string Action { get; set; }
-
-        public bool TransactionRelated()
-        {
-            return false;
-        }
-
-        public bool Validate()
-        {
-            return true;
-        }
-    }
-
-    public class ProtalServer : IHostedService
-    {
-        //static private ILogger logger = NLog.LogManager.GetCurrentClassLogger();
-
-        public ProtalServer(
-            ILogger<ProtalServer> logger
-            , IConfiguration configuration
-            //, IDbContextFactory<MainDBContext> maindbContextFactory
-            , IMainDbService mainDbService
-            //, IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory
-            , ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory
-            , ISqlConnectionFactory<MainDBContext> mainDbConnectionFactory
-            , IHostEnvironment environment
-            //, IOCPPWSServerFactory ocppWSServerFactory
-            , IConnectionLogdbService connectionLogdbService
-            , WebDbService webDbService
-            , ServerMessageService serverMessageService
-            , IServiceProvider serviceProvider
-            , OcppWebsocketService websocketService
-            , ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice
-            //, StationConfigService stationConfigService
-            , OuterHttpClient httpClient)
-        {
-            _ct = _cts.Token;
-            this.logger = logger;
-            this.configuration = configuration;
-            //this.maindbContextFactory = maindbContextFactory;
-            this.mainDbService = mainDbService;
-            //this.webDbConnectionFactory = webDbConnectionFactory;
-            //this.connectionLogdbContextFactory = connectionLogdbContextFactory;
-            //this.ocppWSServerFactory = ocppWSServerFactory;
-            this.connectionLogdbService = connectionLogdbService;
-            this.webDbService = webDbService;
-            this.messageService = serverMessageService;
-            this.websocketService = websocketService;
-            this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
-            this.serviceProvider = serviceProvider;
-            //this.stationConfigService = stationConfigService;
-            this.httpClient = httpClient;
-            isInDocker = !string.IsNullOrEmpty(configuration["DOTNET_RUNNING_IN_CONTAINER"]);
-
-            // = configuration.GetConnectionString("WebDBContext");
-            this.profileHandler = serviceProvider.GetService<ProfileHandler>();// new ProfileHandler(configuration, serviceProvider);
-            _loadingBalanceService = new LoadingBalanceService(mainDbConnectionFactory, webDbConnectionFactory);
-
-            WarmUpLog();
-        }
-
-        #region private fields
-        private OuterHttpClient httpClient;
-        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 ILogger<ProtalServer> logger;
-        private readonly IConfiguration configuration;
-        private readonly IServiceProvider serviceProvider;
-        //private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
-        private readonly IMainDbService mainDbService;
-        //private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
-
-        //private readonly IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory;
-        //private readonly IOCPPWSServerFactory ocppWSServerFactory;
-        private readonly IConnectionLogdbService connectionLogdbService;
-        private readonly WebDbService webDbService;
-        private readonly ServerMessageService messageService;
-        private readonly OcppWebsocketService websocketService;
-        private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
-        //private readonly StationConfigService stationConfigService;
-        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 DateTime checkUpdateDt = DateTime.UtcNow;
-        private DateTime _CheckFeeDt = DateTime.UtcNow;
-        private DateTime _CheckLBDt = DateTime.UtcNow;
-        private DateTime _CheckDenyListDt = DateTime.UtcNow.AddDays(-1);
-        private readonly LoadingBalanceService _loadingBalanceService;// = new LoadingBalanceService();
-        private List<StationInfoDto> _StationInfo = new List<StationInfoDto>();
-
-        private 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"
-        };
-        private readonly List<Profile> profiles = new List<Profile>()
-        {
-             new CoreProfile(),
-             new FirmwareManagementProfile(),
-             new ReservationProfile(),
-             new RemoteTriggerProfile(),
-             new SmartChargingProfile(),
-             new LocalAuthListManagementProfile(),
-             new SecurityProfile(),
-        };
-        private CancellationTokenSource _cts = new CancellationTokenSource();
-        private CancellationToken _ct;
-        private Semaphore bootSemaphore = new Semaphore(10, 10);
-        #endregion
-
-        internal Dictionary<string, WsClientData> GetClientDic()
-        {
-            Dictionary<string, WsClientData> toReturn = null;
-            toReturn = new Dictionary<string, WsClientData>(clientDic);
-            return toReturn;
-        }
-
-        internal IReadOnlyList<Profile> Profiles => profiles.AsReadOnly();
-        internal LoadingBalanceService LoadingBalanceService => _loadingBalanceService;
-        internal ProfileHandler ProfileHandler => profileHandler;
-
-        internal readonly List<Func<WsClientData, CancellationToken, Task>> InitActions = new List<Func<WsClientData, CancellationToken, Task>>();
-
-        public async Task StartAsync(CancellationToken cancellationToken)
-        {
-            GlobalConfig.DenyModelNames = await webDbService.GetDenyModelNames(cancellationToken);
-            Start();
-            return;
-        }
-
-        public Task StopAsync(CancellationToken cancellationToken)
-        {
-            return Task.CompletedTask;
-        }
-
-        internal void UpdateClientDisplayPrice(string key,string price)
-        {
-            clientDic[key].DisplayPrice = price;
-        }
-
-        internal void SendMsg(WsClientData session, string msg, string messageType, string errorMsg = "")
-        {
-            Send(session,msg,messageType,errorMsg);
-        }
-
-        internal void Start()
-        {
-            Console.WriteLine("Starting Server...");
-
-            if (!GlobalConfig.LoadAPPConfig(configuration))
-            {
-                Console.WriteLine("Please check App.Config setting .");
-                return;
-            }
-
-            StartWsService();
-            //OpenNetwork();
-
-
-            //RunHttpConsoleService();
-            return;
-            if (!isInDocker)
-            {
-                Task consoleReadTask = new Task(RunConsoleInteractive);
-                consoleReadTask.Start();
-                //RunConsoleInteractive();
-                return;
-            }
-        }
-
-        private void StartWsService()
-        {
-            websocketService.NewSessionConnected += AppServer_NewSessionConnected;
-        }
-        private void StopWsService()
-        {
-            websocketService.NewSessionConnected -= AppServer_NewSessionConnected;
-        }
-
-        private async void AppServer_NewSessionConnected(object sender, WsClientData session)
-        {
-            logger.LogDebug(string.Format("{0} NewSessionConnected", session.Path));
-
-            try
-            {
-                bool isNotSupported = session.SecWebSocketProtocol.Contains("ocpp1.6") ? false : session.SecWebSocketProtocol.Contains("ocpp2.0") ? false : true;
-                if (isNotSupported)
-                {
-                    //logger.LogDebug(string.Format("ChargeBoxId:{0} SecWebSocketProtocol:{1} NotSupported", session.ChargeBoxId, session.SecWebSocketProtocol));
-                    WriteMachineLog(session, string.Format("SecWebSocketProtocol:{0} NotSupported", session.SecWebSocketProtocol), "Connection", "");
-                    return;
-                }
-
-                TryRemoveDuplicatedSession(session);
-                clientDic[session.ChargeBoxId] = session;
-
-                session.SessionClosed += AppServer_SessionClosed;
-                session.m_ReceiveData += ReceivedMessageTimeLimited;
-                // logger.LogDebug("------------New " + (session == null ? "Oops" : session.ChargeBoxId));
-                WriteMachineLog(session, "NewSessionConnected", "Connection", "");
-
-                await mainDbService.UpdateMachineConnectionType(session.ChargeBoxId, session.UriScheme.Contains("wss") ? 2 : 1);
-
-            }
-            catch (Exception ex)
-            {
-                logger.LogError(string.Format("NewSessionConnected Ex: {0}", ex.ToString()));
-            }
-        }
-
-        private void AppServer_SessionClosed(object sender, string closeReason)
-        {
-            if (sender is not WsClientData session)
-            {
-                return;
-            }
-
-            //session.SessionClosed -= AppServer_SessionClosed;
-            //session.m_ReceiveData -= ReceivedMessageTimeLimited;
-
-            //WriteMachineLog(session, string.Format("CloseReason: {0}", closeReason), "Connection", "");
-            RemoveClient(session, closeReason);
-        }
-
-        private void TryRemoveDuplicatedSession(WsClientData session)
-        {
-            if (clientDic.ContainsKey(session.ChargeBoxId))
-            {
-                var oldSession = clientDic[session.ChargeBoxId];
-                //WriteMachineLog(oldSession, "Duplicate Logins", "Connection", "");
-                RemoveClient(oldSession, "Duplicate Logins");
-            }
-        }
-
-        private void RunConsoleInteractive()
-        {
-            while (true)
-            {
-                if (Console.In is null)
-                {
-                    break;
-                }
-
-                var input = Console.ReadLine();
-
-                switch (input.ToLower())
-                {
-                    case "stop":
-                        Console.WriteLine("Command stop");
-                        Stop();
-                        break;
-
-                    case "gc":
-                        Console.WriteLine("Command GC");
-                        GC.Collect();
-                        break;
-                    case "lc":
-                        {
-                            Console.WriteLine("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)
-                            {
-                                Console.WriteLine(i + ":" + c.ChargeBoxId + " " + c.SessionID);
-                                i++;
-                            }
-                        }
-                        break;
-                    case "lcn":
-                        {
-                            Console.WriteLine("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)
-                            {
-                                Console.WriteLine(iLcn + ":" + c + ":" + clientDic.Where(z => z.Value.CustomerName == c).Count().ToString());
-                                iLcn++;
-                            }
-
-                        }
-                        break;
-                    case "help":
-                        Console.WriteLine("Command help!!");
-                        Console.WriteLine("lcn : List Customer Name");
-                        Console.WriteLine("gc : GC Collect");
-                        Console.WriteLine("lc : List Clients");
-                        Console.WriteLine("cls : clear console");
-                        Console.WriteLine("silent : silent");
-                        Console.WriteLine("show : show log");
-                        // logger.Info("rcl : show Real Connection Limit");
-                        break;
-                    case "cls":
-                        Console.WriteLine("Command clear");
-                        Console.Clear();
-                        break;
-
-                    case "silent":
-                        Console.WriteLine("Command silent");
-                        //var xe = XElement.Load("NLog.config");
-                        //var xns = xe.GetDefaultNamespace();
-                        //var minlevelattr = xe.Descendants(xns + "rules").Elements(xns + "logger")
-                        //    .Where(c => c.Attribute("writeTo").Value.Equals("console")).Attributes("minlevel").FirstOrDefault();
-                        //if (minlevelattr != null)
-                        //{
-
-                        //    minlevelattr.Value = "Warn";
-                        //}
-                        //xe.Save("NLog.config");
-                        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);
-                            }
-                        }
-                        break;
-                    case "show":
-                        Console.WriteLine("Command show");
-                        //var xe1 = XElement.Load("NLog.config");
-                        //var xns1 = xe1.GetDefaultNamespace();
-                        //var minlevelattr1 = xe1.Descendants(xns1 + "rules").Elements(xns1 + "logger")
-                        //    .Where(c => c.Attribute("writeTo").Value.Equals("console")).Attributes("minlevel").FirstOrDefault();
-                        //if (minlevelattr1 != null)
-                        //{
-
-                        //    minlevelattr1.Value = "trace";
-                        //}
-                        //xe1.Save("NLog.config");
-                        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);
-                            }
-                        }
-                        break;
-                    case "rcl":
-                        break;
-                    default:
-                        break;
-                }
-            }
-        }
-
-        internal void Stop()
-        {
-            _cts?.Cancel();
-        }
-
-        async private void ReceivedMessageTimeLimited(object sender, string rawdata)
-        {
-            if (sender is not WsClientData session)
-            {
-                return;
-            }
-            CancellationTokenSource tokenSource = new();
-            var task = ReceivedMessage(session, rawdata);
-            var completedTask = await Task.WhenAny(task, Task.Delay(90_000, tokenSource.Token));
-
-            if (completedTask != task)
-            {
-                logger.LogCritical("Process timeout: {0} ", rawdata);
-                await task;
-                return;
-            }
-
-            tokenSource.Cancel();
-            return;
-        }
-
-        async private Task ReceivedMessage(WsClientData session, string rawdata)
-        {
-            try
-            {
-                BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-                MessageResult analysisResult = msgAnalyser.AnalysisReceiveData(session, rawdata);
-
-                WriteMachineLog(session, rawdata,
-                     string.Format("{0} {1}", string.IsNullOrEmpty(analysisResult.Action) ? "unknown" : analysisResult.Action, analysisResult.Id == 2 ? "Request" : (analysisResult.Id == 3 ? "Confirmation" : "Error")), analysisResult.Exception == null ? "" : analysisResult.Exception.Message);
-
-                if (session.ResetSecurityProfile)
-                {
-                    logger.LogError(string.Format("[{0}] ChargeBoxId:{1} ResetSecurityProfile", DateTime.UtcNow, session.ChargeBoxId));
-                    RemoveClient(session, "ResetSecurityProfile");
-                    return;
-                }
-
-
-                if (!analysisResult.Success)
-                {
-                    //解析RawData就發生錯誤
-                    if (!string.IsNullOrEmpty(analysisResult.CallErrorMsg))
-                    {
-                        Send(session, analysisResult.CallErrorMsg, string.Format("{0} {1}", analysisResult.Action, "Error"));
-                    }
-                    else
-                    {
-                        if (analysisResult.Message == null)
-                        {
-                            string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                            string errorMsg = string.Empty;
-                            if (analysisResult.Exception != null)
-                            {
-                                errorMsg = analysisResult.Exception.ToString();
-                            }
-
-                            Send(session, replyMsg, string.Format("{0} {1}", "unknown", "Error"), "EVSE's sent essage has parsed Failed. ");
-                        }
-                        else
-                        {
-                            BaseMessage _baseMsg = analysisResult.Message as BaseMessage;
-
-
-                            string replyMsg = BasicMessageHandler.GenerateCallError(_baseMsg.Id, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                            string errorMsg = string.Empty;
-                            if (analysisResult.Exception != null)
-                            {
-                                errorMsg = analysisResult.Exception.ToString();
-                            }
-
-                            Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-                        }
-
-                    }
-                }
-                else
-                {
-                    switch (analysisResult.Id)
-                    {
-                        case BasicMessageHandler.TYPENUMBER_CALL:
-                            {
-                                if (!session.ISOCPP20)
-                                {
-                                    Actions action = Convertor.GetAction(analysisResult.Action);
-                                    try
-                                    {
-                                        await ProcessRequestMessage(analysisResult, session, action);
-                                    }
-                                    catch (Exception e)
-                                    {
-                                        logger.LogError($"Processing {action} exception!");
-                                        throw;
-                                    }
-                                }
-                                else
-                                {
-                                    EVCB_OCPP20.Packet.Features.Actions action = Convertor.GetActionby20(analysisResult.Action);
-                                    MessageResult result = new MessageResult() { Success = true };
-                                    //ocpp20 處理
-                                    switch (action)
-                                    {
-
-                                        case EVCB_OCPP20.Packet.Features.Actions.BootNotification:
-                                            {
-                                                EVCB_OCPP20.Packet.Messages.BootNotificationRequest _request = (EVCB_OCPP20.Packet.Messages.IRequest)analysisResult.Message as EVCB_OCPP20.Packet.Messages.BootNotificationRequest;
-
-                                                var confirm = new EVCB_OCPP20.Packet.Messages.BootNotificationResponse() { CurrentTime = DateTime.UtcNow, Interval = 180, Status = EVCB_OCPP20.Packet.DataTypes.EnumTypes.RegistrationStatusEnumType.Pending };
-
-                                                result.Message = confirm;
-                                                result.Success = true;
-
-                                                string response = BasicMessageHandler.GenerateConfirmationofOCPP20(analysisResult.UUID, (EVCB_OCPP20.Packet.Messages.IConfirmation)result.Message);
-                                                Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Response"), result.Exception == null ? string.Empty : result.Exception.ToString());
-
-
-                                                var request = new EVCB_OCPP20.Packet.Messages.SetNetworkProfileRequest()
-                                                {
-                                                    ConfigurationSlot = 1,
-                                                    ConnectionData = new EVCB_OCPP20.Packet.DataTypes.NetworkConnectionProfileType()
-                                                    {
-                                                        OcppVersion = EVCB_OCPP20.Packet.DataTypes.EnumTypes.OCPPVersionEnumType.OCPP20,
-                                                        OcppTransport = EVCB_OCPP20.Packet.DataTypes.EnumTypes.OCPPTransportEnumType.JSON,
-                                                        MessageTimeout = 30,
-                                                        OcppCsmsUrl = session.UriScheme == "ws" ? GlobalConfig.OCPP20_WSUrl : GlobalConfig.OCPP20_WSSUrl,
-                                                        OcppInterface = EVCB_OCPP20.Packet.DataTypes.EnumTypes.OCPPInterfaceEnumType.Wired0
-                                                    }
-
-                                                };
-                                                var uuid = session.queue20.store(request);
-                                                string requestText = BasicMessageHandler.GenerateRequestofOCPP20(uuid, "SetNetworkProfile", request);
-                                                Send(session, requestText, "SetNetworkProfile");
-
-                                            }
-                                            break;
-                                        default:
-                                            {
-                                                logger.LogError(string.Format("We don't implement messagetype:{0} of raw data :{1} by {2}", analysisResult.Id, rawdata, session.ChargeBoxId));
-                                            }
-                                            break;
-                                    }
-
-                                }
-                            }
-                            break;
-                        case BasicMessageHandler.TYPENUMBER_CALLRESULT:
-                            {
-                                if (!session.ISOCPP20)
-                                {
-                                    Actions action = Convertor.GetAction(analysisResult.Action);
-                                    ProcessConfirmationMessage(analysisResult, session, action);
-                                }
-                                else
-                                {
-                                    EVCB_OCPP20.Packet.Features.Actions action = Convertor.GetActionby20(analysisResult.Action);
-                                    MessageResult result = new MessageResult() { Success = true };
-                                    //ocpp20 處理
-                                    switch (action)
-                                    {
-
-                                        case EVCB_OCPP20.Packet.Features.Actions.SetNetworkProfile:
-                                            {
-                                                EVCB_OCPP20.Packet.Messages.SetNetworkProfileResponse response = (EVCB_OCPP20.Packet.Messages.IConfirmation)analysisResult.Message as EVCB_OCPP20.Packet.Messages.SetNetworkProfileResponse;
-
-                                                if (response.Status == EVCB_OCPP20.Packet.DataTypes.EnumTypes.SetNetworkProfileStatusEnumType.Accepted)
-                                                {
-                                                    var request = new EVCB_OCPP20.Packet.Messages.SetVariablesRequest()
-                                                    {
-                                                        SetVariableData = new List<EVCB_OCPP20.Packet.DataTypes.SetVariableDataType>()
-                                                         {
-                                                              new EVCB_OCPP20.Packet.DataTypes.SetVariableDataType()
-                                                              {
-                                                                    Component=new EVCB_OCPP20.Packet.DataTypes.ComponentType()
-                                                                    {
-                                                                         Name="OCPPCommCtrlr",
-
-                                                                    },
-                                                                     AttributeType= EVCB_OCPP20.Packet.DataTypes.EnumTypes.AttributeEnumType.Actual,
-                                                                     AttributeValue= JsonConvert.SerializeObject(new List<int>(){ 1 }),
-                                                                     Variable=new EVCB_OCPP20.Packet.DataTypes.VariableType()
-                                                                    {
-                                                                            Name="NetworkConfigurationPriority",
-
-                                                                    }
-
-
-                                                              }
-                                                         }
-
-                                                    };
-                                                    var uuid = session.queue20.store(request);
-                                                    string requestText = BasicMessageHandler.GenerateRequestofOCPP20(uuid, "SetVariables", request);
-                                                    Send(session, requestText, "SetVariables");
-                                                }
-
-                                            }
-                                            break;
-                                        case EVCB_OCPP20.Packet.Features.Actions.SetVariables:
-                                            {
-                                                EVCB_OCPP20.Packet.Messages.SetVariablesResponse response = (EVCB_OCPP20.Packet.Messages.IConfirmation)analysisResult.Message as EVCB_OCPP20.Packet.Messages.SetVariablesResponse;
-
-                                                if (response.SetVariableResult[0].AttributeStatus == EVCB_OCPP20.Packet.DataTypes.EnumTypes.SetVariableStatusEnumType.RebootRequired)
-                                                {
-                                                    var request = new EVCB_OCPP20.Packet.Messages.ResetRequest()
-                                                    {
-                                                        Type = EVCB_OCPP20.Packet.DataTypes.EnumTypes.ResetEnumType.OnIdle
-
-                                                    };
-                                                    var uuid = session.queue20.store(request);
-                                                    string requestText = BasicMessageHandler.GenerateRequestofOCPP20(uuid, "Reset", request);
-                                                    Send(session, requestText, "Reset");
-
-                                                }
-                                            }
-                                            break;
-                                        default:
-                                            {
-                                                logger.LogError(string.Format("We don't implement messagetype:{0} of raw data :{1} by {2}", analysisResult.Id, rawdata, session.ChargeBoxId));
-                                            }
-                                            break;
-                                    }
-                                }
-
-                            }
-                            break;
-                        case BasicMessageHandler.TYPENUMBER_CALLERROR:
-                            {
-                                //只處理 丟出Request 收到Error的訊息                              
-                                if (analysisResult.Success && analysisResult.Message != null)
-                                {
-                                    Actions action = Convertor.GetAction(analysisResult.Action);
-                                    ProcessErrorMessage(analysisResult, session, action);
-                                }
-
-                            }
-                            break;
-                        default:
-                            {
-                                logger.LogError(string.Format("Can't analyze messagetype:{0} of raw data :{1} by {2}", analysisResult.Id, rawdata, session.ChargeBoxId));
-                            }
-                            break;
-
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-
-
-                if (ex.InnerException != null)
-                {
-                    logger.LogError(string.Format("{0} **Inner Exception :{1} ", session.ChargeBoxId + rawdata, ex.ToString()));
-
-
-                }
-                else
-                {
-                    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)
-        {
-            Stopwatch outter_stopwatch = Stopwatch.StartNew();
-            //BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-            if (!session.IsCheckIn && action != Actions.BootNotification)
-            {
-                if (analysisResult.Message is IRequest request && !request.TransactionRelated())
-                {
-                    string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.GenericError, OCPPErrorDescription.NotChecked);
-                    Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Error"));
-                }
-            }
-            else
-            {
-                var profileName = profiles.Where(x => x.IsExisted(analysisResult.Action)).Select(x => x.Name).FirstOrDefault();
-                switch (profileName)
-                {
-                    case "Core":
-                        {
-                            var replyResult = await profileHandler.ExecuteCoreRequest(action, session, (IRequest)analysisResult.Message).ConfigureAwait(false);
-
-                            var sendTimer = Stopwatch.StartNew();
-                            if (replyResult.Success)
-                            {
-                                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 bootNotificationConfirmation &&
-                                    analysisResult.Message is BootNotificationRequest bootNotificationRequest)
-                                {
-                                    session.ChargePointVendor = bootNotificationRequest.chargePointVendor;
-
-                                    if (session.BootStatus == BootStatus.Startup
-                                        //&& bootSemaphore.WaitOne(0)
-                                        )
-                                    {
-                                        session.BootStatus = BootStatus.Initializing;
-                                        session.AddTask(StartInitializeEVSE(session));
-                                    }
-
-                                    if (bootNotificationConfirmation.status == Packet.Messages.SubTypes.RegistrationStatus.Accepted)
-                                    {
-                                        session.IsCheckIn = true;
-
-                                        var sendTask = async () => await messageService.SendDataTransferRequest(
-                                            session.ChargeBoxId,
-                                            messageId: "ID_FirmwareVersion",
-                                            vendorId: "Phihong Technology",
-                                            data: string.Empty);
-                                        await sendTask();
-                                        //await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, session.DisconnetCancellationToken);
-                                    }
-                                }
-
-                                if (action == Actions.Authorize && replyResult.Message is AuthorizeConfirmation)
-                                {
-                                    var authorizeRequest = (IRequest)analysisResult.Message as AuthorizeRequest;
-                                    if (session.UserDisplayPrices.ContainsKey(authorizeRequest.idTag))
-                                    {
-                                        await messageService.SendDataTransferRequest(
-                                            session.ChargeBoxId,
-                                            messageId: "SetUserPrice",
-                                            vendorId: "Phihong Technology",
-                                            data: JsonConvert.SerializeObject(
-                                                new
-                                                {
-                                                    idToken = authorizeRequest.idTag,
-                                                    price = session.UserDisplayPrices[authorizeRequest.idTag]
-                                                })
-                                            );
-                                    }
-                                }
-
-                            }
-                            else
-                            {
-                                if (action == Actions.StopTransaction && replyResult.CallErrorMsg == "Reject Response Message")
-                                {
-                                    //do nothing 
-                                    logger.LogWarning(replyResult.Exception.ToString());
-                                }
-                                else
-                                {
-                                    string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                                    string errorMsg = replyResult.Exception != null ? replyResult.Exception.ToString() : string.Empty;
-
-                                    Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-                                }
-                            }
-                            sendTimer.Stop();
-                            if(sendTimer.ElapsedMilliseconds/1000 > 1)
-                            {
-                                logger.LogCritical("ProcessRequestMessage Send Cost {time} sec", sendTimer.ElapsedMilliseconds / 1000);
-                            }
-
-                            if (action == Actions.StartTransaction)
-                            {
-                                var stationId = await _loadingBalanceService.GetStationIdByMachineId(session.MachineId);
-                                var _powerDic = await _loadingBalanceService.GetSettingPower(stationId);
-                                if (_powerDic != null)
-                                {
-                                    foreach (var kv in _powerDic)
-                                    {
-                                        try
-                                        {
-                                            if (kv.Value.HasValue)
-                                            {
-                                                profileHandler.SetChargingProfile(kv.Key, kv.Value.Value, Packet.Messages.SubTypes.ChargingRateUnitType.W);
-                                            }
-                                        }
-                                        catch (Exception ex)
-                                        {
-                                            logger.LogError(string.Format("Set Profile Exception: {0}", ex.ToString()));
-                                        }
-
-                                    }
-                                }
-                            }
-
-                            if (action == Actions.StopTransaction)
-                            {
-                                var stationId = await _loadingBalanceService.GetStationIdByMachineId(session.MachineId);
-                                var _powerDic = await _loadingBalanceService.GetSettingPower(stationId);
-                                if (_powerDic != null)
-                                {
-                                    foreach (var kv in _powerDic)
-                                    {
-                                        try
-                                        {
-                                            if (kv.Value.HasValue)
-                                            {
-                                                profileHandler.SetChargingProfile(kv.Key, kv.Value.Value, Packet.Messages.SubTypes.ChargingRateUnitType.W);
-                                            }
-                                        }
-                                        catch (Exception ex)
-                                        {
-                                            logger.LogError(string.Format("Set Profile Exception: {0}", ex.ToString()));
-                                        }
-                                    }
-                                }
-                            }
-
-                        }
-                        break;
-                    case "FirmwareManagement":
-                        {
-                            var replyResult = await profileHandler.ExecuteFirmwareManagementRequest(action, session, (IRequest)analysisResult.Message);
-                            if (replyResult.Success)
-                            {
-                                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()));
-
-                            }
-                            else
-                            {
-                                string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                                string errorMsg = replyResult.Exception != null ? replyResult.Exception.ToString() : string.Empty;
-
-                                Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-                            }
-
-                        }
-                        break;
-                    case "Security":
-                        {
-                            var replyResult = profileHandler.ExecuteSecurityRequest(action, session, (IRequest)analysisResult.Message);
-                            if (replyResult.Success)
-                            {
-                                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()));
-
-                            }
-                            else
-                            {
-                                string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                                string errorMsg = replyResult.Exception != null ? replyResult.Exception.ToString() : string.Empty;
-
-                                Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                            string errorMsg = string.Format("Couldn't find action name: {0} of profile", action);
-                            Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-                        }
-                        break;
-
-                }
-            }
-            outter_stopwatch.Stop();
-            if (outter_stopwatch.ElapsedMilliseconds > 1000)
-            {
-                logger.LogCritical("ProcessRequestMessage {action} too long {time} sec", action.ToString(), outter_stopwatch.ElapsedMilliseconds / 1000);
-            }
-        }
-
-        async private void ProcessConfirmationMessage(MessageResult analysisResult, WsClientData session, Actions action)
-        {
-
-            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-            if (await confirmWaitingMessageSerevice.TryConfirmMessage(analysisResult))
-            {
-                var profileName = profiles.Where(x => x.IsExisted(analysisResult.Action)).Select(x => x.Name).FirstOrDefault();
-                MessageResult confirmResult = null;
-                switch (profileName)
-                {
-                    case "Core":
-                        {
-                            confirmResult = await profileHandler.ExecuteCoreConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
-                        }
-                        break;
-                    case "FirmwareManagement":
-                        {
-                            confirmResult = await profileHandler.ExecuteFirmwareManagementConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
-                        }
-                        break;
-                    case "RemoteTrigger":
-                        {
-                            confirmResult = await profileHandler.ExecuteRemoteTriggerConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
-                        }
-                        break;
-                    case "Reservation":
-                        {
-                            confirmResult = await profileHandler.ExecuteReservationConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
-                        }
-                        break;
-                    case "LocalAuthListManagement":
-                        {
-                            confirmResult = await profileHandler.ExecuteLocalAuthListManagementConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
-                        }
-                        break;
-                    case "SmartCharging":
-                        {
-                            confirmResult = await profileHandler.ExecuteSmartChargingConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
-                        }
-                        break;
-                    case "Security":
-                        {
-                            confirmResult = profileHandler.ExecuteSecurityConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
-                        }
-                        break;
-                    default:
-                        {
-                            string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                            string errorMsg = string.Format("Couldn't find action name: {0} of profile", action);
-                            Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-                        }
-                        break;
-
-                }
-
-                if (confirmResult == null || !confirmResult.Success)
-                {
-                    logger.LogError(string.Format("Action:{0} MessageId:{1}  ExecuteConfirm Error:{2} ",
-                        analysisResult.Action, analysisResult.UUID, confirmResult.Exception.ToString()));
-                }
-            }
-            else
-            {
-                string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                string errorMsg = string.Format("Action:{0} MessageId:{1}  didn't exist in confirm message", analysisResult.Action, analysisResult.UUID);
-                Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-            }
-        }
-
-        private async void ProcessErrorMessage(MessageResult analysisResult, WsClientData session, Actions action)
-        {
-            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-            if (await confirmWaitingMessageSerevice.TryConfirmMessage(analysisResult))
-            {
-                var profileName = profiles.Where(x => x.IsExisted(analysisResult.Action)).Select(x => x.Name).FirstOrDefault();
-                switch (profileName)
-                {
-                    case "Core":
-                        {
-                            _ = profileHandler.ReceivedCoreError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
-                        }
-                        break;
-                    case "FirmwareManagement":
-                        {
-                            _ = profileHandler.ReceivedFirmwareManagementError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
-                        }
-                        break;
-                    case "RemoteTrigger":
-                        {
-                            _ = profileHandler.ReceivedRemoteTriggerError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
-                        }
-                        break;
-                    case "Reservation":
-                        {
-                            _ = profileHandler.ExecuteReservationError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
-                        }
-                        break;
-                    case "LocalAuthListManagement":
-                        {
-                            _ = profileHandler.ReceivedLocalAuthListManagementError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
-                        }
-                        break;
-                    case "SmartCharging":
-                        {
-                            _ = profileHandler.ReceivedSmartChargingError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
-                        }
-                        break;
-                    default:
-                        {
-                            string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                            string errorMsg = string.Format("Couldn't find action name: {0} of profile", action);
-                            Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-                        }
-                        break;
-
-                }
-            }
-            else
-            {
-                string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
-                string errorMsg = string.Format("Action:{0} MessageId:{1}  didn't exist in confirm message", analysisResult.Action, analysisResult.UUID);
-                Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
-
-
-            }
-        }
-
-        private async Task StartInitializeEVSE(WsClientData session)
-        {
-            try
-            {
-                await InitializeEVSE(session);
-            }
-            catch (Exception e)
-            {
-                logger.LogCritical("StartInitializeEVSE:{errormsg}", e.Message);
-                logger.LogCritical("StartInitializeEVSE:{errorStackTrace}", e.StackTrace);
-            }
-            finally
-            {
-                session.BootStatus = BootStatus.Pending;
-                //bootSemaphore.Release();
-            }
-        }
-
-        private async Task InitializeEVSE(WsClientData session)
-        {
-            // Pending mode 下發設定 
-            string connectorType = await mainDbService.GetMachineConnectorType(session.ChargeBoxId, session.DisconnetCancellationToken);
-            if (!string.IsNullOrEmpty(connectorType) &&
-                (connectorType.Contains("6") || connectorType.Contains("7") || connectorType.Contains("8") || connectorType.Contains("9")))
-            {
-                session.IsAC = false;
-            }
-            await mainDbService.SetMachineConnectionType(session.ChargeBoxId, session.UriScheme.Contains("wss") ? 2 : 1, session.DisconnetCancellationToken);
-
-            string requestId = string.Empty;
-
-            var displayPriceText = await webDbService.SetDefaultFee(session);
-            UpdateClientDisplayPrice(session.ChargeBoxId, displayPriceText);
-
-            Func<string, Task<string>> sendTask;
-            sendTask = async (string serialNo) => await messageService.SendGetEVSEConfigureRequest(session.ChargeBoxId, serialNo: serialNo);
-            var response = await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, session.DisconnetCancellationToken);
-
-            if (!string.IsNullOrEmpty(displayPriceText))
-            {
-                sendTask = async (string serialNo) => await messageService.SendChangeConfigurationRequest(
-                    session.ChargeBoxId, key: "DefaultPrice", value: displayPriceText, serialNo: serialNo);
-                await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, session.DisconnetCancellationToken);
-            }
-
-            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");
-            }
-
-            //foreach (var initFunction in InitActions)
-            for (var index = 0; index < InitActions.Count; index++)
-            {
-                var initFunction = InitActions[index];
-                await initFunction(session, session.DisconnetCancellationToken);
-            }
-            //await StationConfigService?.CheckAndUpdateEvseConfig(session, session.DisconnetCancellationToken);
-        }
-
-        private void Send(WsClientData session, string msg, string messageType, string errorMsg = "")
-        {
-            try
-            {
-
-                if (session != null)
-                {
-                    WriteMachineLog(session, msg, messageType, errorMsg, true);
-                    session.Send(msg);
-                }
-            }
-            catch (Exception ex)
-            {
-                logger.LogError(string.Format("Send Ex:{0}", ex.ToString()));
-            }
-
-
-        }
-
-        internal async void RemoveClient(WsClientData session, string reason)
-        {
-            if (session == null)
-            {
-                return;
-            }
-
-            if (!string.IsNullOrEmpty(session.MachineId))
-                logger.LogTrace("RemoveClient[{0}]:{1}", session.ChargeBoxId, reason);
-
-            WriteMachineLog(session, string.Format("CloseReason: {0}", reason), "Connection", "");
-
-            //if (session.Connected)
-            //{
-            //    session.Close(CloseReason.ServerShutdown);
-            //}
-            RemoveClientDic(session);
-            try
-            {
-                session.SessionClosed -= AppServer_SessionClosed;
-                session.m_ReceiveData -= ReceivedMessageTimeLimited;
-
-                if (session.State == WebSocketState.Open)
-                {
-                    await session.Close();
-                }
-                // session.Close(CloseReason.ServerShutdown);
-
-            }
-            catch (Exception ex)
-            {
-                //logger.LogWarning("Close client socket error!!");
-                logger.LogWarning(string.Format("Close client socket error!! {0} Msg:{1}", session.ChargeBoxId, ex.Message));
-            }
-
-            if (session != null)
-            {
-                session = null;
-            }
-        }
-
-        private void RemoveClientDic(WsClientData session)
-        {
-            if (string.IsNullOrEmpty(session.ChargeBoxId))
-            {
-                return;
-            }
-
-            if (clientDic.ContainsKey(session.ChargeBoxId))
-            {
-                if (clientDic[session.ChargeBoxId].SessionID == session.SessionID)
-                {
-                    logger.LogDebug(String.Format("ChargeBoxId:{0} Remove SessionId:{1} Removed SessionId:{2}", session.ChargeBoxId, session.SessionID, clientDic[session.ChargeBoxId].SessionID));
-
-                    clientDic.Remove(session.ChargeBoxId, out _);
-                    logger.LogTrace("RemoveClient ContainsKey " + session.ChargeBoxId);
-                }
-
-            }
-        }
-
-        private void WarmUpLog()
-        {
-            connectionLogdbService.WarmUpLog();
-        }
-
-        private void WriteMachineLog(WsClientData WsClientData, string data, string messageType, string errorMsg = "", bool isSent = false)
-        {
-            try
-            {
-
-                if (WsClientData == null || string.IsNullOrEmpty(data)) return;
-
-                if (WsClientData.ChargeBoxId == null)
-                {
-                    logger.LogCritical(WsClientData.Path.ToString() + "]********************session ChargeBoxId null sessionId=" + WsClientData.SessionID);
-                }
-
-                connectionLogdbService.WriteMachineLog(WsClientData, data, messageType, errorMsg, isSent);
-            }
-            catch (Exception ex)
-            {
-                //Console.WriteLine(ex.ToString());
-                logger.LogError(ex,ex.Message);
-            }
-
-
-        }
-    }
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages;
+using EVCB_OCPP.Packet.Messages.Basic;
+using EVCB_OCPP.Packet.Messages.Core;
+using EVCB_OCPP.WSServer.Dto;
+using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Message;
+using EVCB_OCPP.WSServer.Service;
+using Microsoft.Extensions.Hosting;
+using Newtonsoft.Json;
+
+
+using System.Data;
+using System.Diagnostics;
+using System.Security.Authentication;
+using System.Xml.Linq;
+using NLog;
+using Microsoft.Extensions.Configuration;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Builder;
+using NLog.Extensions.Logging;
+using Microsoft.Data.SqlClient;
+using System.Collections.Concurrent;
+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
+{
+    public class DestroyRequest : IRequest
+    {
+        public string Action { get; set; }
+
+        public bool TransactionRelated()
+        {
+            return false;
+        }
+
+        public bool Validate()
+        {
+            return true;
+        }
+    }
+
+    public class ProtalServer : IHostedService
+    {
+        //static private ILogger logger = NLog.LogManager.GetCurrentClassLogger();
+
+        public ProtalServer(
+            ILogger<ProtalServer> logger
+            , IConfiguration configuration
+            //, IDbContextFactory<MainDBContext> maindbContextFactory
+            , IMainDbService mainDbService
+            //, IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory
+            , ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory
+            , ISqlConnectionFactory<MainDBContext> mainDbConnectionFactory
+            , IHostEnvironment environment
+            //, IOCPPWSServerFactory ocppWSServerFactory
+            , IConnectionLogdbService connectionLogdbService
+            , WebDbService webDbService
+            , ServerMessageService serverMessageService
+            , IServiceProvider serviceProvider
+            , OcppWebsocketService websocketService
+            , ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice
+            //, StationConfigService stationConfigService
+            , OuterHttpClient httpClient)
+        {
+            _ct = _cts.Token;
+            this.logger = logger;
+            this.configuration = configuration;
+            //this.maindbContextFactory = maindbContextFactory;
+            this.mainDbService = mainDbService;
+            //this.webDbConnectionFactory = webDbConnectionFactory;
+            //this.connectionLogdbContextFactory = connectionLogdbContextFactory;
+            //this.ocppWSServerFactory = ocppWSServerFactory;
+            this.connectionLogdbService = connectionLogdbService;
+            this.webDbService = webDbService;
+            this.messageService = serverMessageService;
+            this.websocketService = websocketService;
+            this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
+            this.serviceProvider = serviceProvider;
+            //this.stationConfigService = stationConfigService;
+            this.httpClient = httpClient;
+            isInDocker = !string.IsNullOrEmpty(configuration["DOTNET_RUNNING_IN_CONTAINER"]);
+
+            // = configuration.GetConnectionString("WebDBContext");
+            this.profileHandler = serviceProvider.GetService<ProfileHandler>();// new ProfileHandler(configuration, serviceProvider);
+            _loadingBalanceService = new LoadingBalanceService(mainDbConnectionFactory, webDbConnectionFactory);
+
+            WarmUpLog();
+        }
+
+        #region private fields
+        private OuterHttpClient httpClient;
+        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 ILogger<ProtalServer> logger;
+        private readonly IConfiguration configuration;
+        private readonly IServiceProvider serviceProvider;
+        //private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+        private readonly IMainDbService mainDbService;
+        //private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
+
+        //private readonly IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory;
+        //private readonly IOCPPWSServerFactory ocppWSServerFactory;
+        private readonly IConnectionLogdbService connectionLogdbService;
+        private readonly WebDbService webDbService;
+        private readonly ServerMessageService messageService;
+        private readonly OcppWebsocketService websocketService;
+        private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
+        //private readonly StationConfigService stationConfigService;
+        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 DateTime checkUpdateDt = DateTime.UtcNow;
+        private DateTime _CheckFeeDt = DateTime.UtcNow;
+        private DateTime _CheckLBDt = DateTime.UtcNow;
+        private DateTime _CheckDenyListDt = DateTime.UtcNow.AddDays(-1);
+        private readonly LoadingBalanceService _loadingBalanceService;// = new LoadingBalanceService();
+        private List<StationInfoDto> _StationInfo = new List<StationInfoDto>();
+
+        private 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"
+        };
+        private readonly List<Profile> profiles = new List<Profile>()
+        {
+             new CoreProfile(),
+             new FirmwareManagementProfile(),
+             new ReservationProfile(),
+             new RemoteTriggerProfile(),
+             new SmartChargingProfile(),
+             new LocalAuthListManagementProfile(),
+             new SecurityProfile(),
+        };
+        private CancellationTokenSource _cts = new CancellationTokenSource();
+        private CancellationToken _ct;
+        private Semaphore bootSemaphore = new Semaphore(10, 10);
+        #endregion
+
+        internal Dictionary<string, WsClientData> GetClientDic()
+        {
+            Dictionary<string, WsClientData> toReturn = null;
+            toReturn = new Dictionary<string, WsClientData>(clientDic);
+            return toReturn;
+        }
+
+        internal IReadOnlyList<Profile> Profiles => profiles.AsReadOnly();
+        internal LoadingBalanceService LoadingBalanceService => _loadingBalanceService;
+        internal ProfileHandler ProfileHandler => profileHandler;
+
+        internal readonly List<Func<WsClientData, CancellationToken, Task>> InitActions = new List<Func<WsClientData, CancellationToken, Task>>();
+
+        public async Task StartAsync(CancellationToken cancellationToken)
+        {
+            GlobalConfig.DenyModelNames = await webDbService.GetDenyModelNames(cancellationToken);
+            Start();
+            return;
+        }
+
+        public Task StopAsync(CancellationToken cancellationToken)
+        {
+            return Task.CompletedTask;
+        }
+
+        internal void UpdateClientDisplayPrice(string key,string price)
+        {
+            clientDic[key].DisplayPrice = price;
+        }
+
+        internal void SendMsg(WsClientData session, string msg, string messageType, string errorMsg = "")
+        {
+            Send(session,msg,messageType,errorMsg);
+        }
+
+        internal void Start()
+        {
+            Console.WriteLine("Starting Server...");
+
+            if (!GlobalConfig.LoadAPPConfig(configuration))
+            {
+                Console.WriteLine("Please check App.Config setting .");
+                return;
+            }
+
+            StartWsService();
+            //OpenNetwork();
+
+
+            //RunHttpConsoleService();
+            return;
+            if (!isInDocker)
+            {
+                Task consoleReadTask = new Task(RunConsoleInteractive);
+                consoleReadTask.Start();
+                //RunConsoleInteractive();
+                return;
+            }
+        }
+
+        private void StartWsService()
+        {
+            websocketService.NewSessionConnected += AppServer_NewSessionConnected;
+        }
+        private void StopWsService()
+        {
+            websocketService.NewSessionConnected -= AppServer_NewSessionConnected;
+        }
+
+        private async void AppServer_NewSessionConnected(object sender, WsClientData session)
+        {
+            logger.LogDebug(string.Format("{0} NewSessionConnected", session.Path));
+
+            try
+            {
+                bool isNotSupported = session.SecWebSocketProtocol.Contains("ocpp1.6") ? false : session.SecWebSocketProtocol.Contains("ocpp2.0") ? false : true;
+                if (isNotSupported)
+                {
+                    //logger.LogDebug(string.Format("ChargeBoxId:{0} SecWebSocketProtocol:{1} NotSupported", session.ChargeBoxId, session.SecWebSocketProtocol));
+                    WriteMachineLog(session, string.Format("SecWebSocketProtocol:{0} NotSupported", session.SecWebSocketProtocol), "Connection", "");
+                    return;
+                }
+
+                TryRemoveDuplicatedSession(session);
+                clientDic[session.ChargeBoxId] = session;
+
+                session.SessionClosed += AppServer_SessionClosed;
+                session.m_ReceiveData += ReceivedMessageTimeLimited;
+                // logger.LogDebug("------------New " + (session == null ? "Oops" : session.ChargeBoxId));
+                WriteMachineLog(session, "NewSessionConnected", "Connection", "");
+
+                await mainDbService.UpdateMachineConnectionType(session.ChargeBoxId, session.UriScheme.Contains("wss") ? 2 : 1);
+
+            }
+            catch (Exception ex)
+            {
+                logger.LogError(string.Format("NewSessionConnected Ex: {0}", ex.ToString()));
+            }
+        }
+
+        private void AppServer_SessionClosed(object sender, string closeReason)
+        {
+            if (sender is not WsClientData session)
+            {
+                return;
+            }
+
+            //session.SessionClosed -= AppServer_SessionClosed;
+            //session.m_ReceiveData -= ReceivedMessageTimeLimited;
+
+            //WriteMachineLog(session, string.Format("CloseReason: {0}", closeReason), "Connection", "");
+            RemoveClient(session, closeReason);
+        }
+
+        private void TryRemoveDuplicatedSession(WsClientData session)
+        {
+            if (clientDic.ContainsKey(session.ChargeBoxId))
+            {
+                var oldSession = clientDic[session.ChargeBoxId];
+                //WriteMachineLog(oldSession, "Duplicate Logins", "Connection", "");
+                RemoveClient(oldSession, "Duplicate Logins");
+            }
+        }
+
+        private void RunConsoleInteractive()
+        {
+            while (true)
+            {
+                if (Console.In is null)
+                {
+                    break;
+                }
+
+                var input = Console.ReadLine();
+
+                switch (input.ToLower())
+                {
+                    case "stop":
+                        Console.WriteLine("Command stop");
+                        Stop();
+                        break;
+
+                    case "gc":
+                        Console.WriteLine("Command GC");
+                        GC.Collect();
+                        break;
+                    case "lc":
+                        {
+                            Console.WriteLine("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)
+                            {
+                                Console.WriteLine(i + ":" + c.ChargeBoxId + " " + c.SessionID);
+                                i++;
+                            }
+                        }
+                        break;
+                    case "lcn":
+                        {
+                            Console.WriteLine("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)
+                            {
+                                Console.WriteLine(iLcn + ":" + c + ":" + clientDic.Where(z => z.Value.CustomerName == c).Count().ToString());
+                                iLcn++;
+                            }
+
+                        }
+                        break;
+                    case "help":
+                        Console.WriteLine("Command help!!");
+                        Console.WriteLine("lcn : List Customer Name");
+                        Console.WriteLine("gc : GC Collect");
+                        Console.WriteLine("lc : List Clients");
+                        Console.WriteLine("cls : clear console");
+                        Console.WriteLine("silent : silent");
+                        Console.WriteLine("show : show log");
+                        // logger.Info("rcl : show Real Connection Limit");
+                        break;
+                    case "cls":
+                        Console.WriteLine("Command clear");
+                        Console.Clear();
+                        break;
+
+                    case "silent":
+                        Console.WriteLine("Command silent");
+                        //var xe = XElement.Load("NLog.config");
+                        //var xns = xe.GetDefaultNamespace();
+                        //var minlevelattr = xe.Descendants(xns + "rules").Elements(xns + "logger")
+                        //    .Where(c => c.Attribute("writeTo").Value.Equals("console")).Attributes("minlevel").FirstOrDefault();
+                        //if (minlevelattr != null)
+                        //{
+
+                        //    minlevelattr.Value = "Warn";
+                        //}
+                        //xe.Save("NLog.config");
+                        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);
+                            }
+                        }
+                        break;
+                    case "show":
+                        Console.WriteLine("Command show");
+                        //var xe1 = XElement.Load("NLog.config");
+                        //var xns1 = xe1.GetDefaultNamespace();
+                        //var minlevelattr1 = xe1.Descendants(xns1 + "rules").Elements(xns1 + "logger")
+                        //    .Where(c => c.Attribute("writeTo").Value.Equals("console")).Attributes("minlevel").FirstOrDefault();
+                        //if (minlevelattr1 != null)
+                        //{
+
+                        //    minlevelattr1.Value = "trace";
+                        //}
+                        //xe1.Save("NLog.config");
+                        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);
+                            }
+                        }
+                        break;
+                    case "rcl":
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        internal void Stop()
+        {
+            _cts?.Cancel();
+        }
+
+        async private void ReceivedMessageTimeLimited(object sender, string rawdata)
+        {
+            if (sender is not WsClientData session)
+            {
+                return;
+            }
+            CancellationTokenSource tokenSource = new();
+            var task = ReceivedMessage(session, rawdata);
+            var completedTask = await Task.WhenAny(task, Task.Delay(90_000, tokenSource.Token));
+
+            if (completedTask != task)
+            {
+                logger.LogCritical("Process timeout: {0} ", rawdata);
+                await task;
+                return;
+            }
+
+            tokenSource.Cancel();
+            return;
+        }
+
+        async private Task ReceivedMessage(WsClientData session, string rawdata)
+        {
+            try
+            {
+                BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+                MessageResult analysisResult = msgAnalyser.AnalysisReceiveData(session, rawdata);
+
+                WriteMachineLog(session, rawdata,
+                     string.Format("{0} {1}", string.IsNullOrEmpty(analysisResult.Action) ? "unknown" : analysisResult.Action, analysisResult.Id == 2 ? "Request" : (analysisResult.Id == 3 ? "Confirmation" : "Error")), analysisResult.Exception == null ? "" : analysisResult.Exception.Message);
+
+                if (session.ResetSecurityProfile)
+                {
+                    logger.LogError(string.Format("[{0}] ChargeBoxId:{1} ResetSecurityProfile", DateTime.UtcNow, session.ChargeBoxId));
+                    RemoveClient(session, "ResetSecurityProfile");
+                    return;
+                }
+
+
+                if (!analysisResult.Success)
+                {
+                    //解析RawData就發生錯誤
+                    if (!string.IsNullOrEmpty(analysisResult.CallErrorMsg))
+                    {
+                        Send(session, analysisResult.CallErrorMsg, string.Format("{0} {1}", analysisResult.Action, "Error"));
+                    }
+                    else
+                    {
+                        if (analysisResult.Message == null)
+                        {
+                            string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                            string errorMsg = string.Empty;
+                            if (analysisResult.Exception != null)
+                            {
+                                errorMsg = analysisResult.Exception.ToString();
+                            }
+
+                            Send(session, replyMsg, string.Format("{0} {1}", "unknown", "Error"), "EVSE's sent essage has parsed Failed. ");
+                        }
+                        else
+                        {
+                            BaseMessage _baseMsg = analysisResult.Message as BaseMessage;
+
+
+                            string replyMsg = BasicMessageHandler.GenerateCallError(_baseMsg.Id, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                            string errorMsg = string.Empty;
+                            if (analysisResult.Exception != null)
+                            {
+                                errorMsg = analysisResult.Exception.ToString();
+                            }
+
+                            Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+                        }
+
+                    }
+                }
+                else
+                {
+                    switch (analysisResult.Id)
+                    {
+                        case BasicMessageHandler.TYPENUMBER_CALL:
+                            {
+                                if (!session.ISOCPP20)
+                                {
+                                    Actions action = Convertor.GetAction(analysisResult.Action);
+                                    try
+                                    {
+                                        await ProcessRequestMessage(analysisResult, session, action);
+                                    }
+                                    catch (Exception e)
+                                    {
+                                        logger.LogError($"Processing {action} exception!");
+                                        throw;
+                                    }
+                                }
+                                else
+                                {
+                                    EVCB_OCPP20.Packet.Features.Actions action = Convertor.GetActionby20(analysisResult.Action);
+                                    MessageResult result = new MessageResult() { Success = true };
+                                    //ocpp20 處理
+                                    switch (action)
+                                    {
+
+                                        case EVCB_OCPP20.Packet.Features.Actions.BootNotification:
+                                            {
+                                                EVCB_OCPP20.Packet.Messages.BootNotificationRequest _request = (EVCB_OCPP20.Packet.Messages.IRequest)analysisResult.Message as EVCB_OCPP20.Packet.Messages.BootNotificationRequest;
+
+                                                var confirm = new EVCB_OCPP20.Packet.Messages.BootNotificationResponse() { CurrentTime = DateTime.UtcNow, Interval = 180, Status = EVCB_OCPP20.Packet.DataTypes.EnumTypes.RegistrationStatusEnumType.Pending };
+
+                                                result.Message = confirm;
+                                                result.Success = true;
+
+                                                string response = BasicMessageHandler.GenerateConfirmationofOCPP20(analysisResult.UUID, (EVCB_OCPP20.Packet.Messages.IConfirmation)result.Message);
+                                                Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Response"), result.Exception == null ? string.Empty : result.Exception.ToString());
+
+
+                                                var request = new EVCB_OCPP20.Packet.Messages.SetNetworkProfileRequest()
+                                                {
+                                                    ConfigurationSlot = 1,
+                                                    ConnectionData = new EVCB_OCPP20.Packet.DataTypes.NetworkConnectionProfileType()
+                                                    {
+                                                        OcppVersion = EVCB_OCPP20.Packet.DataTypes.EnumTypes.OCPPVersionEnumType.OCPP20,
+                                                        OcppTransport = EVCB_OCPP20.Packet.DataTypes.EnumTypes.OCPPTransportEnumType.JSON,
+                                                        MessageTimeout = 30,
+                                                        OcppCsmsUrl = session.UriScheme == "ws" ? GlobalConfig.OCPP20_WSUrl : GlobalConfig.OCPP20_WSSUrl,
+                                                        OcppInterface = EVCB_OCPP20.Packet.DataTypes.EnumTypes.OCPPInterfaceEnumType.Wired0
+                                                    }
+
+                                                };
+                                                var uuid = session.queue20.store(request);
+                                                string requestText = BasicMessageHandler.GenerateRequestofOCPP20(uuid, "SetNetworkProfile", request);
+                                                Send(session, requestText, "SetNetworkProfile");
+
+                                            }
+                                            break;
+                                        default:
+                                            {
+                                                logger.LogError(string.Format("We don't implement messagetype:{0} of raw data :{1} by {2}", analysisResult.Id, rawdata, session.ChargeBoxId));
+                                            }
+                                            break;
+                                    }
+
+                                }
+                            }
+                            break;
+                        case BasicMessageHandler.TYPENUMBER_CALLRESULT:
+                            {
+                                if (!session.ISOCPP20)
+                                {
+                                    Actions action = Convertor.GetAction(analysisResult.Action);
+                                    ProcessConfirmationMessage(analysisResult, session, action);
+                                }
+                                else
+                                {
+                                    EVCB_OCPP20.Packet.Features.Actions action = Convertor.GetActionby20(analysisResult.Action);
+                                    MessageResult result = new MessageResult() { Success = true };
+                                    //ocpp20 處理
+                                    switch (action)
+                                    {
+
+                                        case EVCB_OCPP20.Packet.Features.Actions.SetNetworkProfile:
+                                            {
+                                                EVCB_OCPP20.Packet.Messages.SetNetworkProfileResponse response = (EVCB_OCPP20.Packet.Messages.IConfirmation)analysisResult.Message as EVCB_OCPP20.Packet.Messages.SetNetworkProfileResponse;
+
+                                                if (response.Status == EVCB_OCPP20.Packet.DataTypes.EnumTypes.SetNetworkProfileStatusEnumType.Accepted)
+                                                {
+                                                    var request = new EVCB_OCPP20.Packet.Messages.SetVariablesRequest()
+                                                    {
+                                                        SetVariableData = new List<EVCB_OCPP20.Packet.DataTypes.SetVariableDataType>()
+                                                         {
+                                                              new EVCB_OCPP20.Packet.DataTypes.SetVariableDataType()
+                                                              {
+                                                                    Component=new EVCB_OCPP20.Packet.DataTypes.ComponentType()
+                                                                    {
+                                                                         Name="OCPPCommCtrlr",
+
+                                                                    },
+                                                                     AttributeType= EVCB_OCPP20.Packet.DataTypes.EnumTypes.AttributeEnumType.Actual,
+                                                                     AttributeValue= JsonConvert.SerializeObject(new List<int>(){ 1 }),
+                                                                     Variable=new EVCB_OCPP20.Packet.DataTypes.VariableType()
+                                                                    {
+                                                                            Name="NetworkConfigurationPriority",
+
+                                                                    }
+
+
+                                                              }
+                                                         }
+
+                                                    };
+                                                    var uuid = session.queue20.store(request);
+                                                    string requestText = BasicMessageHandler.GenerateRequestofOCPP20(uuid, "SetVariables", request);
+                                                    Send(session, requestText, "SetVariables");
+                                                }
+
+                                            }
+                                            break;
+                                        case EVCB_OCPP20.Packet.Features.Actions.SetVariables:
+                                            {
+                                                EVCB_OCPP20.Packet.Messages.SetVariablesResponse response = (EVCB_OCPP20.Packet.Messages.IConfirmation)analysisResult.Message as EVCB_OCPP20.Packet.Messages.SetVariablesResponse;
+
+                                                if (response.SetVariableResult[0].AttributeStatus == EVCB_OCPP20.Packet.DataTypes.EnumTypes.SetVariableStatusEnumType.RebootRequired)
+                                                {
+                                                    var request = new EVCB_OCPP20.Packet.Messages.ResetRequest()
+                                                    {
+                                                        Type = EVCB_OCPP20.Packet.DataTypes.EnumTypes.ResetEnumType.OnIdle
+
+                                                    };
+                                                    var uuid = session.queue20.store(request);
+                                                    string requestText = BasicMessageHandler.GenerateRequestofOCPP20(uuid, "Reset", request);
+                                                    Send(session, requestText, "Reset");
+
+                                                }
+                                            }
+                                            break;
+                                        default:
+                                            {
+                                                logger.LogError(string.Format("We don't implement messagetype:{0} of raw data :{1} by {2}", analysisResult.Id, rawdata, session.ChargeBoxId));
+                                            }
+                                            break;
+                                    }
+                                }
+
+                            }
+                            break;
+                        case BasicMessageHandler.TYPENUMBER_CALLERROR:
+                            {
+                                //只處理 丟出Request 收到Error的訊息                              
+                                if (analysisResult.Success && analysisResult.Message != null)
+                                {
+                                    Actions action = Convertor.GetAction(analysisResult.Action);
+                                    ProcessErrorMessage(analysisResult, session, action);
+                                }
+
+                            }
+                            break;
+                        default:
+                            {
+                                logger.LogError(string.Format("Can't analyze messagetype:{0} of raw data :{1} by {2}", analysisResult.Id, rawdata, session.ChargeBoxId));
+                            }
+                            break;
+
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+
+
+                if (ex.InnerException != null)
+                {
+                    logger.LogError(string.Format("{0} **Inner Exception :{1} ", session.ChargeBoxId + rawdata, ex.ToString()));
+
+
+                }
+                else
+                {
+                    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)
+        {
+            Stopwatch outter_stopwatch = Stopwatch.StartNew();
+            //BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+            if (!session.IsCheckIn && action != Actions.BootNotification)
+            {
+                if (analysisResult.Message is IRequest request && !request.TransactionRelated())
+                {
+                    string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.GenericError, OCPPErrorDescription.NotChecked);
+                    Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Error"));
+                }
+            }
+            else
+            {
+                var profileName = profiles.Where(x => x.IsExisted(analysisResult.Action)).Select(x => x.Name).FirstOrDefault();
+                switch (profileName)
+                {
+                    case "Core":
+                        {
+                            var replyResult = await profileHandler.ExecuteCoreRequest(action, session, (IRequest)analysisResult.Message).ConfigureAwait(false);
+
+                            var sendTimer = Stopwatch.StartNew();
+                            if (replyResult.Success)
+                            {
+                                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 bootNotificationConfirmation &&
+                                    analysisResult.Message is BootNotificationRequest bootNotificationRequest)
+                                {
+                                    session.ChargePointVendor = bootNotificationRequest.chargePointVendor;
+
+                                    if (session.BootStatus == BootStatus.Startup
+                                        //&& bootSemaphore.WaitOne(0)
+                                        )
+                                    {
+                                        session.BootStatus = BootStatus.Initializing;
+                                        session.AddTask(StartInitializeEVSE(session));
+                                    }
+
+                                    if (bootNotificationConfirmation.status == Packet.Messages.SubTypes.RegistrationStatus.Accepted)
+                                    {
+                                        session.IsCheckIn = true;
+
+                                        var sendTask = async () => await messageService.SendDataTransferRequest(
+                                            session.ChargeBoxId,
+                                            messageId: "ID_FirmwareVersion",
+                                            vendorId: "Phihong Technology",
+                                            data: string.Empty);
+                                        await sendTask();
+                                        //await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, session.DisconnetCancellationToken);
+                                    }
+                                }
+
+                                if (action == Actions.Authorize && replyResult.Message is AuthorizeConfirmation)
+                                {
+                                    var authorizeRequest = (IRequest)analysisResult.Message as AuthorizeRequest;
+                                    if (session.UserDisplayPrices.ContainsKey(authorizeRequest.idTag))
+                                    {
+                                        await messageService.SendDataTransferRequest(
+                                            session.ChargeBoxId,
+                                            messageId: "SetUserPrice",
+                                            vendorId: "Phihong Technology",
+                                            data: JsonConvert.SerializeObject(
+                                                new
+                                                {
+                                                    idToken = authorizeRequest.idTag,
+                                                    price = session.UserDisplayPrices[authorizeRequest.idTag]
+                                                })
+                                            );
+                                    }
+                                }
+
+                            }
+                            else
+                            {
+                                if (action == Actions.StopTransaction && replyResult.CallErrorMsg == "Reject Response Message")
+                                {
+                                    //do nothing 
+                                    logger.LogWarning(replyResult.Exception.ToString());
+                                }
+                                else
+                                {
+                                    string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                                    string errorMsg = replyResult.Exception != null ? replyResult.Exception.ToString() : string.Empty;
+
+                                    Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+                                }
+                            }
+                            sendTimer.Stop();
+                            if(sendTimer.ElapsedMilliseconds/1000 > 1)
+                            {
+                                logger.LogCritical("ProcessRequestMessage Send Cost {time} sec", sendTimer.ElapsedMilliseconds / 1000);
+                            }
+
+                            if (action == Actions.StartTransaction)
+                            {
+                                var stationId = await _loadingBalanceService.GetStationIdByMachineId(session.MachineId);
+                                var _powerDic = await _loadingBalanceService.GetSettingPower(stationId);
+                                if (_powerDic != null)
+                                {
+                                    foreach (var kv in _powerDic)
+                                    {
+                                        try
+                                        {
+                                            if (kv.Value.HasValue)
+                                            {
+                                                profileHandler.SetChargingProfile(kv.Key, kv.Value.Value, Packet.Messages.SubTypes.ChargingRateUnitType.W);
+                                            }
+                                        }
+                                        catch (Exception ex)
+                                        {
+                                            logger.LogError(string.Format("Set Profile Exception: {0}", ex.ToString()));
+                                        }
+
+                                    }
+                                }
+                            }
+
+                            if (action == Actions.StopTransaction)
+                            {
+                                var stationId = await _loadingBalanceService.GetStationIdByMachineId(session.MachineId);
+                                var _powerDic = await _loadingBalanceService.GetSettingPower(stationId);
+                                if (_powerDic != null)
+                                {
+                                    foreach (var kv in _powerDic)
+                                    {
+                                        try
+                                        {
+                                            if (kv.Value.HasValue)
+                                            {
+                                                profileHandler.SetChargingProfile(kv.Key, kv.Value.Value, Packet.Messages.SubTypes.ChargingRateUnitType.W);
+                                            }
+                                        }
+                                        catch (Exception ex)
+                                        {
+                                            logger.LogError(string.Format("Set Profile Exception: {0}", ex.ToString()));
+                                        }
+                                    }
+                                }
+                            }
+
+                        }
+                        break;
+                    case "FirmwareManagement":
+                        {
+                            var replyResult = await profileHandler.ExecuteFirmwareManagementRequest(action, session, (IRequest)analysisResult.Message);
+                            if (replyResult.Success)
+                            {
+                                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()));
+
+                            }
+                            else
+                            {
+                                string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                                string errorMsg = replyResult.Exception != null ? replyResult.Exception.ToString() : string.Empty;
+
+                                Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+                            }
+
+                        }
+                        break;
+                    case "Security":
+                        {
+                            var replyResult = profileHandler.ExecuteSecurityRequest(action, session, (IRequest)analysisResult.Message);
+                            if (replyResult.Success)
+                            {
+                                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()));
+
+                            }
+                            else
+                            {
+                                string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                                string errorMsg = replyResult.Exception != null ? replyResult.Exception.ToString() : string.Empty;
+
+                                Send(session, response, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+                            }
+                        }
+                        break;
+                    default:
+                        {
+                            string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                            string errorMsg = string.Format("Couldn't find action name: {0} of profile", action);
+                            Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+                        }
+                        break;
+
+                }
+            }
+            outter_stopwatch.Stop();
+            if (outter_stopwatch.ElapsedMilliseconds > 1000)
+            {
+                logger.LogCritical("ProcessRequestMessage {action} too long {time} sec", action.ToString(), outter_stopwatch.ElapsedMilliseconds / 1000);
+            }
+        }
+
+        async private void ProcessConfirmationMessage(MessageResult analysisResult, WsClientData session, Actions action)
+        {
+
+            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+            if (await confirmWaitingMessageSerevice.TryConfirmMessage(analysisResult))
+            {
+                var profileName = profiles.Where(x => x.IsExisted(analysisResult.Action)).Select(x => x.Name).FirstOrDefault();
+                MessageResult confirmResult = null;
+                switch (profileName)
+                {
+                    case "Core":
+                        {
+                            confirmResult = await profileHandler.ExecuteCoreConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
+                        }
+                        break;
+                    case "FirmwareManagement":
+                        {
+                            confirmResult = await profileHandler.ExecuteFirmwareManagementConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
+                        }
+                        break;
+                    case "RemoteTrigger":
+                        {
+                            confirmResult = await profileHandler.ExecuteRemoteTriggerConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
+                        }
+                        break;
+                    case "Reservation":
+                        {
+                            confirmResult = await profileHandler.ExecuteReservationConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
+                        }
+                        break;
+                    case "LocalAuthListManagement":
+                        {
+                            confirmResult = await profileHandler.ExecuteLocalAuthListManagementConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
+                        }
+                        break;
+                    case "SmartCharging":
+                        {
+                            confirmResult = await profileHandler.ExecuteSmartChargingConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
+                        }
+                        break;
+                    case "Security":
+                        {
+                            confirmResult = profileHandler.ExecuteSecurityConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
+                        }
+                        break;
+                    default:
+                        {
+                            string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                            string errorMsg = string.Format("Couldn't find action name: {0} of profile", action);
+                            Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+                        }
+                        break;
+
+                }
+
+                if (confirmResult == null || !confirmResult.Success)
+                {
+                    logger.LogError(string.Format("Action:{0} MessageId:{1}  ExecuteConfirm Error:{2} ",
+                        analysisResult.Action, analysisResult.UUID, confirmResult.Exception.ToString()));
+                }
+            }
+            else
+            {
+                string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                string errorMsg = string.Format("Action:{0} MessageId:{1}  didn't exist in confirm message", analysisResult.Action, analysisResult.UUID);
+                Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+            }
+        }
+
+        private async void ProcessErrorMessage(MessageResult analysisResult, WsClientData session, Actions action)
+        {
+            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+            if (await confirmWaitingMessageSerevice.TryConfirmMessage(analysisResult))
+            {
+                var profileName = profiles.Where(x => x.IsExisted(analysisResult.Action)).Select(x => x.Name).FirstOrDefault();
+                switch (profileName)
+                {
+                    case "Core":
+                        {
+                            _ = profileHandler.ReceivedCoreError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
+                        }
+                        break;
+                    case "FirmwareManagement":
+                        {
+                            _ = profileHandler.ReceivedFirmwareManagementError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
+                        }
+                        break;
+                    case "RemoteTrigger":
+                        {
+                            _ = profileHandler.ReceivedRemoteTriggerError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
+                        }
+                        break;
+                    case "Reservation":
+                        {
+                            _ = profileHandler.ExecuteReservationError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
+                        }
+                        break;
+                    case "LocalAuthListManagement":
+                        {
+                            _ = profileHandler.ReceivedLocalAuthListManagementError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
+                        }
+                        break;
+                    case "SmartCharging":
+                        {
+                            _ = profileHandler.ReceivedSmartChargingError(action, analysisResult.ReceivedErrorCode, session, analysisResult.RequestId);
+                        }
+                        break;
+                    default:
+                        {
+                            string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                            string errorMsg = string.Format("Couldn't find action name: {0} of profile", action);
+                            Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+                        }
+                        break;
+
+                }
+            }
+            else
+            {
+                string replyMsg = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.InternalError, OCPPErrorDescription.InternalError);
+                string errorMsg = string.Format("Action:{0} MessageId:{1}  didn't exist in confirm message", analysisResult.Action, analysisResult.UUID);
+                Send(session, replyMsg, string.Format("{0} {1}", analysisResult.Action, "Error"), errorMsg);
+
+
+            }
+        }
+
+        private async Task StartInitializeEVSE(WsClientData session)
+        {
+            try
+            {
+                await InitializeEVSE(session);
+            }
+            catch (Exception e)
+            {
+                logger.LogCritical("StartInitializeEVSE:{errormsg}", e.Message);
+                logger.LogCritical("StartInitializeEVSE:{errorStackTrace}", e.StackTrace);
+            }
+            finally
+            {
+                session.BootStatus = BootStatus.Pending;
+                //bootSemaphore.Release();
+            }
+        }
+
+        private async Task InitializeEVSE(WsClientData session)
+        {
+            // Pending mode 下發設定 
+            string connectorType = await mainDbService.GetMachineConnectorType(session.ChargeBoxId, session.DisconnetCancellationToken);
+            if (!string.IsNullOrEmpty(connectorType) &&
+                (connectorType.Contains("6") || connectorType.Contains("7") || connectorType.Contains("8") || connectorType.Contains("9")))
+            {
+                session.IsAC = false;
+            }
+            await mainDbService.SetMachineConnectionType(session.ChargeBoxId, session.UriScheme.Contains("wss") ? 2 : 1, session.DisconnetCancellationToken);
+
+            string requestId = string.Empty;
+
+            var displayPriceText = await webDbService.SetDefaultFee(session);
+            UpdateClientDisplayPrice(session.ChargeBoxId, displayPriceText);
+
+            Func<string, Task<string>> sendTask;
+            sendTask = async (string serialNo) => await messageService.SendGetEVSEConfigureRequest(session.ChargeBoxId, serialNo: serialNo);
+            var response = await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, session.DisconnetCancellationToken);
+
+            if (!string.IsNullOrEmpty(displayPriceText))
+            {
+                sendTask = async (string serialNo) => await messageService.SendChangeConfigurationRequest(
+                    session.ChargeBoxId, key: "DefaultPrice", value: displayPriceText, serialNo: serialNo);
+                await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, session.DisconnetCancellationToken);
+            }
+
+            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");
+            }
+
+            //foreach (var initFunction in InitActions)
+            for (var index = 0; index < InitActions.Count; index++)
+            {
+                var initFunction = InitActions[index];
+                await initFunction(session, session.DisconnetCancellationToken);
+            }
+            //await StationConfigService?.CheckAndUpdateEvseConfig(session, session.DisconnetCancellationToken);
+        }
+
+        private void Send(WsClientData session, string msg, string messageType, string errorMsg = "")
+        {
+            try
+            {
+
+                if (session != null)
+                {
+                    WriteMachineLog(session, msg, messageType, errorMsg, true);
+                    session.Send(msg);
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.LogError(string.Format("Send Ex:{0}", ex.ToString()));
+            }
+
+
+        }
+
+        internal async void RemoveClient(WsClientData session, string reason)
+        {
+            if (session == null)
+            {
+                return;
+            }
+
+            if (!string.IsNullOrEmpty(session.MachineId))
+                logger.LogTrace("RemoveClient[{0}]:{1}", session.ChargeBoxId, reason);
+
+            WriteMachineLog(session, string.Format("CloseReason: {0}", reason), "Connection", "");
+
+            //if (session.Connected)
+            //{
+            //    session.Close(CloseReason.ServerShutdown);
+            //}
+            RemoveClientDic(session);
+            try
+            {
+                session.SessionClosed -= AppServer_SessionClosed;
+                session.m_ReceiveData -= ReceivedMessageTimeLimited;
+
+                if (session.State == WebSocketState.Open)
+                {
+                    await session.Close();
+                }
+                // session.Close(CloseReason.ServerShutdown);
+
+            }
+            catch (Exception ex)
+            {
+                //logger.LogWarning("Close client socket error!!");
+                logger.LogWarning(string.Format("Close client socket error!! {0} Msg:{1}", session.ChargeBoxId, ex.Message));
+            }
+
+            if (session != null)
+            {
+                session = null;
+            }
+        }
+
+        private void RemoveClientDic(WsClientData session)
+        {
+            if (string.IsNullOrEmpty(session.ChargeBoxId))
+            {
+                return;
+            }
+
+            if (clientDic.ContainsKey(session.ChargeBoxId))
+            {
+                if (clientDic[session.ChargeBoxId].SessionID == session.SessionID)
+                {
+                    logger.LogDebug(String.Format("ChargeBoxId:{0} Remove SessionId:{1} Removed SessionId:{2}", session.ChargeBoxId, session.SessionID, clientDic[session.ChargeBoxId].SessionID));
+
+                    clientDic.Remove(session.ChargeBoxId, out _);
+                    logger.LogTrace("RemoveClient ContainsKey " + session.ChargeBoxId);
+                }
+
+            }
+        }
+
+        private void WarmUpLog()
+        {
+            connectionLogdbService.WarmUpLog();
+        }
+
+        private void WriteMachineLog(WsClientData WsClientData, string data, string messageType, string errorMsg = "", bool isSent = false)
+        {
+            try
+            {
+
+                if (WsClientData == null || string.IsNullOrEmpty(data)) return;
+
+                if (WsClientData.ChargeBoxId == null)
+                {
+                    logger.LogCritical(WsClientData.Path.ToString() + "]********************session ChargeBoxId null sessionId=" + WsClientData.SessionID);
+                }
+
+                connectionLogdbService.WriteMachineLog(WsClientData, data, messageType, errorMsg, isSent);
+            }
+            catch (Exception ex)
+            {
+                //Console.WriteLine(ex.ToString());
+                logger.LogError(ex,ex.Message);
+            }
+
+
+        }
+    }
 }

+ 95 - 95
EVCB_OCPP.WSServer/Service/BusinessServiceFactory.cs

@@ -1,95 +1,95 @@
-using EVCB_OCPP.Domain;
-
-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;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Service;
-
-
-public interface IBusinessService
-{
-    Task<IdTokenInfo> Authorize(string chargeBoxId, string idTag, int? connectorId = null, string source = null);
-
-    Task NotifyFaultStatus(ErrorDetails details);
-
-    Task NotifyConnectorUnplugged(string chargeBoxId,string data);
-
-}
-
-public static class BusinessServiceFactoryRegistration
-{
-    public static void AddBusinessServiceFactory(this IServiceCollection services)
-    {
-        services.AddSingleton<HttpClientService>();
-        services.AddSingleton<OuterHttpClient>();
-
-        services.AddTransient<OuterBusinessService>();
-        services.AddTransient<LocalBusinessService>();
-        services.AddSingleton<IBusinessServiceFactory, BusinessServiceFactory>();
-    }
-}
-
-public class BusinessServiceFactory : IBusinessServiceFactory
-{
-
-    public BusinessServiceFactory(
-        IServiceProvider serviceProvider,
-        IMainDbService mainDbService
-        //IDbContextFactory<MainDBContext> mainDBContextFactory
-        )
-    {
-        this.serviceProvider = serviceProvider;
-        this.mainDbService = mainDbService;
-        //this.mainDBContextFactory = mainDBContextFactory;
-    }
-
-    private readonly IServiceProvider serviceProvider;
-    private readonly IMainDbService mainDbService;
-
-    //private readonly IDbContextFactory<MainDBContext> mainDBContextFactory;
-
-    public async Task<IBusinessService> CreateBusinessService(string customerId)
-    {
-        bool isCallOut = false;
-        //using (var db = this.mainDBContextFactory.CreateDbContextAsync())
-        //{
-        //    isCallOut = await db.Customer.Where(x => x.Id == new Guid(customerId)).Select(x => x.CallPartnerApiOnSchedule).FirstOrDefaultAsync();
-        //}
-        CustomerSignMaterial _customer = null;
-        //using (var db = this.mainDBContextFactory.CreateDbContextAsync())
-        //{
-        //    _customer = await db.Customer.Where(x => x.Id == new Guid(customerId)).Select(x => new CustomerSignMaterial() { Id = x.Id.ToString(), APIUrl = x.ApiUrl, SaltKey = x.ApiKey, CallsThirdParty = x.CallPartnerApiOnSchedule }).FirstOrDefaultAsync();
-        //}
-        var _customerDb = await mainDbService.GetCustomer(customerId);
-        if (_customerDb is not null)
-        {
-            _customer = new CustomerSignMaterial() { 
-                Id = _customerDb.Id.ToString(),
-                APIUrl = _customerDb.ApiUrl,
-                SaltKey = _customerDb.ApiKey,
-                CallsThirdParty = _customerDb.CallPartnerApiOnSchedule
-            };
-        }
-        isCallOut = _customer != null && _customer.CallsThirdParty;
-        //return isCallOut ? new OuterBusinessService(customerId) : new LocalBusinessService(customerId);
-        if (isCallOut)
-        {
-            OuterBusinessService outerBusinessService = serviceProvider.GetService<OuterBusinessService>();
-            //outerBusinessService.CustomerId = customerId;
-            outerBusinessService.CustomerSignMaterial = _customer;
-            return outerBusinessService;
-        }
-        LocalBusinessService toReturn = serviceProvider.GetService<LocalBusinessService>();
-        toReturn.CustomerId = customerId;
-        return toReturn;
-
-        //return isCallOut ? new OuterBusinessService(customerId) : 
-    }
-
-}
+using EVCB_OCPP.Domain;
+
+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;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service;
+
+
+public interface IBusinessService
+{
+    Task<IdTokenInfo> Authorize(string chargeBoxId, string idTag, int? connectorId = null, string source = null);
+
+    Task NotifyFaultStatus(ErrorDetails details);
+
+    Task NotifyConnectorUnplugged(string chargeBoxId,string data);
+
+}
+
+public static class BusinessServiceFactoryRegistration
+{
+    public static void AddBusinessServiceFactory(this IServiceCollection services)
+    {
+        services.AddSingleton<HttpClientService>();
+        services.AddSingleton<OuterHttpClient>();
+
+        services.AddTransient<OuterBusinessService>();
+        services.AddTransient<LocalBusinessService>();
+        services.AddSingleton<IBusinessServiceFactory, BusinessServiceFactory>();
+    }
+}
+
+public class BusinessServiceFactory : IBusinessServiceFactory
+{
+
+    public BusinessServiceFactory(
+        IServiceProvider serviceProvider,
+        IMainDbService mainDbService
+        //IDbContextFactory<MainDBContext> mainDBContextFactory
+        )
+    {
+        this.serviceProvider = serviceProvider;
+        this.mainDbService = mainDbService;
+        //this.mainDBContextFactory = mainDBContextFactory;
+    }
+
+    private readonly IServiceProvider serviceProvider;
+    private readonly IMainDbService mainDbService;
+
+    //private readonly IDbContextFactory<MainDBContext> mainDBContextFactory;
+
+    public async Task<IBusinessService> CreateBusinessService(string customerId)
+    {
+        bool isCallOut = false;
+        //using (var db = this.mainDBContextFactory.CreateDbContextAsync())
+        //{
+        //    isCallOut = await db.Customer.Where(x => x.Id == new Guid(customerId)).Select(x => x.CallPartnerApiOnSchedule).FirstOrDefaultAsync();
+        //}
+        CustomerSignMaterial _customer = null;
+        //using (var db = this.mainDBContextFactory.CreateDbContextAsync())
+        //{
+        //    _customer = await db.Customer.Where(x => x.Id == new Guid(customerId)).Select(x => new CustomerSignMaterial() { Id = x.Id.ToString(), APIUrl = x.ApiUrl, SaltKey = x.ApiKey, CallsThirdParty = x.CallPartnerApiOnSchedule }).FirstOrDefaultAsync();
+        //}
+        var _customerDb = await mainDbService.GetCustomer(customerId);
+        if (_customerDb is not null)
+        {
+            _customer = new CustomerSignMaterial() { 
+                Id = _customerDb.Id.ToString(),
+                APIUrl = _customerDb.ApiUrl,
+                SaltKey = _customerDb.ApiKey,
+                CallsThirdParty = _customerDb.CallPartnerApiOnSchedule
+            };
+        }
+        isCallOut = _customer != null && _customer.CallsThirdParty;
+        //return isCallOut ? new OuterBusinessService(customerId) : new LocalBusinessService(customerId);
+        if (isCallOut)
+        {
+            OuterBusinessService outerBusinessService = serviceProvider.GetService<OuterBusinessService>();
+            //outerBusinessService.CustomerId = customerId;
+            outerBusinessService.CustomerSignMaterial = _customer;
+            return outerBusinessService;
+        }
+        LocalBusinessService toReturn = serviceProvider.GetService<LocalBusinessService>();
+        toReturn.CustomerId = customerId;
+        return toReturn;
+
+        //return isCallOut ? new OuterBusinessService(customerId) : 
+    }
+
+}

+ 291 - 291
EVCB_OCPP.WSServer/Service/ConfirmWaitingMessageSerevice.cs

@@ -1,291 +1,291 @@
-using Azure.Core;
-using Azure;
-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;
-using EVCB_OCPP.WSServer.Service.DbService;
-
-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(
-            IMainDbService mainDbService,
-            IDbContextFactory<MainDBContext> maindbContextFactory
-            ,ILogger<ConfirmWaitingMessageSerevice> logger)
-        {
-            this.mainDbService = mainDbService;
-            this.maindbContextFactory = maindbContextFactory;
-            this.logger = logger;
-        }
-
-        public event EventHandler<ConfirmWaitingMessageRemoveReason> OnMessageRemoved;
-
-        private readonly Object _lockConfirmPacketList = new object();
-        private readonly IMainDbService mainDbService;
-        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
-            {
-                Id = table_id,
-                SentAction = action,
-                SentOn = DateTime.UtcNow,
-                SentTimes = 4,
-                ChargePointSerialNumber = chargePointSerialNumber,
-                RequestId = requestId,
-                SentUniqueId = msg_id,
-                CreatedBy = createdBy,
-                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();
-            //}
-
-            await mainDbService.UpdateServerMessageUpdateTime(table_id);
-
-            #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;
-
-                try
-                {
-                    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));
-
-                    }
-                }
-                catch (Exception ex)
-                {
-                    logger.LogWarning(string.Format("TryConfirmMessage:{0}", JsonConvert.SerializeObject(analysisResult)));
-                    logger.LogWarning(ex.ToString());
-                }
-                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<object> SendAndWaitUntilResultAsync(Func<Task<string>> startSendTaskFunc, CancellationToken token = default)
-        {
-            object message;
-            do
-            {
-                var requestId = await startSendTaskFunc();
-                message = await WaitResultAsync(requestId, token: token);
-            }
-            while (message == null && !token.IsCancellationRequested);
-            return message;
-        }
-
-        internal async Task<object> SendAndWaitUntilResultAsync(Func<string, Task> startSendTaskFunc, CancellationToken token = default)
-        {
-            object message;
-            do
-            {
-                var SerialNo = Guid.NewGuid().ToString();
-                var waitObject = CreateAndAddWaitObject(SerialNo);
-                await startSendTaskFunc(SerialNo);
-                message = await WaitResultAsync(SerialNo, waitObject: waitObject, token: token);
-            }
-            while (message == null && !token.IsCancellationRequested);
-            return message;
-        }
-
-        internal async Task<object> WaitResultAsync(string serialNo, MessageResultWaitObject waitObject = null, int maxWaitSec = 65000, CancellationToken token = default)
-        {
-            if (waitObject == null)
-            {
-                waitObject = new MessageResultWaitObject();
-                asyncWaitingTasks.Add(serialNo, waitObject);
-            }
-
-            var task = waitObject.Lock.WaitAsync(token);
-            var completedTask = await Task.WhenAny(task, Task.Delay(180_000, token));
-            if (completedTask != task)
-            {
-                logger.LogWarning("wait {msg} time out", serialNo);
-                var checkResult = await mainDbService.TryGetResponseFromDb(serialNo, token);
-                if (checkResult is not null)
-                {
-                    logger.LogWarning("{msg} found in db", serialNo);
-                    return checkResult;
-                }
-                logger.LogWarning("{msg} not found in db", serialNo);
-                return null;
-            }
-            return waitObject.Result.Message;
-        }
-
-        private MessageResultWaitObject CreateAndAddWaitObject(string serialNo)
-        {
-            var waiObj = new MessageResultWaitObject();
-            asyncWaitingTasks.Add(serialNo, waiObj);
-            return waiObj;
-        }
-
-        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);
-    }
-}
+using Azure.Core;
+using Azure;
+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;
+using EVCB_OCPP.WSServer.Service.DbService;
+
+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(
+            IMainDbService mainDbService,
+            IDbContextFactory<MainDBContext> maindbContextFactory
+            ,ILogger<ConfirmWaitingMessageSerevice> logger)
+        {
+            this.mainDbService = mainDbService;
+            this.maindbContextFactory = maindbContextFactory;
+            this.logger = logger;
+        }
+
+        public event EventHandler<ConfirmWaitingMessageRemoveReason> OnMessageRemoved;
+
+        private readonly Object _lockConfirmPacketList = new object();
+        private readonly IMainDbService mainDbService;
+        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
+            {
+                Id = table_id,
+                SentAction = action,
+                SentOn = DateTime.UtcNow,
+                SentTimes = 4,
+                ChargePointSerialNumber = chargePointSerialNumber,
+                RequestId = requestId,
+                SentUniqueId = msg_id,
+                CreatedBy = createdBy,
+                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();
+            //}
+
+            await mainDbService.UpdateServerMessageUpdateTime(table_id);
+
+            #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;
+
+                try
+                {
+                    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));
+
+                    }
+                }
+                catch (Exception ex)
+                {
+                    logger.LogWarning(string.Format("TryConfirmMessage:{0}", JsonConvert.SerializeObject(analysisResult)));
+                    logger.LogWarning(ex.ToString());
+                }
+                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<object> SendAndWaitUntilResultAsync(Func<Task<string>> startSendTaskFunc, CancellationToken token = default)
+        {
+            object message;
+            do
+            {
+                var requestId = await startSendTaskFunc();
+                message = await WaitResultAsync(requestId, token: token);
+            }
+            while (message == null && !token.IsCancellationRequested);
+            return message;
+        }
+
+        internal async Task<object> SendAndWaitUntilResultAsync(Func<string, Task> startSendTaskFunc, CancellationToken token = default)
+        {
+            object message;
+            do
+            {
+                var SerialNo = Guid.NewGuid().ToString();
+                var waitObject = CreateAndAddWaitObject(SerialNo);
+                await startSendTaskFunc(SerialNo);
+                message = await WaitResultAsync(SerialNo, waitObject: waitObject, token: token);
+            }
+            while (message == null && !token.IsCancellationRequested);
+            return message;
+        }
+
+        internal async Task<object> WaitResultAsync(string serialNo, MessageResultWaitObject waitObject = null, int maxWaitSec = 65000, CancellationToken token = default)
+        {
+            if (waitObject == null)
+            {
+                waitObject = new MessageResultWaitObject();
+                asyncWaitingTasks.Add(serialNo, waitObject);
+            }
+
+            var task = waitObject.Lock.WaitAsync(token);
+            var completedTask = await Task.WhenAny(task, Task.Delay(180_000, token));
+            if (completedTask != task)
+            {
+                logger.LogWarning("wait {msg} time out", serialNo);
+                var checkResult = await mainDbService.TryGetResponseFromDb(serialNo, token);
+                if (checkResult is not null)
+                {
+                    logger.LogWarning("{msg} found in db", serialNo);
+                    return checkResult;
+                }
+                logger.LogWarning("{msg} not found in db", serialNo);
+                return null;
+            }
+            return waitObject.Result.Message;
+        }
+
+        private MessageResultWaitObject CreateAndAddWaitObject(string serialNo)
+        {
+            var waiObj = new MessageResultWaitObject();
+            asyncWaitingTasks.Add(serialNo, waiObj);
+            return waiObj;
+        }
+
+        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);
+    }
+}

+ 383 - 383
EVCB_OCPP.WSServer/Service/DbService/ConnectionLogdbService.cs

@@ -1,384 +1,384 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.WSServer.Helper;
-using EVCB_OCPP.WSServer.Service.WsService;
-using log4net;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using NLog.Fluent;
-
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using static System.Runtime.InteropServices.JavaScript.JSType;
-
-namespace EVCB_OCPP.WSServer.Service.DbService;
-
-public interface IConnectionLogdbService
-{
-    void WarmUpLog();
-    void WriteMachineLog(WsClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false);
-}
-
-public class ConnectionLogdbService : IConnectionLogdbService
-{
-    public const string LimitConfigKey = "ConnectionLogDbLimit";
-
-    public ConnectionLogdbService(
-        IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory,
-        ISqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory,
-        ILogger<ConnectionLogdbService> logger,
-        IConfiguration configuration)
-    {
-        this.connectionLogdbContextFactory = connectionLogdbContextFactory;
-        this.sqlConnectionFactory = sqlConnectionFactory;
-        this.logger = logger;
-        //connectionLogdbConnectionString = configuration.GetConnectionString("MeterValueDBContext");
-
-        var opLimit = GetLimit(configuration);
-        queueHandler = new(WriteMachineLogEF, opLimit);
-
-        InitInsertConnectonLogHandler();
-    }
-
-    private readonly IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory;
-    private readonly ISqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory;
-    private readonly ILogger<ConnectionLogdbService> logger;
-    private readonly QueueHandler<MachineLog> queueHandler;
-    //private readonly string connectionLogdbConnectionString;
-    private readonly Queue<string> _existTables = new();
-    private GroupHandler<MachineLog> insertConnectonLogHandler;
-
-    public void WarmUpLog()
-    {
-        try
-        {
-            using (var log = connectionLogdbContextFactory.CreateDbContext())
-            {
-                log.MachineConnectionLog.ToList();
-            }
-        }
-        catch (Exception ex)
-        {
-            logger.LogError(ex.Message);
-            logger.LogError(ex.StackTrace);
-            //Console.WriteLine(ex.ToString());
-        }
-    }
-
-    public void WriteMachineLog(WsClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false)
-    {
-        var log = new MachineLog(clientData, data, messageType, errorMsg, isSent);
-        //queueHandler.Enqueue(log);
-        //_ = WriteMachineLogEF(log);
-        insertConnectonLogHandler.HandleAsync(log);
-        //_ = InsertWithDapper(log);
-    }
-
-    private async Task InsertWithDapper(MachineLog log)
-    {
-        var watch = Stopwatch.StartNew();
-        long t0, t1, t2, t3;
-        var workTime = DateTime.UtcNow;
-        if (!await GetTableExist(workTime))
-        {
-            t0 = watch.ElapsedMilliseconds;
-            await WriteMachineLogEF(log);
-            watch.Stop();
-            t1 = watch.ElapsedMilliseconds;
-            if (t1 > 500)
-            {
-                logger.LogWarning("ConnectionLog InsertWithDapper {0}/{1}", t0, t1);
-            }
-            return;
-        }
-
-        t0 = watch.ElapsedMilliseconds;
-        var tableName = GetTableName(workTime);
-        string command = $"""
-            INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session)
-            VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session);
-            """;
-
-        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("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);
-        parameters.Add("IsSent", log.isSent, DbType.Boolean);
-        parameters.Add("EVSEEndPoint", log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString(), DbType.String, size: 25);
-        parameters.Add("Session", log.clientData.SessionID == null ? "123" : log.clientData.SessionID, DbType.String, size: 36);
-
-        t1 = watch.ElapsedMilliseconds;
-        using var sqlConnection = await sqlConnectionFactory.CreateAsync();
-        t2 = watch.ElapsedMilliseconds;
-        await sqlConnection.ExecuteAsync(command, parameters);
-
-        watch.Stop();
-        t3 = watch.ElapsedMilliseconds;
-        if (t3 > 1000)
-        {
-            logger.LogWarning("ConnectionLog Dapper {0}/{1}/{2}/{3}", t0, t1, t2, t3);
-        }
-    }
-
-    private void InitInsertConnectonLogHandler()
-    {
-        if (insertConnectonLogHandler is not null)
-        {
-            throw new Exception($"{nameof(InitInsertConnectonLogHandler)} should only called once");
-        }
-
-        insertConnectonLogHandler = new GroupHandler<MachineLog>(
-            BundleInsertWithDapper,
-            logger,
-            workerCnt: 20
-            );
-    }
-
-    private async Task WriteMachineLogEF(MachineLog log)
-    {
-        var watcher = Stopwatch.StartNew();
-
-        try
-        {
-            if (log.clientData == null || string.IsNullOrEmpty(log.data)) return;
-
-            if (log.clientData.ChargeBoxId == null)
-            {
-                logger.LogCritical(log.clientData.Path.ToString() + "]********************session ChargeBoxId null sessionId=" + log.clientData.SessionID);
-            }
-            string sp = "[dbo].[uspInsertMachineConnectionLog] @CreatedOn," +
-                  "@ChargeBoxId,@MessageType,@Data,@Msg,@IsSent,@EVSEEndPoint,@Session";
-            var dd = DateTime.UtcNow;
-            SqlParameter[] parameter =
-            {
-                    new SqlParameter("CreatedOn", SqlDbType.DateTime){ Value = dd },
-                    new SqlParameter("ChargeBoxId", SqlDbType.NVarChar, 50){ Value= log.clientData.ChargeBoxId==null?"unknown":log.clientData.ChargeBoxId.Replace("'","''") },
-                    new SqlParameter("MessageType", SqlDbType.NVarChar , 50){ Value =  log.messageType.Replace("'","''")},
-                    new SqlParameter("Data", SqlDbType.NVarChar, -1) { Value = log.data.Replace("'", "''") },
-                    new SqlParameter("Msg", SqlDbType.NVarChar, 200) { Value = log.errorMsg.Replace("'", "''") },
-                    new  SqlParameter("IsSent", SqlDbType.Bit) { Value = log.isSent },
-                    new  SqlParameter("EVSEEndPoint", SqlDbType.NVarChar, 25) { Value = log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString() },
-                    new  SqlParameter("Session", SqlDbType.NVarChar, 36) { Value = log.clientData.SessionID == null ? "123" : log.clientData.SessionID }
-            };
-            using (var db = await connectionLogdbContextFactory.CreateDbContextAsync())
-            {
-                await db.Database.ExecuteSqlRawAsync(sp, parameter);
-            }
-        }
-        catch (Exception ex)
-        {
-            logger.LogError(ex.ToString());
-        }
-
-        watcher.Stop();
-        if (watcher.ElapsedMilliseconds > 1000)
-        {
-            logger.LogWarning("WriteMachineLog too long {0}", watcher.ElapsedMilliseconds);
-        }
-    }
-
-    private async Task BundleInsertWithDapper(BundleHandlerData<MachineLog> bundleHandlerData)
-    {
-        var watch = Stopwatch.StartNew();
-        var times = new List<long>();
-        var workTime = DateTime.UtcNow;
-
-        var parmsList = bundleHandlerData.Datas.ToList();
-
-        if (parmsList.Count == 0)
-        {
-            return;
-        }
-
-        var candidate = parmsList[0];
-        if (!await GetTableExist(workTime))
-        {
-            times.Add(watch.ElapsedMilliseconds);
-            await WriteMachineLogEF(candidate);
-            times.Add(watch.ElapsedMilliseconds);
-
-            if (watch.ElapsedMilliseconds > 500)
-            {
-                logger.LogWarning($"ConnectionLog InsertWithDapper {string.Join("/", times)}");
-            }
-            parmsList.Remove(candidate);
-            bundleHandlerData.AddCompletedData(candidate);
-        }
-
-        times.Add(watch.ElapsedMilliseconds);
-        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
-        //using var trans = await sqlConnection.BeginTransactionAsync();
-
-        times.Add(watch.ElapsedMilliseconds);
-        var tableName = GetTableName(workTime);
-
-        string withHourIndexCommand = $"""
-        INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session, HourIndex)
-        VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session, @HourIndex);
-        """;
-
-        string command = $"""
-        INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session)
-        VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session);
-        """;
-
-        foreach (var log in parmsList)
-        {
-            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("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);
-            parameters.Add("IsSent", log.isSent, DbType.Boolean);
-            parameters.Add("EVSEEndPoint", log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString(), DbType.String, size: 25);
-            parameters.Add("Session", log.clientData.SessionID == null ? "123" : log.clientData.SessionID, DbType.String, size: 36);
-            parameters.Add("HourIndex", workTime.Hour, DbType.Int32);
-
-            try
-            {
-                await sqlConnection.ExecuteAsync(withHourIndexCommand, parameters);
-            }
-            catch
-            {
-                logger.LogInformation("Connection Log insert with HourIndex failed, insert without HourIndex");
-                await sqlConnection.ExecuteAsync(command, parameters);
-            }
-
-            bundleHandlerData.AddCompletedData(log);
-        }
-
-        times.Add(watch.ElapsedMilliseconds);
-        //await trans.CommitAsync();
-
-        watch.Stop();
-        if (watch.ElapsedMilliseconds > 1000)
-        {
-            logger.LogWarning($"MachineLog Bundle Dapper {string.Join("/", times)} count:{bundleHandlerData.Datas.Count()}");
-        }
-
-        return;
-    }
-
-    private async Task BulkInsertWithBulkCopy(IEnumerable<MachineLog> parms)
-    {
-        var watcher = Stopwatch.StartNew();
-        long t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
-
-        var parmsList = parms.ToList();
-        if (parmsList.Count == 0)
-        {
-            return;
-        }
-
-        var workTime = DateTime.UtcNow;
-        if (!await GetTableExist(workTime))
-        {
-            var candidate = parmsList.First();
-            await WriteMachineLogEF(candidate);
-            parmsList.Remove(candidate);
-        }
-
-        t0 = watcher.ElapsedMilliseconds;
-        var table = new DataTable();
-        table.Columns.Add("CreatedOn");
-        table.Columns.Add("ChargeBoxId");
-        table.Columns.Add("MessageType");
-        table.Columns.Add("Data");
-        table.Columns.Add("Msg");
-        table.Columns.Add("IsSent");
-        table.Columns.Add("EVSEEndPoint");
-        table.Columns.Add("Session");
-
-        foreach (var param in parmsList)
-        {
-            var row = table.NewRow();
-            row["CreatedOn"] = workTime;
-            row["ChargeBoxId"] = param.clientData.ChargeBoxId == null ? "unknown" : param.clientData.ChargeBoxId.Replace("'", "''");
-            row["MessageType"] = param.messageType.Replace("'", "''");
-            row["Data"] = param.data.Replace("'", "''");
-            row["Msg"] = param.errorMsg.Replace("'", "''");
-            row["IsSent"] = param.isSent;
-            row["EVSEEndPoint"] = param.clientData.Endpoint == null ? "123" : param.clientData.Endpoint.ToString();
-            row["Session"] = param.clientData.SessionID == null ? "123" : param.clientData.SessionID;
-
-            table.Rows.Add(row);
-        }
-        t1 = watcher.ElapsedMilliseconds;
-        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
-        using SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlConnection);
-        t2 = watcher.ElapsedMilliseconds;
-        sqlBulkCopy.BatchSize = parmsList.Count();
-        sqlBulkCopy.DestinationTableName = GetTableName(workTime);
-
-        sqlBulkCopy.ColumnMappings.Add("CreatedOn", "CreatedOn");
-        sqlBulkCopy.ColumnMappings.Add("ChargeBoxId", "ChargeBoxId");
-        sqlBulkCopy.ColumnMappings.Add("MessageType", "MessageType");
-        sqlBulkCopy.ColumnMappings.Add("Data", "Data");
-        sqlBulkCopy.ColumnMappings.Add("Msg", "Msg");
-        sqlBulkCopy.ColumnMappings.Add("IsSent", "IsSent");
-        sqlBulkCopy.ColumnMappings.Add("EVSEEndPoint", "EVSEEndPoint");
-        sqlBulkCopy.ColumnMappings.Add("Session", "Session");
-        t3 = watcher.ElapsedMilliseconds;
-        await sqlBulkCopy.WriteToServerAsync(table);
-
-        watcher.Stop();
-        t4 = watcher.ElapsedMilliseconds;
-
-        if (t4 > 500)
-        {
-            logger.LogWarning("ConnectionLog BulkInsertWithBulkCopy Slow {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, parms.Count());
-        }
-    }
-
-    private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
-    {
-        var tableName = GetTableName(tableDateTime);
-        if (_existTables.Contains(tableName))
-        {
-            return true;
-        }
-
-        FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
-
-        using var db = await connectionLogdbContextFactory.CreateDbContextAsync();
-        var resultList = await db.Database.SqlQuery<int>(checkTableSql)?.ToListAsync();
-
-        if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
-        {
-            _existTables.Enqueue(tableName);
-            if (_existTables.Count > 30)
-            {
-                _existTables.TryDequeue(out _);
-            }
-            return true;
-        }
-
-        return false;
-    }
-    private static string GetTableName(DateTime dateTime)
-        => $"MachineConnectionLog{dateTime:yyMMdd}";
-
-    private int GetLimit(IConfiguration configuration)
-    {
-        var limitConfig = configuration[LimitConfigKey];
-        int limit = 10;
-        if (limitConfig != default)
-        {
-            int.TryParse(limitConfig, out limit);
-        }
-        return limit;
-    }
-}
-
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Service.WsService;
+using log4net;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using NLog.Fluent;
+
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+
+namespace EVCB_OCPP.WSServer.Service.DbService;
+
+public interface IConnectionLogdbService
+{
+    void WarmUpLog();
+    void WriteMachineLog(WsClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false);
+}
+
+public class ConnectionLogdbService : IConnectionLogdbService
+{
+    public const string LimitConfigKey = "ConnectionLogDbLimit";
+
+    public ConnectionLogdbService(
+        IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory,
+        ISqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory,
+        ILogger<ConnectionLogdbService> logger,
+        IConfiguration configuration)
+    {
+        this.connectionLogdbContextFactory = connectionLogdbContextFactory;
+        this.sqlConnectionFactory = sqlConnectionFactory;
+        this.logger = logger;
+        //connectionLogdbConnectionString = configuration.GetConnectionString("MeterValueDBContext");
+
+        var opLimit = GetLimit(configuration);
+        queueHandler = new(WriteMachineLogEF, opLimit);
+
+        InitInsertConnectonLogHandler();
+    }
+
+    private readonly IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory;
+    private readonly ISqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory;
+    private readonly ILogger<ConnectionLogdbService> logger;
+    private readonly QueueHandler<MachineLog> queueHandler;
+    //private readonly string connectionLogdbConnectionString;
+    private readonly Queue<string> _existTables = new();
+    private GroupHandler<MachineLog> insertConnectonLogHandler;
+
+    public void WarmUpLog()
+    {
+        try
+        {
+            using (var log = connectionLogdbContextFactory.CreateDbContext())
+            {
+                log.MachineConnectionLog.ToList();
+            }
+        }
+        catch (Exception ex)
+        {
+            logger.LogError(ex.Message);
+            logger.LogError(ex.StackTrace);
+            //Console.WriteLine(ex.ToString());
+        }
+    }
+
+    public void WriteMachineLog(WsClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false)
+    {
+        var log = new MachineLog(clientData, data, messageType, errorMsg, isSent);
+        //queueHandler.Enqueue(log);
+        //_ = WriteMachineLogEF(log);
+        insertConnectonLogHandler.HandleAsync(log);
+        //_ = InsertWithDapper(log);
+    }
+
+    private async Task InsertWithDapper(MachineLog log)
+    {
+        var watch = Stopwatch.StartNew();
+        long t0, t1, t2, t3;
+        var workTime = DateTime.UtcNow;
+        if (!await GetTableExist(workTime))
+        {
+            t0 = watch.ElapsedMilliseconds;
+            await WriteMachineLogEF(log);
+            watch.Stop();
+            t1 = watch.ElapsedMilliseconds;
+            if (t1 > 500)
+            {
+                logger.LogWarning("ConnectionLog InsertWithDapper {0}/{1}", t0, t1);
+            }
+            return;
+        }
+
+        t0 = watch.ElapsedMilliseconds;
+        var tableName = GetTableName(workTime);
+        string command = $"""
+            INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session)
+            VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session);
+            """;
+
+        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("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);
+        parameters.Add("IsSent", log.isSent, DbType.Boolean);
+        parameters.Add("EVSEEndPoint", log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString(), DbType.String, size: 25);
+        parameters.Add("Session", log.clientData.SessionID == null ? "123" : log.clientData.SessionID, DbType.String, size: 36);
+
+        t1 = watch.ElapsedMilliseconds;
+        using var sqlConnection = await sqlConnectionFactory.CreateAsync();
+        t2 = watch.ElapsedMilliseconds;
+        await sqlConnection.ExecuteAsync(command, parameters);
+
+        watch.Stop();
+        t3 = watch.ElapsedMilliseconds;
+        if (t3 > 1000)
+        {
+            logger.LogWarning("ConnectionLog Dapper {0}/{1}/{2}/{3}", t0, t1, t2, t3);
+        }
+    }
+
+    private void InitInsertConnectonLogHandler()
+    {
+        if (insertConnectonLogHandler is not null)
+        {
+            throw new Exception($"{nameof(InitInsertConnectonLogHandler)} should only called once");
+        }
+
+        insertConnectonLogHandler = new GroupHandler<MachineLog>(
+            BundleInsertWithDapper,
+            logger,
+            workerCnt: 20
+            );
+    }
+
+    private async Task WriteMachineLogEF(MachineLog log)
+    {
+        var watcher = Stopwatch.StartNew();
+
+        try
+        {
+            if (log.clientData == null || string.IsNullOrEmpty(log.data)) return;
+
+            if (log.clientData.ChargeBoxId == null)
+            {
+                logger.LogCritical(log.clientData.Path.ToString() + "]********************session ChargeBoxId null sessionId=" + log.clientData.SessionID);
+            }
+            string sp = "[dbo].[uspInsertMachineConnectionLog] @CreatedOn," +
+                  "@ChargeBoxId,@MessageType,@Data,@Msg,@IsSent,@EVSEEndPoint,@Session";
+            var dd = DateTime.UtcNow;
+            SqlParameter[] parameter =
+            {
+                    new SqlParameter("CreatedOn", SqlDbType.DateTime){ Value = dd },
+                    new SqlParameter("ChargeBoxId", SqlDbType.NVarChar, 50){ Value= log.clientData.ChargeBoxId==null?"unknown":log.clientData.ChargeBoxId.Replace("'","''") },
+                    new SqlParameter("MessageType", SqlDbType.NVarChar , 50){ Value =  log.messageType.Replace("'","''")},
+                    new SqlParameter("Data", SqlDbType.NVarChar, -1) { Value = log.data.Replace("'", "''") },
+                    new SqlParameter("Msg", SqlDbType.NVarChar, 200) { Value = log.errorMsg.Replace("'", "''") },
+                    new  SqlParameter("IsSent", SqlDbType.Bit) { Value = log.isSent },
+                    new  SqlParameter("EVSEEndPoint", SqlDbType.NVarChar, 25) { Value = log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString() },
+                    new  SqlParameter("Session", SqlDbType.NVarChar, 36) { Value = log.clientData.SessionID == null ? "123" : log.clientData.SessionID }
+            };
+            using (var db = await connectionLogdbContextFactory.CreateDbContextAsync())
+            {
+                await db.Database.ExecuteSqlRawAsync(sp, parameter);
+            }
+        }
+        catch (Exception ex)
+        {
+            logger.LogError(ex.ToString());
+        }
+
+        watcher.Stop();
+        if (watcher.ElapsedMilliseconds > 1000)
+        {
+            logger.LogWarning("WriteMachineLog too long {0}", watcher.ElapsedMilliseconds);
+        }
+    }
+
+    private async Task BundleInsertWithDapper(BundleHandlerData<MachineLog> bundleHandlerData)
+    {
+        var watch = Stopwatch.StartNew();
+        var times = new List<long>();
+        var workTime = DateTime.UtcNow;
+
+        var parmsList = bundleHandlerData.Datas.ToList();
+
+        if (parmsList.Count == 0)
+        {
+            return;
+        }
+
+        var candidate = parmsList[0];
+        if (!await GetTableExist(workTime))
+        {
+            times.Add(watch.ElapsedMilliseconds);
+            await WriteMachineLogEF(candidate);
+            times.Add(watch.ElapsedMilliseconds);
+
+            if (watch.ElapsedMilliseconds > 500)
+            {
+                logger.LogWarning($"ConnectionLog InsertWithDapper {string.Join("/", times)}");
+            }
+            parmsList.Remove(candidate);
+            bundleHandlerData.AddCompletedData(candidate);
+        }
+
+        times.Add(watch.ElapsedMilliseconds);
+        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
+        //using var trans = await sqlConnection.BeginTransactionAsync();
+
+        times.Add(watch.ElapsedMilliseconds);
+        var tableName = GetTableName(workTime);
+
+        string withHourIndexCommand = $"""
+        INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session, HourIndex)
+        VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session, @HourIndex);
+        """;
+
+        string command = $"""
+        INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session)
+        VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session);
+        """;
+
+        foreach (var log in parmsList)
+        {
+            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("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);
+            parameters.Add("IsSent", log.isSent, DbType.Boolean);
+            parameters.Add("EVSEEndPoint", log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString(), DbType.String, size: 25);
+            parameters.Add("Session", log.clientData.SessionID == null ? "123" : log.clientData.SessionID, DbType.String, size: 36);
+            parameters.Add("HourIndex", workTime.Hour, DbType.Int32);
+
+            try
+            {
+                await sqlConnection.ExecuteAsync(withHourIndexCommand, parameters);
+            }
+            catch
+            {
+                logger.LogInformation("Connection Log insert with HourIndex failed, insert without HourIndex");
+                await sqlConnection.ExecuteAsync(command, parameters);
+            }
+
+            bundleHandlerData.AddCompletedData(log);
+        }
+
+        times.Add(watch.ElapsedMilliseconds);
+        //await trans.CommitAsync();
+
+        watch.Stop();
+        if (watch.ElapsedMilliseconds > 1000)
+        {
+            logger.LogWarning($"MachineLog Bundle Dapper {string.Join("/", times)} count:{bundleHandlerData.Datas.Count()}");
+        }
+
+        return;
+    }
+
+    private async Task BulkInsertWithBulkCopy(IEnumerable<MachineLog> parms)
+    {
+        var watcher = Stopwatch.StartNew();
+        long t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
+
+        var parmsList = parms.ToList();
+        if (parmsList.Count == 0)
+        {
+            return;
+        }
+
+        var workTime = DateTime.UtcNow;
+        if (!await GetTableExist(workTime))
+        {
+            var candidate = parmsList.First();
+            await WriteMachineLogEF(candidate);
+            parmsList.Remove(candidate);
+        }
+
+        t0 = watcher.ElapsedMilliseconds;
+        var table = new DataTable();
+        table.Columns.Add("CreatedOn");
+        table.Columns.Add("ChargeBoxId");
+        table.Columns.Add("MessageType");
+        table.Columns.Add("Data");
+        table.Columns.Add("Msg");
+        table.Columns.Add("IsSent");
+        table.Columns.Add("EVSEEndPoint");
+        table.Columns.Add("Session");
+
+        foreach (var param in parmsList)
+        {
+            var row = table.NewRow();
+            row["CreatedOn"] = workTime;
+            row["ChargeBoxId"] = param.clientData.ChargeBoxId == null ? "unknown" : param.clientData.ChargeBoxId.Replace("'", "''");
+            row["MessageType"] = param.messageType.Replace("'", "''");
+            row["Data"] = param.data.Replace("'", "''");
+            row["Msg"] = param.errorMsg.Replace("'", "''");
+            row["IsSent"] = param.isSent;
+            row["EVSEEndPoint"] = param.clientData.Endpoint == null ? "123" : param.clientData.Endpoint.ToString();
+            row["Session"] = param.clientData.SessionID == null ? "123" : param.clientData.SessionID;
+
+            table.Rows.Add(row);
+        }
+        t1 = watcher.ElapsedMilliseconds;
+        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
+        using SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlConnection);
+        t2 = watcher.ElapsedMilliseconds;
+        sqlBulkCopy.BatchSize = parmsList.Count();
+        sqlBulkCopy.DestinationTableName = GetTableName(workTime);
+
+        sqlBulkCopy.ColumnMappings.Add("CreatedOn", "CreatedOn");
+        sqlBulkCopy.ColumnMappings.Add("ChargeBoxId", "ChargeBoxId");
+        sqlBulkCopy.ColumnMappings.Add("MessageType", "MessageType");
+        sqlBulkCopy.ColumnMappings.Add("Data", "Data");
+        sqlBulkCopy.ColumnMappings.Add("Msg", "Msg");
+        sqlBulkCopy.ColumnMappings.Add("IsSent", "IsSent");
+        sqlBulkCopy.ColumnMappings.Add("EVSEEndPoint", "EVSEEndPoint");
+        sqlBulkCopy.ColumnMappings.Add("Session", "Session");
+        t3 = watcher.ElapsedMilliseconds;
+        await sqlBulkCopy.WriteToServerAsync(table);
+
+        watcher.Stop();
+        t4 = watcher.ElapsedMilliseconds;
+
+        if (t4 > 500)
+        {
+            logger.LogWarning("ConnectionLog BulkInsertWithBulkCopy Slow {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, parms.Count());
+        }
+    }
+
+    private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
+    {
+        var tableName = GetTableName(tableDateTime);
+        if (_existTables.Contains(tableName))
+        {
+            return true;
+        }
+
+        FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
+
+        using var db = await connectionLogdbContextFactory.CreateDbContextAsync();
+        var resultList = await db.Database.SqlQuery<int>(checkTableSql)?.ToListAsync();
+
+        if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
+        {
+            _existTables.Enqueue(tableName);
+            if (_existTables.Count > 30)
+            {
+                _existTables.TryDequeue(out _);
+            }
+            return true;
+        }
+
+        return false;
+    }
+    private static string GetTableName(DateTime dateTime)
+        => $"MachineConnectionLog{dateTime:yyMMdd}";
+
+    private int GetLimit(IConfiguration configuration)
+    {
+        var limitConfig = configuration[LimitConfigKey];
+        int limit = 10;
+        if (limitConfig != default)
+        {
+            int.TryParse(limitConfig, out limit);
+        }
+        return limit;
+    }
+}
+
 internal record MachineLog(WsClientData clientData, string data, string messageType, string errorMsg, bool isSent);

+ 1315 - 1315
EVCB_OCPP.WSServer/Service/DbService/MainDbService.cs

@@ -1,1316 +1,1316 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.Domain.Models.MainDb;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages.Core;
-using EVCB_OCPP.Packet.Messages.FirmwareManagement;
-using EVCB_OCPP.Packet.Messages.LocalAuthListManagement;
-using EVCB_OCPP.Packet.Messages.RemoteTrigger;
-using EVCB_OCPP.Packet.Messages.Reservation;
-using EVCB_OCPP.Packet.Messages.Security;
-using EVCB_OCPP.Packet.Messages.SmartCharging;
-using EVCB_OCPP.WSServer.Helper;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using OCPPPackage.Profiles;
-using System.Data;
-
-namespace EVCB_OCPP.WSServer.Service.DbService;
-
-public interface IMainDbService
-{
-    Task<string> GetMachineAuthorizationKey(string ChargeBoxId, CancellationToken token = default);
-    Task<string> GetMachineConfiguration(string ChargeBoxId, string configName, CancellationToken token = default);
-    Task<string> GetMachineHeartbeatInterval(string ChargeBoxId);
-    Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId, CancellationToken token = default);
-    Task<string> GetMachineSecurityProfile(string ChargeBoxId, CancellationToken token = default);
-    Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine);
-    Task AddOCMF(Ocmf oCMF);
-    ValueTask<ConnectorStatus> GetConnectorStatus(string ChargeBoxId, int ConnectorId);
-    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<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 FillupFinishedTimetoMachineError(string ChargeBoxId, byte ConnectorId, DateTime FinishedOn);
-
-	ValueTask<Customer> GetCustomer(string id, CancellationToken token = default);
-    ValueTask<Customer> GetCustomer(Guid id, CancellationToken token = default);
-    Task<Guid> GetCustomerIdByChargeBoxId(string chargeboxId);
-    Task<int?> TryGetDuplicatedTransactionId(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp);
-    Task<int> AddNewTransactionRecord(TransactionRecord newTransaction);
-    Task<TransactionRecord> GetTransactionForStopTransaction(int transactionId, string chargeBoxId);
-    Task UpdateTransaction(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost);
-    Task<bool> UpdateHeartBeats(IEnumerable<Machine> heartBeatsData);
-    Task<bool> UpdateHeartBeats(List<string> machineIds);
-    Task UpdateTransactionSOC(int id, string startsoc, string stopsoc);
-    Task UpdateMachineConnectionType(string chargeBoxId, int v);
-    Task<string> GetMachineConnectorType(string chargeBoxId, CancellationToken token = default);
-    Task SetMachineConnectionType(string chargeBoxId, int connectionType, CancellationToken token = default);
-    Task UpdateServerMessageUpdateTime(int table_id);
-    Task AddMachineConfiguration(string chargeBoxId, string key, string value, bool isReadOnly, bool isExist = true);
-    Task UpdateMachineConfiguration(string chargeBoxId, string item, string empty, bool isReadOnly, bool isExists = true);
-    Task<List<MachineConfigurations>> GetMachineConfiguration(string chargeBoxId);
-    Task<object> TryGetResponseFromDb(string msgId, CancellationToken token = default);
-}
-
-public class MainDbService : IMainDbService
-{
-    public MainDbService(
-        IDbContextFactory<MainDBContext> contextFactory,
-        ISqlConnectionFactory<MainDBContext> sqlConnectionFactory,
-        IMemoryCache memoryCache,
-        IConfiguration configuration,
-        ILoggerFactory loggerFactory,
-        ILogger<MainDbService> logger)
-    {
-        this.contextFactory = contextFactory;
-        this.sqlConnectionFactory = sqlConnectionFactory;
-        this.memoryCache = memoryCache;
-        this.loggerFactory = loggerFactory;
-        this.logger = logger;
-        var startupLimit = GetStartupLimit(configuration);
-        //this.connectionString = configuration.GetConnectionString("MainDBContext");
-        startupSemaphore = new(startupLimit);
-
-        var opLimit = GetOpLimit(configuration);
-        opSemaphore = new SemaphoreSlim(opLimit);
-
-        InitUpdateConnectorStatusHandler();
-        InitUpdateMachineBasicInfoHandler();
-        InitAddServerMessageHandler();
-        InitUpdateServerMessageUpdateOnHandler();
-        InitGetMachineConfigurationHandler();
-        InitUpdateErrorFinishedOnHandler();
-    }
-
-    private const string CustomerMemCacheKeyFromat = "Customer_{0}";
-    //private const string ChargeBoxConnectorIdMemCacheKeyFromat = "Connector_{0}{1}";
-
-    private readonly IDbContextFactory<MainDBContext> contextFactory;
-    private readonly ISqlConnectionFactory<MainDBContext> sqlConnectionFactory;
-    private readonly IMemoryCache memoryCache;
-    private readonly ILoggerFactory loggerFactory;
-    private readonly ILogger<MainDbService> logger;
-
-    //private string connectionString;
-    private readonly QueueSemaphore startupSemaphore;
-    private readonly SemaphoreSlim opSemaphore;
-    private GroupHandler<StatusNotificationParam> statusNotificationHandler;
-    private GroupHandler<UpdateMachineBasicInfoParam> updateMachineBasicInfoHandler;
-    private GroupHandler<ServerMessage, string> addServerMessageHandler;
-    private GroupHandler<int> updateServerMessageUpdateOnHandler;
-    private GroupHandler<string, List<MachineConfigurations>> getMachineConfigurationHandler;
-    private GroupHandler<UpdateErrofFinishedOnParam> updateErrorFinishedOnHandler;
-
-    public async Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId, CancellationToken token = default)
-    {
-        using var semaphoreWrapper = await startupSemaphore.GetToken();
-        using var db = await contextFactory.CreateDbContextAsync(token);
-
-        var machine = await db.Machine.Where(x => x.ChargeBoxId == ChargeBoxId && x.IsDelete == false).Select(x => new { x.CustomerId, x.Id }).AsNoTracking().FirstOrDefaultAsync(token);
-        if (machine == null)
-        {
-            return new MachineAndCustomerInfo(string.Empty, Guid.Empty, "Unknown");
-        }
-        //var customerName = await db.Customer.Where(x => x.Id == machine.CustomerId).Select(x => x.Name).FirstOrDefaultAsync();
-        var customer = await GetCustomer(machine.CustomerId, token);
-        var customerName = customer?.Name;
-        return new MachineAndCustomerInfo(machine.Id, machine.CustomerId, customerName);
-    }
-
-    public Task<List<MachineConfigurations>> GetMachineConfiguration(string chargeBoxId)
-    {
-        return getMachineConfigurationHandler.HandleAsync(chargeBoxId);
-    }
-
-    public async Task<string> GetMachineConfiguration(string ChargeBoxId, string configName, CancellationToken token = default)
-    {
-        using var semaphoreWrapper = await startupSemaphore.GetToken();
-        using var db = await contextFactory.CreateDbContextAsync(token);
-        return await db.MachineConfigurations
-            .Where(x => x.ChargeBoxId == ChargeBoxId && x.ConfigureName == configName)
-            .Select(x => x.ConfigureSetting).FirstOrDefaultAsync(token);
-    }
-
-    public Task<string> GetMachineSecurityProfile(string ChargeBoxId, CancellationToken token = default)
-    {
-        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.SecurityProfile, token);
-    }
-
-    public Task<string> GetMachineAuthorizationKey(string ChargeBoxId, CancellationToken token = default)
-    {
-        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.AuthorizationKey, token);
-    }
-
-    public Task<string> GetMachineHeartbeatInterval(string ChargeBoxId)
-    {
-        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.HeartbeatInterval);
-    }
-
-    public Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine)
-    {
-        //return UpdateMachineBasicInfoEF(ChargeBoxId, machine);
-        return updateMachineBasicInfoHandler.HandleAsync(new UpdateMachineBasicInfoParam(ChargeBoxId, machine));
-    }
-
-    public async Task AddOCMF(Ocmf oCMF)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        await db.Ocmf.AddAsync(oCMF);
-        await db.SaveChangesAsync();
-    }
-
-    public async ValueTask AddConnectorStatus(
-        string ChargeBoxId, byte ConnectorId, DateTime CreatedOn, int Status,
-        int ChargePointErrorCodeId, string ErrorInfo, string VendorId, string VendorErrorCode)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        var _currentStatus = new ConnectorStatus()
-        {
-            ChargeBoxId = ChargeBoxId,
-            ConnectorId = ConnectorId,
-            CreatedOn = CreatedOn,
-            Status = Status,
-            ChargePointErrorCodeId = ChargePointErrorCodeId,
-            ErrorInfo = ErrorInfo,
-            VendorId = VendorId,
-            VendorErrorCode = VendorErrorCode,
-            Id = Guid.NewGuid().ToString()
-        };
-        await db.ConnectorStatus.AddAsync(_currentStatus);
-
-        await db.SaveChangesAsync();
-
-        //memoryCache.Set(
-        //    string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, ChargeBoxId, ConnectorId)
-        //    , _currentStatus, TimeSpan.FromHours(12));
-    }
-
-    public async ValueTask<ConnectorStatus> GetConnectorStatus(string ChargeBoxId, int ConnectorId)
-    {
-        //var key = string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, ChargeBoxId, ConnectorId);
-        //if (memoryCache.TryGetValue<ConnectorStatus>(key, out var status))
-        //{
-        //    return status;
-        //}
-
-        using var db = await contextFactory.CreateDbContextAsync();
-        var statusFromDb = await db.ConnectorStatus.Where(x => x.ChargeBoxId == ChargeBoxId
-                            && x.ConnectorId == ConnectorId).AsNoTracking().FirstOrDefaultAsync();
-
-        //memoryCache.Set(key, statusFromDb, TimeSpan.FromHours(12));
-        return statusFromDb;
-    }
-
-    public async Task UpdateConnectorStatus(string Id, ConnectorStatus Status)
-    {
-        //await statusNotificationHandler.HandleAsync(new StatusNotificationParam(Id, Status));
-        //await UpdateConnectorStatusEF(Id, Status);
-        await UpdateConnectorStatusDapper(Id, Status);
-
-        //var key = string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, Status.ChargeBoxId, Status.ConnectorId);
-        //memoryCache.Set(key, Status, TimeSpan.FromHours(12));
-        return;
-    }
-
-    public Task<Guid> GetCustomerIdByChargeBoxId(string chargeboxId)
-    {
-        //return GetCustomerIdByChargeBoxIdEF(chargeboxId);
-        return GetCustomerIdByChargeBoxIdDapper(chargeboxId);
-    }
-
-    public Task<int?> TryGetDuplicatedTransactionId(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp)
-    {
-        //return TryGetDuplicatedTransactionIdEF(chargeBoxId, customerId, connectorId, timestamp);
-        return TryGetDuplicatedTransactionIdDapper(chargeBoxId, customerId, connectorId, timestamp);
-    }
-
-
-    public ValueTask AddMachineError(byte ConnectorId, DateTime CreatedOn, int Status, string ChargeBoxId,
-        int ErrorCodeId, string ErrorInfo, int PreStatus, string VendorErrorCode, string VendorId)
-    {
-        //return AddMachineErrorEF(ConnectorId, CreatedOn, Status, ChargeBoxId, ErrorCodeId, ErrorInfo, PreStatus, VendorErrorCode, VendorId);
-        return AddMachineErrorDapper(ConnectorId, CreatedOn, Status, ChargeBoxId, ErrorCodeId, ErrorInfo, PreStatus, VendorErrorCode, VendorId);
-    }
-
-	public ValueTask FillupFinishedTimetoMachineError(string ChargeBoxId,byte ConnectorId, DateTime FinishedOn)
-	{
-		
-		return AddFinishedTimetoMachineErrorDapper(ChargeBoxId, ConnectorId, FinishedOn);
-	}
-
-	public async Task<string> AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy, DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "")
-    {
-        if (string.IsNullOrEmpty(CreatedBy))
-        {
-            CreatedBy = "Server";
-        }
-
-        if (string.IsNullOrEmpty(SerialNo))
-        {
-            SerialNo = Guid.NewGuid().ToString();
-        }
-        var _CreatedOn = CreatedOn ?? DateTime.UtcNow;
-
-        string _OutRequest = "";
-        if (OutRequest is not null)
-        {
-            _OutRequest = JsonConvert.SerializeObject(
-                OutRequest,
-                new JsonSerializerSettings()
-                {
-                    NullValueHandling = NullValueHandling.Ignore,
-                    Formatting = Formatting.None
-                });
-        }
-
-        var data = new ServerMessage()
-        {
-            ChargeBoxId = ChargeBoxId,
-            CreatedBy = CreatedBy,
-            CreatedOn = _CreatedOn,
-            OutAction = OutAction,
-            OutRequest = _OutRequest,
-            SerialNo = SerialNo,
-            InMessage = InMessage
-        };
-
-        await AddServerMessage(data);
-        return SerialNo;
-    }
-
-    public Task<string> AddServerMessage(ServerMessage message)
-    {
-        //return AddServerMessageEF(message);
-        return addServerMessageHandler.HandleAsync(message);
-        //var id = message.SerialNo;
-        //await AddServerMessageDapper(message);
-        //return id;
-    }
-
-    public ValueTask<Customer> GetCustomer(string id, CancellationToken token = default)
-        => GetCustomer(new Guid(id), token);
-
-    public async ValueTask<Customer> GetCustomer(Guid id, CancellationToken token = default)
-    {
-        var key = string.Format(CustomerMemCacheKeyFromat, id);
-        if (memoryCache.TryGetValue<Customer>(key, out var customer))
-        {
-            return customer;
-        }
-
-        Customer toReturn = null;
-        using (var db = await contextFactory.CreateDbContextAsync(token))
-        {
-            toReturn = await db.Customer.FirstOrDefaultAsync(x => x.Id == id, token);
-        }
-
-        if (toReturn is not null)
-        {
-            memoryCache.Set(key, toReturn, TimeSpan.FromSeconds(15));
-        }
-
-        return toReturn;
-    }
-
-    public Task<int> AddNewTransactionRecord(TransactionRecord newTransaction)
-    {
-        //return AddNewTransactionRecordEF(newTransaction);
-        return AddNewTransactionRecordDapper(newTransaction);
-    }
-
-    public Task<TransactionRecord> GetTransactionForStopTransaction(int transactionId, string chargeBoxId)
-    {
-        //return GetTransactionForStopTransactionEF(transactionId, chargeBoxId);
-        return GetTransactionForStopTransactionDapper(transactionId, chargeBoxId);
-    }
-
-    public Task UpdateTransaction(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost)
-    {
-        //return UpdateTransactionEF(transactionId, meterStop, stopTime, stopReasonId, stopReason, stopIdTag, receipt, cost);
-        return UpdateTransactionDapper(transactionId, meterStop, stopTime, stopReasonId, stopReason, stopIdTag, receipt, cost);
-    }
-
-    public async Task UpdateTransactionSOC(int id, string startSOC, string stopSOC)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@TransactionId", id, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@StartSOC", startSOC, DbType.String, ParameterDirection.Input, 3);
-        parameters.Add("@StopSOC", stopSOC, DbType.String, ParameterDirection.Input, 3);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        var resultCnt = await conn.ExecuteAsync("""
-            UPDATE TransactionRecord
-            SET StartSOC = @StartSOC, StopSOC = @StopSOC
-            WHERE Id = @TransactionId
-            """, parameters);
-        if (resultCnt != 1)
-        {
-            throw new Exception("Update over one columes");
-        }
-        return;
-    }
-
-    public Task UpdateServerMessageUpdateTime(int table_id)
-    {
-        return updateServerMessageUpdateOnHandler.HandleAsync(table_id);
-    }
-
-    public async Task AddMachineConfiguration(string chargeBoxId, string key, string value, bool isReadOnly, bool isExists = true)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-
-        await db.MachineConfigurations.AddAsync(new MachineConfigurations()
-        {
-            ChargeBoxId = chargeBoxId,
-            ConfigureName = key,
-            ReadOnly = isReadOnly,
-            ConfigureSetting = string.IsNullOrEmpty(value) ? string.Empty : value,
-            Exists = isExists
-        });
-
-        await db.SaveChangesAsync();
-    }
-
-    private async Task UpdateTransactionEF(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-
-        var _transaction = db.TransactionRecord.Where(x => x.Id == transactionId //&& x.ChargeBoxId == session.ChargeBoxId
-            ).FirstOrDefault();
-
-        _transaction.MeterStop = meterStop;
-        _transaction.StopTime = stopTime;
-        _transaction.StopReasonId = stopReasonId;
-        _transaction.StopReason = stopReason;
-        _transaction.StopIdTag = stopIdTag;
-        _transaction.Receipt = receipt;
-        _transaction.Cost = cost;
-
-        //await db.SaveChangesAsync();
-        await db.SaveChangesAsync();
-    }
-
-    public async Task UpdateMachineConfiguration(string chargeBoxId, string item, string value, bool isReadonly, bool isExists = true)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        var config = await db.MachineConfigurations.FirstOrDefaultAsync(x => x.ChargeBoxId == chargeBoxId && x.ConfigureName == item);
-        if (config is null)
-        {
-            return;
-        }
-        config.ConfigureSetting = value;
-        config.ReadOnly = isReadonly;
-        config.Exists = isExists;
-        await db.SaveChangesAsync();
-    }
-
-    private async Task UpdateTransactionDapper(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@TransactionId", transactionId, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@MeterStop", meterStop, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2);
-        parameters.Add("@StopTime", stopTime, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@StopReasonId", stopReasonId, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@StopReason", stopReason, DbType.String, ParameterDirection.Input, 60);
-        parameters.Add("@StopIdTag", stopIdTag, DbType.String, ParameterDirection.Input, 20);
-        parameters.Add("@Receipt", receipt, DbType.String, ParameterDirection.Input, 3000);
-        parameters.Add("@Cost", cost, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        var resultCnt = await conn.ExecuteAsync("""
-            UPDATE TransactionRecord
-            SET MeterStop = @MeterStop, StopTime = @StopTime, StopReasonId = @StopReasonId,
-            StopReason = @StopReason, StopIdTag = @StopIdTag, Receipt = @Receipt, Cost = @Cost
-            WHERE Id = @TransactionId
-            """, parameters);
-        if (resultCnt != 1)
-        {
-            throw new Exception("Update over one columes");
-        }
-        return;
-    }
-
-    public Task<bool> UpdateHeartBeats(IEnumerable<Machine> heartBeatsData)
-    {
-        //return UpdateHeartBeatsEF(heartBeatsData);
-        return UpdateHeartBeatsDapper(heartBeatsData);
-    }
-    public Task<bool> UpdateHeartBeats(List<string> machineIds)
-    {
-        return UpdateHeartBeatsDapper(machineIds);
-    }
-
-    public async Task UpdateMachineConnectionType(string chargeBoxId, int connectionType)
-    {
-        using var semaphoreWrapper = await startupSemaphore.GetToken();
-        using var db = await contextFactory.CreateDbContextAsync();
-
-        var machine = await db.Machine.Where(x => x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync();
-        if (machine != null)
-        {
-            machine.ConnectionType = connectionType;
-            await db.SaveChangesAsync();
-        }
-    }
-
-    public async Task<object> TryGetResponseFromDb(string msgId, CancellationToken token = default)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@MID", msgId, DbType.String, size: 36);
-        parameters.Add("@MT", DateTime.UtcNow.AddSeconds(-5), DbType.DateTime);
-
-        var sql = """
-            SELECT [OutAction],[InMessage]
-            FROM [ServerMessage]
-            WHERE [SerialNo] = @MID AND CreatedOn > @MT
-            """;
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        ServerMessage item = null;
-        item = await conn.QueryFirstOrDefaultAsync<ServerMessage>(new CommandDefinition(sql, parameters: parameters, cancellationToken: token));
-
-        Actions action = Actions.None;
-        if (item is null ||
-            !Enum.TryParse(item.OutAction, out action))
-        {
-            return null;
-        }
-
-        switch (action)
-        {
-            case Actions.GetConfiguration:
-                return JsonConvert.DeserializeObject<GetConfigurationConfirmation>(item.InMessage);
-            case Actions.ChangeConfiguration:
-                return JsonConvert.DeserializeObject<ChangeConfigurationConfirmation>(item.InMessage);
-            case Actions.RemoteStartTransaction:
-                return JsonConvert.DeserializeObject<RemoteStartTransactionConfirmation>(item.InMessage);
-            case Actions.RemoteStopTransaction:
-                return JsonConvert.DeserializeObject<RemoteStopTransactionConfirmation>(item.InMessage);
-            case Actions.ChangeAvailability:
-                return JsonConvert.DeserializeObject<ChangeAvailabilityConfirmation>(item.InMessage);
-            case Actions.ClearCache:
-                return JsonConvert.DeserializeObject<ClearCacheConfirmation>(item.InMessage);
-            case Actions.DataTransfer:
-                return JsonConvert.DeserializeObject<DataTransferConfirmation>(item.InMessage);
-            case Actions.Reset:
-                return JsonConvert.DeserializeObject<ResetConfirmation>(item.InMessage);
-            case Actions.UnlockConnector:
-                return JsonConvert.DeserializeObject<UnlockConnectorConfirmation>(item.InMessage);
-            case Actions.TriggerMessage:
-                return JsonConvert.DeserializeObject<TriggerMessageConfirmation>(item.InMessage);
-            case Actions.GetDiagnostics:
-                return JsonConvert.DeserializeObject<GetDiagnosticsConfirmation>(item.InMessage);
-            case Actions.UpdateFirmware:
-                return JsonConvert.DeserializeObject<UpdateFirmwareConfirmation>(item.InMessage);
-            case Actions.GetLocalListVersion:
-                return JsonConvert.DeserializeObject<GetLocalListVersionConfirmation>(item.InMessage);
-            case Actions.SendLocalList:
-                return JsonConvert.DeserializeObject<SendLocalListConfirmation>(item.InMessage);
-            case Actions.SetChargingProfile:
-                return JsonConvert.DeserializeObject<SetChargingProfileConfirmation>(item.InMessage);
-            case Actions.ClearChargingProfile:
-                return JsonConvert.DeserializeObject<ClearChargingProfileConfirmation>(item.InMessage);
-            case Actions.GetCompositeSchedule:
-                return JsonConvert.DeserializeObject<GetCompositeScheduleConfirmation>(item.InMessage);
-            case Actions.ReserveNow:
-                return JsonConvert.DeserializeObject<ReserveNowConfirmation>(item.InMessage);
-            case Actions.CancelReservation:
-                return JsonConvert.DeserializeObject<CancelReservationConfirmation>(item.InMessage);
-            case Actions.ExtendedTriggerMessage:
-                return JsonConvert.DeserializeObject<ExtendedTriggerMessageConfirmation>(item.InMessage);
-            default:
-                return null;
-        }
-    }
-
-    private void InitUpdateConnectorStatusHandler()
-    {
-        if (statusNotificationHandler is not null)
-        {
-            throw new Exception($"{nameof(InitUpdateConnectorStatusHandler)} should only called once");
-        }
-
-        statusNotificationHandler = new GroupHandler<StatusNotificationParam>(
-            handleFunc: BundleUpdateConnectorStatusDapper,
-            logger: loggerFactory.CreateLogger("StatusNotificationHandler"),
-            workerCnt: 1);
-    }
-
-    private void InitAddServerMessageHandler()
-    {
-        if (addServerMessageHandler is not null)
-        {
-            throw new Exception($"{nameof(InitAddServerMessageHandler)} should only called once");
-        }
-
-        addServerMessageHandler = new GroupHandler<ServerMessage, string>(
-            handleFunc: BundleAddServerMessage,
-            logger: loggerFactory.CreateLogger("AddServerMessageHandler"));
-    }
-
-    private void InitUpdateMachineBasicInfoHandler()
-    {
-        if (updateMachineBasicInfoHandler is not null)
-        {
-            throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once");
-        }
-
-        updateMachineBasicInfoHandler = new GroupHandler<UpdateMachineBasicInfoParam>(
-            handleFunc: BundelUpdateMachineBasicInfo,
-            logger: loggerFactory.CreateLogger("UpdateMachineBasicInfoHandler"),
-            workerCnt: 1);
-    }
-
-    private void InitUpdateServerMessageUpdateOnHandler()
-    {
-        if (updateServerMessageUpdateOnHandler is not null)
-        {
-            throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once");
-        }
-
-        updateServerMessageUpdateOnHandler = new GroupHandler<int>(
-            handleFunc: BundelUpdateServerMessageUpdateOn,
-            logger: loggerFactory.CreateLogger("UpdateServerMessageUpdateOnHandler"),
-            workerCnt: 10);
-    }
-
-    private void InitGetMachineConfigurationHandler()
-    {
-        if (getMachineConfigurationHandler is not null)
-        {
-            throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once");
-        }
-
-        getMachineConfigurationHandler = new GroupHandler<string, List<MachineConfigurations>>(
-            handleFunc: BundelGetMachineConfiguration,
-            logger: loggerFactory.CreateLogger("GetMachineConfigurationHandler"),
-            workerCnt: 10);
-    }
-
-    private void InitUpdateErrorFinishedOnHandler()
-    {
-        if (updateErrorFinishedOnHandler is not null)
-        {
-            throw new Exception($"{nameof(InitUpdateErrorFinishedOnHandler)} should only called once");
-        }
-
-        updateErrorFinishedOnHandler = new GroupHandler<UpdateErrofFinishedOnParam>(
-            handleFunc: BundelUpdateErrorFinishedOn,
-            logger: loggerFactory.CreateLogger("UpdateErrorFinishedOnHandler"),
-            workerCnt: 1);
-    }
-
-    private async Task UpdateMachineBasicInfoEF(string chargeBoxId, Machine machine)
-    {
-        using var semaphoreWrapper = await startupSemaphore.GetToken();
-        using var db = await contextFactory.CreateDbContextAsync();
-
-        var _machine = await db.Machine.FirstOrDefaultAsync(x => x.ChargeBoxId == chargeBoxId);
-        _machine.ChargeBoxSerialNumber = machine.ChargeBoxSerialNumber;
-        _machine.ChargePointSerialNumber = machine.ChargePointSerialNumber;
-        _machine.ChargePointModel = machine.ChargePointModel;
-        _machine.ChargePointVendor = machine.ChargePointVendor;
-        _machine.FwCurrentVersion = machine.FwCurrentVersion;
-        _machine.Iccid = machine.Iccid;
-        _machine.Imsi = machine.Imsi;
-        _machine.MeterSerialNumber = machine.MeterSerialNumber;
-        _machine.MeterType = machine.MeterType;
-
-        await db.SaveChangesAsync();
-
-        //using var semaphoreWrapper = await startupSemaphore.GetToken();
-    }
-
-    private async Task BundelUpdateMachineBasicInfo(BundleHandlerData<UpdateMachineBasicInfoParam> bundleHandlerData)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        using var trans = await db.Database.BeginTransactionAsync();
-
-        var pams = bundleHandlerData.Datas.DistinctBy(x => x.ChargeBoxId);
-
-        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.FwCurrentVersion = pam.machine.FwCurrentVersion;
-            _machine.Iccid = pam.machine.Iccid;
-            _machine.Imsi = pam.machine.Imsi;
-            _machine.MeterSerialNumber = pam.machine.MeterSerialNumber;
-            _machine.MeterType = pam.machine.MeterType;
-        }
-
-        await db.SaveChangesAsync();
-        await trans.CommitAsync();
-
-        bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas);
-    }
-
-    private async Task BundelUpdateServerMessageUpdateOn(BundleHandlerData<int> bundleHandlerData)
-    {
-        var ids = bundleHandlerData.Datas;
-        var sql = """
-            UPDATE [dbo].[ServerMessage]
-            SET UpdatedOn = @DateTimeNow
-            WHERE Id in @Ids
-            """;
-        DynamicParameters parameters = new DynamicParameters();
-        parameters.Add("DateTimeNow", DateTime.UtcNow, DbType.DateTime);
-        parameters.Add("Ids", ids);
-
-        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
-        var cnt = await sqlConnection.ExecuteAsync(sql, parameters);
-        if (cnt != 0 || ids.Count == 0)
-        {
-            bundleHandlerData.CompletedDatas.AddRange(ids);
-        }
-    }
-
-    private async Task BundelGetMachineConfiguration(BundleHandlerData<string, List<MachineConfigurations>> bundleHandlerData)
-    {
-        var chargeboxIds = bundleHandlerData.Datas;
-        var sql = """
-            SELECT [ChargeBoxId], [ConfigureName], [ConfigureSetting], [ReadOnly], [Exists]
-            FROM [dbo].[MachineConfigurations]
-            WHERE ChargeBoxId IN @ChargeBoxIds
-            """;
-        DynamicParameters parameters = new DynamicParameters();
-        parameters.Add("@ChargeBoxIds", chargeboxIds, direction: ParameterDirection.Input, size: 25);
-
-        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
-        var result = await sqlConnection.QueryAsync<MachineConfigurations>(sql, parameters);
-        var gReult = result.GroupBy(x => x.ChargeBoxId);
-        foreach (var g in gReult)
-        {
-            var originKey = chargeboxIds.FirstOrDefault(x=> x.ToLower() == g.Key.ToLower());
-            if (string.IsNullOrEmpty(originKey))
-            {
-                continue;
-            }
-            bundleHandlerData.AddCompletedData(originKey, g.ToList());
-        }
-        var getConfigChargeboxIds = gReult.Select(x => x.Key);
-        var emptyConfigChargeboxIds = chargeboxIds.Except(getConfigChargeboxIds);
-        foreach (var chargeboxId in emptyConfigChargeboxIds)
-        {
-            bundleHandlerData.AddCompletedData(chargeboxId, new List<MachineConfigurations>());
-        }
-    }
-
-    private async Task UpdateConnectorStatusEF(string Id, ConnectorStatus Status)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-
-        ConnectorStatus status = new() { Id = Id };
-
-        db.ChangeTracker.AutoDetectChangesEnabled = false;
-        db.ConnectorStatus.Attach(status);
-
-
-        status.CreatedOn = Status.CreatedOn;
-        status.Status = Status.Status;
-        status.ChargePointErrorCodeId = Status.ChargePointErrorCodeId;
-        status.ErrorInfo = Status.ErrorInfo;
-        status.VendorId = Status.VendorId;
-        status.VendorErrorCode = Status.VendorErrorCode;
-
-
-        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;
-
-        await db.SaveChangesAsync();
-    }
-
-    private async Task UpdateConnectorStatusDapper(string Id, ConnectorStatus Status)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@Id", Id, DbType.String, ParameterDirection.Input, 36);
-        parameters.Add("@CreatedOn", Status.CreatedOn, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@Status", Status.Status, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@ChargePointErrorCodeId", Status.ChargePointErrorCodeId, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@ErrorInfo", Status.ErrorInfo, DbType.String, ParameterDirection.Input, 50);
-        parameters.Add("@VendorId", Status.VendorId, DbType.String, ParameterDirection.Input, 255);
-        parameters.Add("@VendorErrorCode", Status.VendorErrorCode, DbType.String, ParameterDirection.Input, 100);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        await conn.ExecuteAsync("""
-            update ConnectorStatus
-            set
-            CreatedOn = @CreatedOn,
-            Status = @Status,
-            ChargePointErrorCodeId = @ChargePointErrorCodeId,
-            ErrorInfo = @ErrorInfo,
-            VendorId = @VendorId,
-            VendorErrorCode = @VendorErrorCode
-            where Id = @Id
-            """, parameters);
-    }
-
-    private async Task<Guid> GetCustomerIdByChargeBoxIdEF(string chargeboxId)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        var _CustomerId = await db.Machine.Where(x => x.ChargeBoxId == chargeboxId).Select(x => x.CustomerId).FirstOrDefaultAsync();
-        return _CustomerId;
-    }
-
-    private async Task<Guid> GetCustomerIdByChargeBoxIdDapper(string chargeboxId)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@ChargeBoxId", chargeboxId, DbType.String, ParameterDirection.Input, 50);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        var _existedTx = await conn.QueryFirstOrDefaultAsync<Guid>("""
-            select CustomerId
-            from dbo.Machine
-            where
-            ChargeBoxId = @ChargeBoxId
-            """, parameters);
-
-        return _existedTx;
-    }
-
-    private async Task<int?> TryGetDuplicatedTransactionIdEF(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        var _existedTx = await db.TransactionRecord.Where(x => x.CustomerId == customerId && x.ChargeBoxId == chargeBoxId
-                               && x.ConnectorId == connectorId && x.StartTime == timestamp).Select(x => x.Id).FirstOrDefaultAsync();
-        return _existedTx;
-    }
-
-    private async Task<int?> TryGetDuplicatedTransactionIdDapper(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
-        parameters.Add("@CustomerId", customerId, DbType.Guid, ParameterDirection.Input);
-        parameters.Add("@ConnectorId", connectorId, DbType.Int16, ParameterDirection.Input);
-        parameters.Add("@TimeStamp", timestamp, DbType.DateTime, ParameterDirection.Input);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        var _existedTx = await conn.QueryFirstOrDefaultAsync<int?>("""
-            SELECT Id
-            FROM dbo.TransactionRecord
-            WHERE
-            ChargeBoxId = @ChargeBoxId and
-            CustomerId = @CustomerId and
-            ConnectorId = @ConnectorId and
-            StartTime = @TimeStamp
-            """, parameters);
-
-        return _existedTx;
-    }
-
-    private async ValueTask AddMachineErrorEF(byte connectorId, DateTime createdOn, int status, string chargeBoxId, int errorCodeId, string errorInfo, int preStatus, string vendorErrorCode, string vendorId)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        await db.MachineError.AddAsync(new MachineError()
-        {
-            ConnectorId = connectorId,
-            CreatedOn = createdOn,
-            Status = status,
-            ChargeBoxId = chargeBoxId,
-            ErrorCodeId = errorCodeId,
-            ErrorInfo = errorInfo,
-            PreStatus = preStatus,
-            VendorErrorCode = vendorErrorCode,
-            VendorId = vendorId
-        });
-
-        await db.SaveChangesAsync();
-    }
-
-    private async ValueTask AddMachineErrorDapper(byte connectorId, DateTime createdOn, int status, string chargeBoxId, int errorCodeId, string errorInfo, int preStatus, string vendorErrorCode, string vendorId)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@ConnectorId", connectorId, DbType.Int16, ParameterDirection.Input);
-        parameters.Add("@PreStatus", preStatus, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@Status", status, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@ErrorInfo", errorInfo, DbType.String, ParameterDirection.Input, 50);
-        parameters.Add("@VendorId", vendorId, DbType.String, ParameterDirection.Input, 255);
-        parameters.Add("@CreatedOn", createdOn, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@ErrorCodeId", errorCodeId, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@VendorErrorCode", vendorErrorCode, DbType.String, ParameterDirection.Input, 100);
-        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        await conn.ExecuteAsync("""
-            INSERT INTO MachineError
-            (ConnectorId, PreStatus, Status, ErrorInfo, VendorId, CreatedOn, ErrorCodeId, VendorErrorCode, ChargeBoxId)
-            VALUES (@ConnectorId, @PreStatus, @Status, @ErrorInfo, @VendorId, @CreatedOn, @ErrorCodeId, @VendorErrorCode, @ChargeBoxId)
-            """, parameters);
-    }
-
-	private async ValueTask AddFinishedTimetoMachineErrorDapper(string chargeBoxId, byte connectorId, DateTime finishedTime)
-	{
-        var getCommand = """
-            SELECT TOP(1) Id
-            FROM [dbo].[MachineError]
-            where ChargeBoxId=@ChargeBoxId and ConnectorId=@ConnectorId
-            Order by Id desc
-            """;
-
-        var parameters = new DynamicParameters();
-		parameters.Add("@ConnectorId", connectorId, DbType.Int16, ParameterDirection.Input);	
-		parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
-        //parameters.Add("@CreatedOn", previousErrorOn, DbType.DateTime, ParameterDirection.Input, 50);
-
-        int? recordId = null;
-        using (var conn = await sqlConnectionFactory.CreateAsync())
-        {
-            recordId = await conn.QueryFirstOrDefaultAsync<int?>(getCommand, parameters);
-            if (recordId is null)
-            {
-                return;
-            }
-        }
-
-        await updateErrorFinishedOnHandler.HandleAsync(new UpdateErrofFinishedOnParam(recordId.Value, finishedTime));
-    }
-
-    private async Task BundelUpdateErrorFinishedOn(BundleHandlerData<UpdateErrofFinishedOnParam> bundleHandlerData)
-    {
-        var updateCommand = """
-            Update MachineError 
-            set FinishedOn=@FinishedOn 
-            where Id=@Id
-            """;
-
-        var pams = bundleHandlerData.Datas;
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        using var trans = await conn.BeginTransactionAsync();
-
-        DynamicParameters parameters = null;
-
-        foreach (var pam in pams)
-        {
-
-            parameters = new DynamicParameters();
-            parameters.Add("@Id", pam.Id, DbType.Int32, ParameterDirection.Input);
-            parameters.Add("@FinishedOn", pam.finishedOn, DbType.DateTime, ParameterDirection.Input);
-
-            await conn.ExecuteAsync(new CommandDefinition(
-                updateCommand,
-                parameters: parameters,
-                transaction: trans
-                ));
-        }
-
-        await trans.CommitAsync();
-
-        bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas);
-    }
-
-    private async Task BundleUpdateConnectorStatus(IEnumerable<StatusNotificationParam> statusNotifications)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        using var trans = await db.Database.BeginTransactionAsync();
-
-        statusNotifications = statusNotifications.OrderBy(x => x.Status.CreatedOn).DistinctBy(x => x.Id);
-
-        foreach (var param in statusNotifications)
-        {
-            ConnectorStatus status = new() { Id = param.Id };
-
-            //db.ChangeTracker.AutoDetectChangesEnabled = false;
-            db.ConnectorStatus.Attach(status);
-
-
-            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.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;
-
-            //await db.SaveChangesAsync();
-        }
-
-        await db.SaveChangesAsync();
-        await trans.CommitAsync();
-        //db.ChangeTracker.Clear();
-    }
-
-
-
-    private async Task BundleUpdateConnectorStatusDapper(BundleHandlerData<StatusNotificationParam> bundleHandlerData)
-    {
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        foreach (var status in bundleHandlerData.Datas)
-        {
-            var parameters = new DynamicParameters();
-            parameters.Add("@Id", status.Id, DbType.String, ParameterDirection.Input, 36);
-            parameters.Add("@CreatedOn", status.Status.CreatedOn, DbType.DateTime, ParameterDirection.Input);
-            parameters.Add("@Status", status.Status.Status, DbType.Int32, ParameterDirection.Input);
-            parameters.Add("@ChargePointErrorCodeId", status.Status.ChargePointErrorCodeId, DbType.Int32, ParameterDirection.Input);
-            parameters.Add("@ErrorInfo", status.Status.ErrorInfo, DbType.String, ParameterDirection.Input, 50);
-            parameters.Add("@VendorId", status.Status.VendorId, DbType.String, ParameterDirection.Input, 255);
-            parameters.Add("@VendorErrorCode", status.Status.VendorErrorCode, DbType.String, ParameterDirection.Input, 100);
-
-            await conn.ExecuteAsync("""
-                    update ConnectorStatus
-                    set
-                    CreatedOn = @CreatedOn,
-                    Status = @Status,
-                    ChargePointErrorCodeId = @ChargePointErrorCodeId,
-                    ErrorInfo = @ErrorInfo,
-                    VendorId = @VendorId,
-                    VendorErrorCode = @VendorErrorCode
-                    where Id = @Id
-                    """, parameters);
-            bundleHandlerData.AddCompletedData(status);
-        }
-    }
-
-    private async Task BundleAddServerMessage(BundleHandlerData<ServerMessage, string> bundleHandlerData)
-    {
-        //var sql = """
-        //    INSERT INTO [ServerMessage] ([ChargeBoxId], [CreatedBy], [CreatedOn], [InMessage], [OutAction], [OutRequest], [ReceivedOn], [SerialNo], [UpdatedOn])
-        //    OUTPUT INSERTED.Id
-        //    VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8)
-        //    """;
-
-        //using var conn = await sqlConnectionFactory.CreateAsync();
-
-        //foreach(var data in bundleHandlerData.Datas)
-        //{
-        //    var dymparam = new DynamicParameters();
-        //    dymparam.Add("@p0", data.ChargeBoxId);
-        //    dymparam.Add("@p1", data.CreatedBy);
-        //    dymparam.Add("@p2", data.CreatedOn);
-        //    dymparam.Add("@p3", data.InMessage);
-        //    dymparam.Add("@p4", data.OutAction);
-        //    dymparam.Add("@p5", data.OutRequest);
-        //    dymparam.Add("@p6", data.ReceivedOn);
-        //    dymparam.Add("@p7", data.SerialNo);
-        //    dymparam.Add("@p8", data.UpdatedOn);
-        //}
-
-        using var db = await contextFactory.CreateDbContextAsync();
-        using var trans = await db.Database.BeginTransactionAsync();
-
-        foreach (var message in bundleHandlerData.Datas)
-        {
-            await db.ServerMessage.AddAsync(message);
-        }
-
-        await db.SaveChangesAsync();
-        await trans.CommitAsync();
-
-        bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas.Select(x => new KeyValuePair<ServerMessage, string>(x, x.SerialNo)));
-    }
-
-    private async Task AddServerMessageEF(ServerMessage message)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        using var trans = await db.Database.BeginTransactionAsync();
-
-        await db.ServerMessage.AddAsync(message);
-
-        await db.SaveChangesAsync();
-        await trans.CommitAsync();
-        //db.ChangeTracker.Clear();
-    }
-
-    private async Task AddServerMessageDapper(ServerMessage message)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@SerialNo", message.SerialNo, DbType.String, ParameterDirection.Input, 36);
-        parameters.Add("@OutAction", message.OutAction, DbType.String, ParameterDirection.Input, 30);
-        parameters.Add("@OutRequest", message.OutRequest, DbType.String, ParameterDirection.Input);
-        parameters.Add("@InMessage", message.InMessage, DbType.String, ParameterDirection.Input);
-        parameters.Add("@CreatedOn", message.CreatedOn, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@CreatedBy", message.CreatedBy, DbType.String, ParameterDirection.Input, 36);
-        parameters.Add("@ReceivedOn", message.ReceivedOn, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@ChargeBoxId", message.ChargeBoxId, DbType.String, ParameterDirection.Input, 30);
-        parameters.Add("@UpdatedOn", message.UpdatedOn, DbType.DateTime, ParameterDirection.Input);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        var resultCnt = await conn.ExecuteAsync("""
-            INSERT INTO ServerMessage
-            (SerialNo, OutAction, OutRequest, InMessage, CreatedOn, CreatedBy, ReceivedOn, ChargeBoxId, UpdatedOn)
-            VALUES (@SerialNo, @OutAction, @OutRequest, @InMessage, @CreatedOn, @CreatedBy, @ReceivedOn, @ChargeBoxId, @UpdatedOn)
-            """, parameters);
-        if (resultCnt != 1)
-        {
-            throw new Exception("Insert failed");
-        }
-        return;
-    }
-
-    private async Task<int> AddNewTransactionRecordEF(TransactionRecord newTransaction)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-
-        await db.TransactionRecord.AddAsync(newTransaction);
-
-        await db.SaveChangesAsync();
-
-        return newTransaction.Id;
-    }
-
-    private async Task<int> AddNewTransactionRecordDapper(TransactionRecord newTransaction)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@ChargeBoxId", newTransaction.ChargeBoxId, DbType.String, ParameterDirection.Input, 50);
-        parameters.Add("@ConnectorId", newTransaction.ConnectorId, DbType.Int16, ParameterDirection.Input);
-        parameters.Add("@CreatedOn", newTransaction.CreatedOn, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@UpdatedOn", newTransaction.UpdatedOn, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@StartTransactionReportedOn", newTransaction.StartTransactionReportedOn, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@StopTransactionReportedOn", newTransaction.StopTransactionReportedOn, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@StartIdTag", newTransaction.StartIdTag, DbType.String, ParameterDirection.Input, 20);
-        parameters.Add("@MeterStart", newTransaction.MeterStart, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2);
-        parameters.Add("@MeterStop", newTransaction.MeterStop, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2);
-        parameters.Add("@CustomerId", newTransaction.CustomerId, DbType.Guid, ParameterDirection.Input);
-        parameters.Add("@StartTime", newTransaction.StartTime, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@StopTime", newTransaction.StopTime, DbType.DateTime, ParameterDirection.Input);
-        parameters.Add("@ReservationId", newTransaction.ReservationId, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@RetryStartTransactionTimes", newTransaction.RetryStartTransactionTimes, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@RetryStopTransactionTimes", newTransaction.RetryStopTransactionTimes, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@Fee", newTransaction.Fee, DbType.String, ParameterDirection.Input, 1500);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        var id = await conn.QuerySingleAsync<int>("""
-            INSERT INTO TransactionRecord
-            (ChargeBoxId, ConnectorId, CreatedOn, UpdatedOn, StartTransactionReportedOn, StopTransactionReportedOn,
-            StartIdTag, MeterStart, MeterStop, CustomerId, StartTime, StopTime, ReservationId, RetryStartTransactionTimes, RetryStopTransactionTimes, Fee)
-            OUTPUT INSERTED.Id
-            VALUES (@ChargeBoxId, @ConnectorId, @CreatedOn, @UpdatedOn, @StartTransactionReportedOn, @StopTransactionReportedOn,
-            @StartIdTag, @MeterStart, @MeterStop, @CustomerId, @StartTime, @StopTime, @ReservationId, @RetryStartTransactionTimes, @RetryStopTransactionTimes, @Fee)
-            """, parameters);
-        return id;
-    }
-
-    private async Task<TransactionRecord> GetTransactionForStopTransactionEF(int transactionId, string chargeBoxId)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        return await db.TransactionRecord.Where(x => x.Id == transactionId
-             && x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync();
-    }
-
-    private async Task<TransactionRecord> GetTransactionForStopTransactionDapper(int transactionId, string chargeBoxId)
-    {
-        var parameters = new DynamicParameters();
-        parameters.Add("@TransactionId", transactionId, DbType.Int32, ParameterDirection.Input);
-        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
-
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        var record = await conn.QuerySingleAsync<TransactionRecord>("""
-            SELECT Id, ConnectorId, MeterStop, MeterStart, StartTime, StopTime FROM TransactionRecord
-            WHERE Id = @TransactionId and ChargeBoxId = @ChargeBoxId 
-            """, parameters);
-        return record;
-    }
-
-    private Task BulkInsertServerMessage(IEnumerable<ServerMessage> messages)
-    {
-        var table = new DataTable();
-        table.Columns.Add("ChargeBoxId");
-        table.Columns.Add("SerialNo");
-        table.Columns.Add("OutAction");
-        table.Columns.Add("OutRequest");
-        table.Columns.Add("InMessage");
-        table.Columns.Add("CreatedOn");
-        table.Columns.Add("CreatedBy");
-        table.Columns.Add("UpdatedOn");
-        table.Columns.Add("ReceivedOn");
-
-        foreach (var param in messages)
-        {
-            var row = table.NewRow();
-            row["ChargeBoxId"] = param.ChargeBoxId;
-            row["SerialNo"] = param.SerialNo;
-            row["OutAction"] = param.OutAction;
-            row["OutRequest"] = param.OutRequest;
-            row["InMessage"] = param.InMessage;
-            row["CreatedOn"] = param.CreatedOn;
-            row["CreatedBy"] = param.CreatedBy;
-            row["UpdatedOn"] = param.UpdatedOn;
-            row["ReceivedOn"] = param.ReceivedOn;
-
-            table.Rows.Add(row);
-        }
-
-        using SqlConnection sqlConnection = sqlConnectionFactory.Create();
-        using SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlConnection);
-
-        sqlBulkCopy.BatchSize = messages.Count();
-        sqlBulkCopy.DestinationTableName = "ServerMessage";
-
-        sqlBulkCopy.ColumnMappings.Add("ChargeBoxId", "ChargeBoxId");
-        sqlBulkCopy.ColumnMappings.Add("SerialNo", "SerialNo");
-        sqlBulkCopy.ColumnMappings.Add("OutAction", "OutAction");
-        sqlBulkCopy.ColumnMappings.Add("OutRequest", "OutRequest");
-        sqlBulkCopy.ColumnMappings.Add("InMessage", "InMessage");
-        sqlBulkCopy.ColumnMappings.Add("CreatedOn", "CreatedOn");
-        sqlBulkCopy.ColumnMappings.Add("CreatedBy", "CreatedBy");
-        sqlBulkCopy.ColumnMappings.Add("UpdatedOn", "UpdatedOn");
-        sqlBulkCopy.ColumnMappings.Add("ReceivedOn", "ReceivedOn");
-
-        return sqlBulkCopy.WriteToServerAsync(table);
-    }
-
-    private int GetStartupLimit(IConfiguration configuration)
-    {
-        var limitConfig = configuration["MainDbStartupLimit"];
-        int limit = 5;
-        if (limitConfig != default)
-        {
-            int.TryParse(limitConfig, out limit);
-        }
-        return limit;
-    }
-
-    private int GetOpLimit(IConfiguration configuration)
-    {
-        var limitConfig = configuration["MainDbOpLimit"];
-        int limit = 500;
-        if (limitConfig != default)
-        {
-            int.TryParse(limitConfig, out limit);
-        }
-        return limit;
-    }
-
-    private async Task<bool> UpdateHeartBeatsDapper(IEnumerable<Machine> heartBeatsData)
-    {
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        using var trans = await conn.BeginTransactionAsync();
-
-        try
-        {
-            foreach (var data in heartBeatsData)
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@Id", data.Id, DbType.String, ParameterDirection.Input, 36);
-                parameters.Add("@HeartbeatUpdatedOn", data.HeartbeatUpdatedOn, DbType.DateTime, ParameterDirection.Input);
-                parameters.Add("@ConnectionType", data.ConnectionType, DbType.Int32, ParameterDirection.Input);
-
-                var resultCnt = await conn.ExecuteAsync("""
-                    UPDATE Machine
-                    SET HeartbeatUpdatedOn = @HeartbeatUpdatedOn, ConnectionType = @ConnectionType
-                    WHERE Id = @Id
-                    """, parameters, trans);
-                if (resultCnt != 1)
-                {
-                    throw new Exception("Update over one columes");
-                }
-            }
-            await trans.CommitAsync();
-        }
-        catch
-        {
-            logger.LogCritical("HeartBeatCheckTrigger update fail, roll back");
-            await trans.RollbackAsync();
-            return false;
-        }
-
-        return true;
-    }
-
-    private async Task<bool> UpdateHeartBeatsDapper(List<string> machineIds)
-    {
-        using var conn = await sqlConnectionFactory.CreateAsync();
-        try
-        {
-            var parameters = new DynamicParameters();
-            parameters.Add("@Ids", machineIds, size: 36);
-            parameters.Add("@HeartbeatUpdatedOn", DateTime.UtcNow, DbType.DateTime, ParameterDirection.Input);
-            var resultCnt = await conn.ExecuteAsync("""
-                UPDATE Machine
-                SET HeartbeatUpdatedOn = @HeartbeatUpdatedOn
-                WHERE Id in @Ids
-                """, parameters);
-        }
-        catch (Exception e)
-        {
-            logger.LogError(e.Message);
-            logger.LogCritical("HeartBeatCheckTrigger update fail");
-            return false;
-        }
-        return true;
-    }
-
-    private async Task<bool> UpdateHeartBeatsEF(IEnumerable<Machine> heartBeatsData)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        using var transaction = await db.Database.BeginTransactionAsync();
-
-        try
-        {
-            foreach (var data in heartBeatsData)
-            {
-                var machine = new Machine() { Id = data.Id };
-                if (machine != null)
-                {
-                    db.Machine.Attach(machine);
-                    machine.HeartbeatUpdatedOn = DateTime.UtcNow;
-                    machine.ConnectionType = data.ConnectionType;
-                    db.Entry(machine).Property(x => x.HeartbeatUpdatedOn).IsModified = true;
-                    db.Entry(machine).Property(x => x.ConnectionType).IsModified = true;
-                }
-            }
-
-            await db.SaveChangesAsync();
-            await transaction.CommitAsync();
-            db.ChangeTracker.Clear();
-        }
-        catch (Exception ex)
-        {
-            logger.LogCritical(ex, "HeartBeatCheckTrigger update fail, roll back");
-            transaction.Rollback();
-            return false;
-        }
-
-        return true;
-    }
-
-    public async Task<string> GetMachineConnectorType(string chargeBoxId, CancellationToken token = default)
-    {
-        using var db = await contextFactory.CreateDbContextAsync(token);
-
-        var machine = await db.Machine.Where(x => x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync(token);
-        if (machine == null)
-        {
-            return null;
-        }
-        return machine.ConnectorType;
-    }
-
-    public async Task SetMachineConnectionType(string chargeBoxId, int v, CancellationToken token = default)
-    {
-        using var db = await contextFactory.CreateDbContextAsync(token);
-
-        var machine = await db.Machine.Where(x => x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync(token);
-        if (machine == null)
-        {
-            return;
-        }
-        machine.ConnectionType = v;
-        return;
-    }
-}
-
-public record MachineAndCustomerInfo(string MachineId, Guid CustomerId, string CustomerName);
-public record StatusNotificationParam(string Id, ConnectorStatus Status);
-public record UpdateMachineBasicInfoParam(string ChargeBoxId, Machine machine);
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.Domain.Models.MainDb;
+using EVCB_OCPP.Packet.Features;
+using EVCB_OCPP.Packet.Messages.Core;
+using EVCB_OCPP.Packet.Messages.FirmwareManagement;
+using EVCB_OCPP.Packet.Messages.LocalAuthListManagement;
+using EVCB_OCPP.Packet.Messages.RemoteTrigger;
+using EVCB_OCPP.Packet.Messages.Reservation;
+using EVCB_OCPP.Packet.Messages.Security;
+using EVCB_OCPP.Packet.Messages.SmartCharging;
+using EVCB_OCPP.WSServer.Helper;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using OCPPPackage.Profiles;
+using System.Data;
+
+namespace EVCB_OCPP.WSServer.Service.DbService;
+
+public interface IMainDbService
+{
+    Task<string> GetMachineAuthorizationKey(string ChargeBoxId, CancellationToken token = default);
+    Task<string> GetMachineConfiguration(string ChargeBoxId, string configName, CancellationToken token = default);
+    Task<string> GetMachineHeartbeatInterval(string ChargeBoxId);
+    Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId, CancellationToken token = default);
+    Task<string> GetMachineSecurityProfile(string ChargeBoxId, CancellationToken token = default);
+    Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine);
+    Task AddOCMF(Ocmf oCMF);
+    ValueTask<ConnectorStatus> GetConnectorStatus(string ChargeBoxId, int ConnectorId);
+    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<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 FillupFinishedTimetoMachineError(string ChargeBoxId, byte ConnectorId, DateTime FinishedOn);
+
+	ValueTask<Customer> GetCustomer(string id, CancellationToken token = default);
+    ValueTask<Customer> GetCustomer(Guid id, CancellationToken token = default);
+    Task<Guid> GetCustomerIdByChargeBoxId(string chargeboxId);
+    Task<int?> TryGetDuplicatedTransactionId(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp);
+    Task<int> AddNewTransactionRecord(TransactionRecord newTransaction);
+    Task<TransactionRecord> GetTransactionForStopTransaction(int transactionId, string chargeBoxId);
+    Task UpdateTransaction(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost);
+    Task<bool> UpdateHeartBeats(IEnumerable<Machine> heartBeatsData);
+    Task<bool> UpdateHeartBeats(List<string> machineIds);
+    Task UpdateTransactionSOC(int id, string startsoc, string stopsoc);
+    Task UpdateMachineConnectionType(string chargeBoxId, int v);
+    Task<string> GetMachineConnectorType(string chargeBoxId, CancellationToken token = default);
+    Task SetMachineConnectionType(string chargeBoxId, int connectionType, CancellationToken token = default);
+    Task UpdateServerMessageUpdateTime(int table_id);
+    Task AddMachineConfiguration(string chargeBoxId, string key, string value, bool isReadOnly, bool isExist = true);
+    Task UpdateMachineConfiguration(string chargeBoxId, string item, string empty, bool isReadOnly, bool isExists = true);
+    Task<List<MachineConfigurations>> GetMachineConfiguration(string chargeBoxId);
+    Task<object> TryGetResponseFromDb(string msgId, CancellationToken token = default);
+}
+
+public class MainDbService : IMainDbService
+{
+    public MainDbService(
+        IDbContextFactory<MainDBContext> contextFactory,
+        ISqlConnectionFactory<MainDBContext> sqlConnectionFactory,
+        IMemoryCache memoryCache,
+        IConfiguration configuration,
+        ILoggerFactory loggerFactory,
+        ILogger<MainDbService> logger)
+    {
+        this.contextFactory = contextFactory;
+        this.sqlConnectionFactory = sqlConnectionFactory;
+        this.memoryCache = memoryCache;
+        this.loggerFactory = loggerFactory;
+        this.logger = logger;
+        var startupLimit = GetStartupLimit(configuration);
+        //this.connectionString = configuration.GetConnectionString("MainDBContext");
+        startupSemaphore = new(startupLimit);
+
+        var opLimit = GetOpLimit(configuration);
+        opSemaphore = new SemaphoreSlim(opLimit);
+
+        InitUpdateConnectorStatusHandler();
+        InitUpdateMachineBasicInfoHandler();
+        InitAddServerMessageHandler();
+        InitUpdateServerMessageUpdateOnHandler();
+        InitGetMachineConfigurationHandler();
+        InitUpdateErrorFinishedOnHandler();
+    }
+
+    private const string CustomerMemCacheKeyFromat = "Customer_{0}";
+    //private const string ChargeBoxConnectorIdMemCacheKeyFromat = "Connector_{0}{1}";
+
+    private readonly IDbContextFactory<MainDBContext> contextFactory;
+    private readonly ISqlConnectionFactory<MainDBContext> sqlConnectionFactory;
+    private readonly IMemoryCache memoryCache;
+    private readonly ILoggerFactory loggerFactory;
+    private readonly ILogger<MainDbService> logger;
+
+    //private string connectionString;
+    private readonly QueueSemaphore startupSemaphore;
+    private readonly SemaphoreSlim opSemaphore;
+    private GroupHandler<StatusNotificationParam> statusNotificationHandler;
+    private GroupHandler<UpdateMachineBasicInfoParam> updateMachineBasicInfoHandler;
+    private GroupHandler<ServerMessage, string> addServerMessageHandler;
+    private GroupHandler<int> updateServerMessageUpdateOnHandler;
+    private GroupHandler<string, List<MachineConfigurations>> getMachineConfigurationHandler;
+    private GroupHandler<UpdateErrofFinishedOnParam> updateErrorFinishedOnHandler;
+
+    public async Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId, CancellationToken token = default)
+    {
+        using var semaphoreWrapper = await startupSemaphore.GetToken();
+        using var db = await contextFactory.CreateDbContextAsync(token);
+
+        var machine = await db.Machine.Where(x => x.ChargeBoxId == ChargeBoxId && x.IsDelete == false).Select(x => new { x.CustomerId, x.Id }).AsNoTracking().FirstOrDefaultAsync(token);
+        if (machine == null)
+        {
+            return new MachineAndCustomerInfo(string.Empty, Guid.Empty, "Unknown");
+        }
+        //var customerName = await db.Customer.Where(x => x.Id == machine.CustomerId).Select(x => x.Name).FirstOrDefaultAsync();
+        var customer = await GetCustomer(machine.CustomerId, token);
+        var customerName = customer?.Name;
+        return new MachineAndCustomerInfo(machine.Id, machine.CustomerId, customerName);
+    }
+
+    public Task<List<MachineConfigurations>> GetMachineConfiguration(string chargeBoxId)
+    {
+        return getMachineConfigurationHandler.HandleAsync(chargeBoxId);
+    }
+
+    public async Task<string> GetMachineConfiguration(string ChargeBoxId, string configName, CancellationToken token = default)
+    {
+        using var semaphoreWrapper = await startupSemaphore.GetToken();
+        using var db = await contextFactory.CreateDbContextAsync(token);
+        return await db.MachineConfigurations
+            .Where(x => x.ChargeBoxId == ChargeBoxId && x.ConfigureName == configName)
+            .Select(x => x.ConfigureSetting).FirstOrDefaultAsync(token);
+    }
+
+    public Task<string> GetMachineSecurityProfile(string ChargeBoxId, CancellationToken token = default)
+    {
+        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.SecurityProfile, token);
+    }
+
+    public Task<string> GetMachineAuthorizationKey(string ChargeBoxId, CancellationToken token = default)
+    {
+        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.AuthorizationKey, token);
+    }
+
+    public Task<string> GetMachineHeartbeatInterval(string ChargeBoxId)
+    {
+        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.HeartbeatInterval);
+    }
+
+    public Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine)
+    {
+        //return UpdateMachineBasicInfoEF(ChargeBoxId, machine);
+        return updateMachineBasicInfoHandler.HandleAsync(new UpdateMachineBasicInfoParam(ChargeBoxId, machine));
+    }
+
+    public async Task AddOCMF(Ocmf oCMF)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        await db.Ocmf.AddAsync(oCMF);
+        await db.SaveChangesAsync();
+    }
+
+    public async ValueTask AddConnectorStatus(
+        string ChargeBoxId, byte ConnectorId, DateTime CreatedOn, int Status,
+        int ChargePointErrorCodeId, string ErrorInfo, string VendorId, string VendorErrorCode)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        var _currentStatus = new ConnectorStatus()
+        {
+            ChargeBoxId = ChargeBoxId,
+            ConnectorId = ConnectorId,
+            CreatedOn = CreatedOn,
+            Status = Status,
+            ChargePointErrorCodeId = ChargePointErrorCodeId,
+            ErrorInfo = ErrorInfo,
+            VendorId = VendorId,
+            VendorErrorCode = VendorErrorCode,
+            Id = Guid.NewGuid().ToString()
+        };
+        await db.ConnectorStatus.AddAsync(_currentStatus);
+
+        await db.SaveChangesAsync();
+
+        //memoryCache.Set(
+        //    string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, ChargeBoxId, ConnectorId)
+        //    , _currentStatus, TimeSpan.FromHours(12));
+    }
+
+    public async ValueTask<ConnectorStatus> GetConnectorStatus(string ChargeBoxId, int ConnectorId)
+    {
+        //var key = string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, ChargeBoxId, ConnectorId);
+        //if (memoryCache.TryGetValue<ConnectorStatus>(key, out var status))
+        //{
+        //    return status;
+        //}
+
+        using var db = await contextFactory.CreateDbContextAsync();
+        var statusFromDb = await db.ConnectorStatus.Where(x => x.ChargeBoxId == ChargeBoxId
+                            && x.ConnectorId == ConnectorId).AsNoTracking().FirstOrDefaultAsync();
+
+        //memoryCache.Set(key, statusFromDb, TimeSpan.FromHours(12));
+        return statusFromDb;
+    }
+
+    public async Task UpdateConnectorStatus(string Id, ConnectorStatus Status)
+    {
+        //await statusNotificationHandler.HandleAsync(new StatusNotificationParam(Id, Status));
+        //await UpdateConnectorStatusEF(Id, Status);
+        await UpdateConnectorStatusDapper(Id, Status);
+
+        //var key = string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, Status.ChargeBoxId, Status.ConnectorId);
+        //memoryCache.Set(key, Status, TimeSpan.FromHours(12));
+        return;
+    }
+
+    public Task<Guid> GetCustomerIdByChargeBoxId(string chargeboxId)
+    {
+        //return GetCustomerIdByChargeBoxIdEF(chargeboxId);
+        return GetCustomerIdByChargeBoxIdDapper(chargeboxId);
+    }
+
+    public Task<int?> TryGetDuplicatedTransactionId(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp)
+    {
+        //return TryGetDuplicatedTransactionIdEF(chargeBoxId, customerId, connectorId, timestamp);
+        return TryGetDuplicatedTransactionIdDapper(chargeBoxId, customerId, connectorId, timestamp);
+    }
+
+
+    public ValueTask AddMachineError(byte ConnectorId, DateTime CreatedOn, int Status, string ChargeBoxId,
+        int ErrorCodeId, string ErrorInfo, int PreStatus, string VendorErrorCode, string VendorId)
+    {
+        //return AddMachineErrorEF(ConnectorId, CreatedOn, Status, ChargeBoxId, ErrorCodeId, ErrorInfo, PreStatus, VendorErrorCode, VendorId);
+        return AddMachineErrorDapper(ConnectorId, CreatedOn, Status, ChargeBoxId, ErrorCodeId, ErrorInfo, PreStatus, VendorErrorCode, VendorId);
+    }
+
+	public ValueTask FillupFinishedTimetoMachineError(string ChargeBoxId,byte ConnectorId, DateTime FinishedOn)
+	{
+		
+		return AddFinishedTimetoMachineErrorDapper(ChargeBoxId, ConnectorId, FinishedOn);
+	}
+
+	public async Task<string> AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy, DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "")
+    {
+        if (string.IsNullOrEmpty(CreatedBy))
+        {
+            CreatedBy = "Server";
+        }
+
+        if (string.IsNullOrEmpty(SerialNo))
+        {
+            SerialNo = Guid.NewGuid().ToString();
+        }
+        var _CreatedOn = CreatedOn ?? DateTime.UtcNow;
+
+        string _OutRequest = "";
+        if (OutRequest is not null)
+        {
+            _OutRequest = JsonConvert.SerializeObject(
+                OutRequest,
+                new JsonSerializerSettings()
+                {
+                    NullValueHandling = NullValueHandling.Ignore,
+                    Formatting = Formatting.None
+                });
+        }
+
+        var data = new ServerMessage()
+        {
+            ChargeBoxId = ChargeBoxId,
+            CreatedBy = CreatedBy,
+            CreatedOn = _CreatedOn,
+            OutAction = OutAction,
+            OutRequest = _OutRequest,
+            SerialNo = SerialNo,
+            InMessage = InMessage
+        };
+
+        await AddServerMessage(data);
+        return SerialNo;
+    }
+
+    public Task<string> AddServerMessage(ServerMessage message)
+    {
+        //return AddServerMessageEF(message);
+        return addServerMessageHandler.HandleAsync(message);
+        //var id = message.SerialNo;
+        //await AddServerMessageDapper(message);
+        //return id;
+    }
+
+    public ValueTask<Customer> GetCustomer(string id, CancellationToken token = default)
+        => GetCustomer(new Guid(id), token);
+
+    public async ValueTask<Customer> GetCustomer(Guid id, CancellationToken token = default)
+    {
+        var key = string.Format(CustomerMemCacheKeyFromat, id);
+        if (memoryCache.TryGetValue<Customer>(key, out var customer))
+        {
+            return customer;
+        }
+
+        Customer toReturn = null;
+        using (var db = await contextFactory.CreateDbContextAsync(token))
+        {
+            toReturn = await db.Customer.FirstOrDefaultAsync(x => x.Id == id, token);
+        }
+
+        if (toReturn is not null)
+        {
+            memoryCache.Set(key, toReturn, TimeSpan.FromSeconds(15));
+        }
+
+        return toReturn;
+    }
+
+    public Task<int> AddNewTransactionRecord(TransactionRecord newTransaction)
+    {
+        //return AddNewTransactionRecordEF(newTransaction);
+        return AddNewTransactionRecordDapper(newTransaction);
+    }
+
+    public Task<TransactionRecord> GetTransactionForStopTransaction(int transactionId, string chargeBoxId)
+    {
+        //return GetTransactionForStopTransactionEF(transactionId, chargeBoxId);
+        return GetTransactionForStopTransactionDapper(transactionId, chargeBoxId);
+    }
+
+    public Task UpdateTransaction(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost)
+    {
+        //return UpdateTransactionEF(transactionId, meterStop, stopTime, stopReasonId, stopReason, stopIdTag, receipt, cost);
+        return UpdateTransactionDapper(transactionId, meterStop, stopTime, stopReasonId, stopReason, stopIdTag, receipt, cost);
+    }
+
+    public async Task UpdateTransactionSOC(int id, string startSOC, string stopSOC)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@TransactionId", id, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@StartSOC", startSOC, DbType.String, ParameterDirection.Input, 3);
+        parameters.Add("@StopSOC", stopSOC, DbType.String, ParameterDirection.Input, 3);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        var resultCnt = await conn.ExecuteAsync("""
+            UPDATE TransactionRecord
+            SET StartSOC = @StartSOC, StopSOC = @StopSOC
+            WHERE Id = @TransactionId
+            """, parameters);
+        if (resultCnt != 1)
+        {
+            throw new Exception("Update over one columes");
+        }
+        return;
+    }
+
+    public Task UpdateServerMessageUpdateTime(int table_id)
+    {
+        return updateServerMessageUpdateOnHandler.HandleAsync(table_id);
+    }
+
+    public async Task AddMachineConfiguration(string chargeBoxId, string key, string value, bool isReadOnly, bool isExists = true)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+
+        await db.MachineConfigurations.AddAsync(new MachineConfigurations()
+        {
+            ChargeBoxId = chargeBoxId,
+            ConfigureName = key,
+            ReadOnly = isReadOnly,
+            ConfigureSetting = string.IsNullOrEmpty(value) ? string.Empty : value,
+            Exists = isExists
+        });
+
+        await db.SaveChangesAsync();
+    }
+
+    private async Task UpdateTransactionEF(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+
+        var _transaction = db.TransactionRecord.Where(x => x.Id == transactionId //&& x.ChargeBoxId == session.ChargeBoxId
+            ).FirstOrDefault();
+
+        _transaction.MeterStop = meterStop;
+        _transaction.StopTime = stopTime;
+        _transaction.StopReasonId = stopReasonId;
+        _transaction.StopReason = stopReason;
+        _transaction.StopIdTag = stopIdTag;
+        _transaction.Receipt = receipt;
+        _transaction.Cost = cost;
+
+        //await db.SaveChangesAsync();
+        await db.SaveChangesAsync();
+    }
+
+    public async Task UpdateMachineConfiguration(string chargeBoxId, string item, string value, bool isReadonly, bool isExists = true)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        var config = await db.MachineConfigurations.FirstOrDefaultAsync(x => x.ChargeBoxId == chargeBoxId && x.ConfigureName == item);
+        if (config is null)
+        {
+            return;
+        }
+        config.ConfigureSetting = value;
+        config.ReadOnly = isReadonly;
+        config.Exists = isExists;
+        await db.SaveChangesAsync();
+    }
+
+    private async Task UpdateTransactionDapper(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@TransactionId", transactionId, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@MeterStop", meterStop, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2);
+        parameters.Add("@StopTime", stopTime, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@StopReasonId", stopReasonId, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@StopReason", stopReason, DbType.String, ParameterDirection.Input, 60);
+        parameters.Add("@StopIdTag", stopIdTag, DbType.String, ParameterDirection.Input, 20);
+        parameters.Add("@Receipt", receipt, DbType.String, ParameterDirection.Input, 3000);
+        parameters.Add("@Cost", cost, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        var resultCnt = await conn.ExecuteAsync("""
+            UPDATE TransactionRecord
+            SET MeterStop = @MeterStop, StopTime = @StopTime, StopReasonId = @StopReasonId,
+            StopReason = @StopReason, StopIdTag = @StopIdTag, Receipt = @Receipt, Cost = @Cost
+            WHERE Id = @TransactionId
+            """, parameters);
+        if (resultCnt != 1)
+        {
+            throw new Exception("Update over one columes");
+        }
+        return;
+    }
+
+    public Task<bool> UpdateHeartBeats(IEnumerable<Machine> heartBeatsData)
+    {
+        //return UpdateHeartBeatsEF(heartBeatsData);
+        return UpdateHeartBeatsDapper(heartBeatsData);
+    }
+    public Task<bool> UpdateHeartBeats(List<string> machineIds)
+    {
+        return UpdateHeartBeatsDapper(machineIds);
+    }
+
+    public async Task UpdateMachineConnectionType(string chargeBoxId, int connectionType)
+    {
+        using var semaphoreWrapper = await startupSemaphore.GetToken();
+        using var db = await contextFactory.CreateDbContextAsync();
+
+        var machine = await db.Machine.Where(x => x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync();
+        if (machine != null)
+        {
+            machine.ConnectionType = connectionType;
+            await db.SaveChangesAsync();
+        }
+    }
+
+    public async Task<object> TryGetResponseFromDb(string msgId, CancellationToken token = default)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@MID", msgId, DbType.String, size: 36);
+        parameters.Add("@MT", DateTime.UtcNow.AddSeconds(-5), DbType.DateTime);
+
+        var sql = """
+            SELECT [OutAction],[InMessage]
+            FROM [ServerMessage]
+            WHERE [SerialNo] = @MID AND CreatedOn > @MT
+            """;
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        ServerMessage item = null;
+        item = await conn.QueryFirstOrDefaultAsync<ServerMessage>(new CommandDefinition(sql, parameters: parameters, cancellationToken: token));
+
+        Actions action = Actions.None;
+        if (item is null ||
+            !Enum.TryParse(item.OutAction, out action))
+        {
+            return null;
+        }
+
+        switch (action)
+        {
+            case Actions.GetConfiguration:
+                return JsonConvert.DeserializeObject<GetConfigurationConfirmation>(item.InMessage);
+            case Actions.ChangeConfiguration:
+                return JsonConvert.DeserializeObject<ChangeConfigurationConfirmation>(item.InMessage);
+            case Actions.RemoteStartTransaction:
+                return JsonConvert.DeserializeObject<RemoteStartTransactionConfirmation>(item.InMessage);
+            case Actions.RemoteStopTransaction:
+                return JsonConvert.DeserializeObject<RemoteStopTransactionConfirmation>(item.InMessage);
+            case Actions.ChangeAvailability:
+                return JsonConvert.DeserializeObject<ChangeAvailabilityConfirmation>(item.InMessage);
+            case Actions.ClearCache:
+                return JsonConvert.DeserializeObject<ClearCacheConfirmation>(item.InMessage);
+            case Actions.DataTransfer:
+                return JsonConvert.DeserializeObject<DataTransferConfirmation>(item.InMessage);
+            case Actions.Reset:
+                return JsonConvert.DeserializeObject<ResetConfirmation>(item.InMessage);
+            case Actions.UnlockConnector:
+                return JsonConvert.DeserializeObject<UnlockConnectorConfirmation>(item.InMessage);
+            case Actions.TriggerMessage:
+                return JsonConvert.DeserializeObject<TriggerMessageConfirmation>(item.InMessage);
+            case Actions.GetDiagnostics:
+                return JsonConvert.DeserializeObject<GetDiagnosticsConfirmation>(item.InMessage);
+            case Actions.UpdateFirmware:
+                return JsonConvert.DeserializeObject<UpdateFirmwareConfirmation>(item.InMessage);
+            case Actions.GetLocalListVersion:
+                return JsonConvert.DeserializeObject<GetLocalListVersionConfirmation>(item.InMessage);
+            case Actions.SendLocalList:
+                return JsonConvert.DeserializeObject<SendLocalListConfirmation>(item.InMessage);
+            case Actions.SetChargingProfile:
+                return JsonConvert.DeserializeObject<SetChargingProfileConfirmation>(item.InMessage);
+            case Actions.ClearChargingProfile:
+                return JsonConvert.DeserializeObject<ClearChargingProfileConfirmation>(item.InMessage);
+            case Actions.GetCompositeSchedule:
+                return JsonConvert.DeserializeObject<GetCompositeScheduleConfirmation>(item.InMessage);
+            case Actions.ReserveNow:
+                return JsonConvert.DeserializeObject<ReserveNowConfirmation>(item.InMessage);
+            case Actions.CancelReservation:
+                return JsonConvert.DeserializeObject<CancelReservationConfirmation>(item.InMessage);
+            case Actions.ExtendedTriggerMessage:
+                return JsonConvert.DeserializeObject<ExtendedTriggerMessageConfirmation>(item.InMessage);
+            default:
+                return null;
+        }
+    }
+
+    private void InitUpdateConnectorStatusHandler()
+    {
+        if (statusNotificationHandler is not null)
+        {
+            throw new Exception($"{nameof(InitUpdateConnectorStatusHandler)} should only called once");
+        }
+
+        statusNotificationHandler = new GroupHandler<StatusNotificationParam>(
+            handleFunc: BundleUpdateConnectorStatusDapper,
+            logger: loggerFactory.CreateLogger("StatusNotificationHandler"),
+            workerCnt: 1);
+    }
+
+    private void InitAddServerMessageHandler()
+    {
+        if (addServerMessageHandler is not null)
+        {
+            throw new Exception($"{nameof(InitAddServerMessageHandler)} should only called once");
+        }
+
+        addServerMessageHandler = new GroupHandler<ServerMessage, string>(
+            handleFunc: BundleAddServerMessage,
+            logger: loggerFactory.CreateLogger("AddServerMessageHandler"));
+    }
+
+    private void InitUpdateMachineBasicInfoHandler()
+    {
+        if (updateMachineBasicInfoHandler is not null)
+        {
+            throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once");
+        }
+
+        updateMachineBasicInfoHandler = new GroupHandler<UpdateMachineBasicInfoParam>(
+            handleFunc: BundelUpdateMachineBasicInfo,
+            logger: loggerFactory.CreateLogger("UpdateMachineBasicInfoHandler"),
+            workerCnt: 1);
+    }
+
+    private void InitUpdateServerMessageUpdateOnHandler()
+    {
+        if (updateServerMessageUpdateOnHandler is not null)
+        {
+            throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once");
+        }
+
+        updateServerMessageUpdateOnHandler = new GroupHandler<int>(
+            handleFunc: BundelUpdateServerMessageUpdateOn,
+            logger: loggerFactory.CreateLogger("UpdateServerMessageUpdateOnHandler"),
+            workerCnt: 10);
+    }
+
+    private void InitGetMachineConfigurationHandler()
+    {
+        if (getMachineConfigurationHandler is not null)
+        {
+            throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once");
+        }
+
+        getMachineConfigurationHandler = new GroupHandler<string, List<MachineConfigurations>>(
+            handleFunc: BundelGetMachineConfiguration,
+            logger: loggerFactory.CreateLogger("GetMachineConfigurationHandler"),
+            workerCnt: 10);
+    }
+
+    private void InitUpdateErrorFinishedOnHandler()
+    {
+        if (updateErrorFinishedOnHandler is not null)
+        {
+            throw new Exception($"{nameof(InitUpdateErrorFinishedOnHandler)} should only called once");
+        }
+
+        updateErrorFinishedOnHandler = new GroupHandler<UpdateErrofFinishedOnParam>(
+            handleFunc: BundelUpdateErrorFinishedOn,
+            logger: loggerFactory.CreateLogger("UpdateErrorFinishedOnHandler"),
+            workerCnt: 1);
+    }
+
+    private async Task UpdateMachineBasicInfoEF(string chargeBoxId, Machine machine)
+    {
+        using var semaphoreWrapper = await startupSemaphore.GetToken();
+        using var db = await contextFactory.CreateDbContextAsync();
+
+        var _machine = await db.Machine.FirstOrDefaultAsync(x => x.ChargeBoxId == chargeBoxId);
+        _machine.ChargeBoxSerialNumber = machine.ChargeBoxSerialNumber;
+        _machine.ChargePointSerialNumber = machine.ChargePointSerialNumber;
+        _machine.ChargePointModel = machine.ChargePointModel;
+        _machine.ChargePointVendor = machine.ChargePointVendor;
+        _machine.FwCurrentVersion = machine.FwCurrentVersion;
+        _machine.Iccid = machine.Iccid;
+        _machine.Imsi = machine.Imsi;
+        _machine.MeterSerialNumber = machine.MeterSerialNumber;
+        _machine.MeterType = machine.MeterType;
+
+        await db.SaveChangesAsync();
+
+        //using var semaphoreWrapper = await startupSemaphore.GetToken();
+    }
+
+    private async Task BundelUpdateMachineBasicInfo(BundleHandlerData<UpdateMachineBasicInfoParam> bundleHandlerData)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        using var trans = await db.Database.BeginTransactionAsync();
+
+        var pams = bundleHandlerData.Datas.DistinctBy(x => x.ChargeBoxId);
+
+        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.FwCurrentVersion = pam.machine.FwCurrentVersion;
+            _machine.Iccid = pam.machine.Iccid;
+            _machine.Imsi = pam.machine.Imsi;
+            _machine.MeterSerialNumber = pam.machine.MeterSerialNumber;
+            _machine.MeterType = pam.machine.MeterType;
+        }
+
+        await db.SaveChangesAsync();
+        await trans.CommitAsync();
+
+        bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas);
+    }
+
+    private async Task BundelUpdateServerMessageUpdateOn(BundleHandlerData<int> bundleHandlerData)
+    {
+        var ids = bundleHandlerData.Datas;
+        var sql = """
+            UPDATE [dbo].[ServerMessage]
+            SET UpdatedOn = @DateTimeNow
+            WHERE Id in @Ids
+            """;
+        DynamicParameters parameters = new DynamicParameters();
+        parameters.Add("DateTimeNow", DateTime.UtcNow, DbType.DateTime);
+        parameters.Add("Ids", ids);
+
+        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
+        var cnt = await sqlConnection.ExecuteAsync(sql, parameters);
+        if (cnt != 0 || ids.Count == 0)
+        {
+            bundleHandlerData.CompletedDatas.AddRange(ids);
+        }
+    }
+
+    private async Task BundelGetMachineConfiguration(BundleHandlerData<string, List<MachineConfigurations>> bundleHandlerData)
+    {
+        var chargeboxIds = bundleHandlerData.Datas;
+        var sql = """
+            SELECT [ChargeBoxId], [ConfigureName], [ConfigureSetting], [ReadOnly], [Exists]
+            FROM [dbo].[MachineConfigurations]
+            WHERE ChargeBoxId IN @ChargeBoxIds
+            """;
+        DynamicParameters parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxIds", chargeboxIds, direction: ParameterDirection.Input, size: 25);
+
+        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
+        var result = await sqlConnection.QueryAsync<MachineConfigurations>(sql, parameters);
+        var gReult = result.GroupBy(x => x.ChargeBoxId);
+        foreach (var g in gReult)
+        {
+            var originKey = chargeboxIds.FirstOrDefault(x=> x.ToLower() == g.Key.ToLower());
+            if (string.IsNullOrEmpty(originKey))
+            {
+                continue;
+            }
+            bundleHandlerData.AddCompletedData(originKey, g.ToList());
+        }
+        var getConfigChargeboxIds = gReult.Select(x => x.Key);
+        var emptyConfigChargeboxIds = chargeboxIds.Except(getConfigChargeboxIds);
+        foreach (var chargeboxId in emptyConfigChargeboxIds)
+        {
+            bundleHandlerData.AddCompletedData(chargeboxId, new List<MachineConfigurations>());
+        }
+    }
+
+    private async Task UpdateConnectorStatusEF(string Id, ConnectorStatus Status)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+
+        ConnectorStatus status = new() { Id = Id };
+
+        db.ChangeTracker.AutoDetectChangesEnabled = false;
+        db.ConnectorStatus.Attach(status);
+
+
+        status.CreatedOn = Status.CreatedOn;
+        status.Status = Status.Status;
+        status.ChargePointErrorCodeId = Status.ChargePointErrorCodeId;
+        status.ErrorInfo = Status.ErrorInfo;
+        status.VendorId = Status.VendorId;
+        status.VendorErrorCode = Status.VendorErrorCode;
+
+
+        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;
+
+        await db.SaveChangesAsync();
+    }
+
+    private async Task UpdateConnectorStatusDapper(string Id, ConnectorStatus Status)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@Id", Id, DbType.String, ParameterDirection.Input, 36);
+        parameters.Add("@CreatedOn", Status.CreatedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@Status", Status.Status, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@ChargePointErrorCodeId", Status.ChargePointErrorCodeId, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@ErrorInfo", Status.ErrorInfo, DbType.String, ParameterDirection.Input, 50);
+        parameters.Add("@VendorId", Status.VendorId, DbType.String, ParameterDirection.Input, 255);
+        parameters.Add("@VendorErrorCode", Status.VendorErrorCode, DbType.String, ParameterDirection.Input, 100);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        await conn.ExecuteAsync("""
+            update ConnectorStatus
+            set
+            CreatedOn = @CreatedOn,
+            Status = @Status,
+            ChargePointErrorCodeId = @ChargePointErrorCodeId,
+            ErrorInfo = @ErrorInfo,
+            VendorId = @VendorId,
+            VendorErrorCode = @VendorErrorCode
+            where Id = @Id
+            """, parameters);
+    }
+
+    private async Task<Guid> GetCustomerIdByChargeBoxIdEF(string chargeboxId)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        var _CustomerId = await db.Machine.Where(x => x.ChargeBoxId == chargeboxId).Select(x => x.CustomerId).FirstOrDefaultAsync();
+        return _CustomerId;
+    }
+
+    private async Task<Guid> GetCustomerIdByChargeBoxIdDapper(string chargeboxId)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxId", chargeboxId, DbType.String, ParameterDirection.Input, 50);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        var _existedTx = await conn.QueryFirstOrDefaultAsync<Guid>("""
+            select CustomerId
+            from dbo.Machine
+            where
+            ChargeBoxId = @ChargeBoxId
+            """, parameters);
+
+        return _existedTx;
+    }
+
+    private async Task<int?> TryGetDuplicatedTransactionIdEF(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        var _existedTx = await db.TransactionRecord.Where(x => x.CustomerId == customerId && x.ChargeBoxId == chargeBoxId
+                               && x.ConnectorId == connectorId && x.StartTime == timestamp).Select(x => x.Id).FirstOrDefaultAsync();
+        return _existedTx;
+    }
+
+    private async Task<int?> TryGetDuplicatedTransactionIdDapper(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
+        parameters.Add("@CustomerId", customerId, DbType.Guid, ParameterDirection.Input);
+        parameters.Add("@ConnectorId", connectorId, DbType.Int16, ParameterDirection.Input);
+        parameters.Add("@TimeStamp", timestamp, DbType.DateTime, ParameterDirection.Input);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        var _existedTx = await conn.QueryFirstOrDefaultAsync<int?>("""
+            SELECT Id
+            FROM dbo.TransactionRecord
+            WHERE
+            ChargeBoxId = @ChargeBoxId and
+            CustomerId = @CustomerId and
+            ConnectorId = @ConnectorId and
+            StartTime = @TimeStamp
+            """, parameters);
+
+        return _existedTx;
+    }
+
+    private async ValueTask AddMachineErrorEF(byte connectorId, DateTime createdOn, int status, string chargeBoxId, int errorCodeId, string errorInfo, int preStatus, string vendorErrorCode, string vendorId)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        await db.MachineError.AddAsync(new MachineError()
+        {
+            ConnectorId = connectorId,
+            CreatedOn = createdOn,
+            Status = status,
+            ChargeBoxId = chargeBoxId,
+            ErrorCodeId = errorCodeId,
+            ErrorInfo = errorInfo,
+            PreStatus = preStatus,
+            VendorErrorCode = vendorErrorCode,
+            VendorId = vendorId
+        });
+
+        await db.SaveChangesAsync();
+    }
+
+    private async ValueTask AddMachineErrorDapper(byte connectorId, DateTime createdOn, int status, string chargeBoxId, int errorCodeId, string errorInfo, int preStatus, string vendorErrorCode, string vendorId)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@ConnectorId", connectorId, DbType.Int16, ParameterDirection.Input);
+        parameters.Add("@PreStatus", preStatus, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@Status", status, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@ErrorInfo", errorInfo, DbType.String, ParameterDirection.Input, 50);
+        parameters.Add("@VendorId", vendorId, DbType.String, ParameterDirection.Input, 255);
+        parameters.Add("@CreatedOn", createdOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@ErrorCodeId", errorCodeId, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@VendorErrorCode", vendorErrorCode, DbType.String, ParameterDirection.Input, 100);
+        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        await conn.ExecuteAsync("""
+            INSERT INTO MachineError
+            (ConnectorId, PreStatus, Status, ErrorInfo, VendorId, CreatedOn, ErrorCodeId, VendorErrorCode, ChargeBoxId)
+            VALUES (@ConnectorId, @PreStatus, @Status, @ErrorInfo, @VendorId, @CreatedOn, @ErrorCodeId, @VendorErrorCode, @ChargeBoxId)
+            """, parameters);
+    }
+
+	private async ValueTask AddFinishedTimetoMachineErrorDapper(string chargeBoxId, byte connectorId, DateTime finishedTime)
+	{
+        var getCommand = """
+            SELECT TOP(1) Id
+            FROM [dbo].[MachineError]
+            where ChargeBoxId=@ChargeBoxId and ConnectorId=@ConnectorId
+            Order by Id desc
+            """;
+
+        var parameters = new DynamicParameters();
+		parameters.Add("@ConnectorId", connectorId, DbType.Int16, ParameterDirection.Input);	
+		parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
+        //parameters.Add("@CreatedOn", previousErrorOn, DbType.DateTime, ParameterDirection.Input, 50);
+
+        int? recordId = null;
+        using (var conn = await sqlConnectionFactory.CreateAsync())
+        {
+            recordId = await conn.QueryFirstOrDefaultAsync<int?>(getCommand, parameters);
+            if (recordId is null)
+            {
+                return;
+            }
+        }
+
+        await updateErrorFinishedOnHandler.HandleAsync(new UpdateErrofFinishedOnParam(recordId.Value, finishedTime));
+    }
+
+    private async Task BundelUpdateErrorFinishedOn(BundleHandlerData<UpdateErrofFinishedOnParam> bundleHandlerData)
+    {
+        var updateCommand = """
+            Update MachineError 
+            set FinishedOn=@FinishedOn 
+            where Id=@Id
+            """;
+
+        var pams = bundleHandlerData.Datas;
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        using var trans = await conn.BeginTransactionAsync();
+
+        DynamicParameters parameters = null;
+
+        foreach (var pam in pams)
+        {
+
+            parameters = new DynamicParameters();
+            parameters.Add("@Id", pam.Id, DbType.Int32, ParameterDirection.Input);
+            parameters.Add("@FinishedOn", pam.finishedOn, DbType.DateTime, ParameterDirection.Input);
+
+            await conn.ExecuteAsync(new CommandDefinition(
+                updateCommand,
+                parameters: parameters,
+                transaction: trans
+                ));
+        }
+
+        await trans.CommitAsync();
+
+        bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas);
+    }
+
+    private async Task BundleUpdateConnectorStatus(IEnumerable<StatusNotificationParam> statusNotifications)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        using var trans = await db.Database.BeginTransactionAsync();
+
+        statusNotifications = statusNotifications.OrderBy(x => x.Status.CreatedOn).DistinctBy(x => x.Id);
+
+        foreach (var param in statusNotifications)
+        {
+            ConnectorStatus status = new() { Id = param.Id };
+
+            //db.ChangeTracker.AutoDetectChangesEnabled = false;
+            db.ConnectorStatus.Attach(status);
+
+
+            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.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;
+
+            //await db.SaveChangesAsync();
+        }
+
+        await db.SaveChangesAsync();
+        await trans.CommitAsync();
+        //db.ChangeTracker.Clear();
+    }
+
+
+
+    private async Task BundleUpdateConnectorStatusDapper(BundleHandlerData<StatusNotificationParam> bundleHandlerData)
+    {
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        foreach (var status in bundleHandlerData.Datas)
+        {
+            var parameters = new DynamicParameters();
+            parameters.Add("@Id", status.Id, DbType.String, ParameterDirection.Input, 36);
+            parameters.Add("@CreatedOn", status.Status.CreatedOn, DbType.DateTime, ParameterDirection.Input);
+            parameters.Add("@Status", status.Status.Status, DbType.Int32, ParameterDirection.Input);
+            parameters.Add("@ChargePointErrorCodeId", status.Status.ChargePointErrorCodeId, DbType.Int32, ParameterDirection.Input);
+            parameters.Add("@ErrorInfo", status.Status.ErrorInfo, DbType.String, ParameterDirection.Input, 50);
+            parameters.Add("@VendorId", status.Status.VendorId, DbType.String, ParameterDirection.Input, 255);
+            parameters.Add("@VendorErrorCode", status.Status.VendorErrorCode, DbType.String, ParameterDirection.Input, 100);
+
+            await conn.ExecuteAsync("""
+                    update ConnectorStatus
+                    set
+                    CreatedOn = @CreatedOn,
+                    Status = @Status,
+                    ChargePointErrorCodeId = @ChargePointErrorCodeId,
+                    ErrorInfo = @ErrorInfo,
+                    VendorId = @VendorId,
+                    VendorErrorCode = @VendorErrorCode
+                    where Id = @Id
+                    """, parameters);
+            bundleHandlerData.AddCompletedData(status);
+        }
+    }
+
+    private async Task BundleAddServerMessage(BundleHandlerData<ServerMessage, string> bundleHandlerData)
+    {
+        //var sql = """
+        //    INSERT INTO [ServerMessage] ([ChargeBoxId], [CreatedBy], [CreatedOn], [InMessage], [OutAction], [OutRequest], [ReceivedOn], [SerialNo], [UpdatedOn])
+        //    OUTPUT INSERTED.Id
+        //    VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8)
+        //    """;
+
+        //using var conn = await sqlConnectionFactory.CreateAsync();
+
+        //foreach(var data in bundleHandlerData.Datas)
+        //{
+        //    var dymparam = new DynamicParameters();
+        //    dymparam.Add("@p0", data.ChargeBoxId);
+        //    dymparam.Add("@p1", data.CreatedBy);
+        //    dymparam.Add("@p2", data.CreatedOn);
+        //    dymparam.Add("@p3", data.InMessage);
+        //    dymparam.Add("@p4", data.OutAction);
+        //    dymparam.Add("@p5", data.OutRequest);
+        //    dymparam.Add("@p6", data.ReceivedOn);
+        //    dymparam.Add("@p7", data.SerialNo);
+        //    dymparam.Add("@p8", data.UpdatedOn);
+        //}
+
+        using var db = await contextFactory.CreateDbContextAsync();
+        using var trans = await db.Database.BeginTransactionAsync();
+
+        foreach (var message in bundleHandlerData.Datas)
+        {
+            await db.ServerMessage.AddAsync(message);
+        }
+
+        await db.SaveChangesAsync();
+        await trans.CommitAsync();
+
+        bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas.Select(x => new KeyValuePair<ServerMessage, string>(x, x.SerialNo)));
+    }
+
+    private async Task AddServerMessageEF(ServerMessage message)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        using var trans = await db.Database.BeginTransactionAsync();
+
+        await db.ServerMessage.AddAsync(message);
+
+        await db.SaveChangesAsync();
+        await trans.CommitAsync();
+        //db.ChangeTracker.Clear();
+    }
+
+    private async Task AddServerMessageDapper(ServerMessage message)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@SerialNo", message.SerialNo, DbType.String, ParameterDirection.Input, 36);
+        parameters.Add("@OutAction", message.OutAction, DbType.String, ParameterDirection.Input, 30);
+        parameters.Add("@OutRequest", message.OutRequest, DbType.String, ParameterDirection.Input);
+        parameters.Add("@InMessage", message.InMessage, DbType.String, ParameterDirection.Input);
+        parameters.Add("@CreatedOn", message.CreatedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@CreatedBy", message.CreatedBy, DbType.String, ParameterDirection.Input, 36);
+        parameters.Add("@ReceivedOn", message.ReceivedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@ChargeBoxId", message.ChargeBoxId, DbType.String, ParameterDirection.Input, 30);
+        parameters.Add("@UpdatedOn", message.UpdatedOn, DbType.DateTime, ParameterDirection.Input);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        var resultCnt = await conn.ExecuteAsync("""
+            INSERT INTO ServerMessage
+            (SerialNo, OutAction, OutRequest, InMessage, CreatedOn, CreatedBy, ReceivedOn, ChargeBoxId, UpdatedOn)
+            VALUES (@SerialNo, @OutAction, @OutRequest, @InMessage, @CreatedOn, @CreatedBy, @ReceivedOn, @ChargeBoxId, @UpdatedOn)
+            """, parameters);
+        if (resultCnt != 1)
+        {
+            throw new Exception("Insert failed");
+        }
+        return;
+    }
+
+    private async Task<int> AddNewTransactionRecordEF(TransactionRecord newTransaction)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+
+        await db.TransactionRecord.AddAsync(newTransaction);
+
+        await db.SaveChangesAsync();
+
+        return newTransaction.Id;
+    }
+
+    private async Task<int> AddNewTransactionRecordDapper(TransactionRecord newTransaction)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxId", newTransaction.ChargeBoxId, DbType.String, ParameterDirection.Input, 50);
+        parameters.Add("@ConnectorId", newTransaction.ConnectorId, DbType.Int16, ParameterDirection.Input);
+        parameters.Add("@CreatedOn", newTransaction.CreatedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@UpdatedOn", newTransaction.UpdatedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@StartTransactionReportedOn", newTransaction.StartTransactionReportedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@StopTransactionReportedOn", newTransaction.StopTransactionReportedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@StartIdTag", newTransaction.StartIdTag, DbType.String, ParameterDirection.Input, 20);
+        parameters.Add("@MeterStart", newTransaction.MeterStart, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2);
+        parameters.Add("@MeterStop", newTransaction.MeterStop, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2);
+        parameters.Add("@CustomerId", newTransaction.CustomerId, DbType.Guid, ParameterDirection.Input);
+        parameters.Add("@StartTime", newTransaction.StartTime, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@StopTime", newTransaction.StopTime, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@ReservationId", newTransaction.ReservationId, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@RetryStartTransactionTimes", newTransaction.RetryStartTransactionTimes, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@RetryStopTransactionTimes", newTransaction.RetryStopTransactionTimes, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@Fee", newTransaction.Fee, DbType.String, ParameterDirection.Input, 1500);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        var id = await conn.QuerySingleAsync<int>("""
+            INSERT INTO TransactionRecord
+            (ChargeBoxId, ConnectorId, CreatedOn, UpdatedOn, StartTransactionReportedOn, StopTransactionReportedOn,
+            StartIdTag, MeterStart, MeterStop, CustomerId, StartTime, StopTime, ReservationId, RetryStartTransactionTimes, RetryStopTransactionTimes, Fee)
+            OUTPUT INSERTED.Id
+            VALUES (@ChargeBoxId, @ConnectorId, @CreatedOn, @UpdatedOn, @StartTransactionReportedOn, @StopTransactionReportedOn,
+            @StartIdTag, @MeterStart, @MeterStop, @CustomerId, @StartTime, @StopTime, @ReservationId, @RetryStartTransactionTimes, @RetryStopTransactionTimes, @Fee)
+            """, parameters);
+        return id;
+    }
+
+    private async Task<TransactionRecord> GetTransactionForStopTransactionEF(int transactionId, string chargeBoxId)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        return await db.TransactionRecord.Where(x => x.Id == transactionId
+             && x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync();
+    }
+
+    private async Task<TransactionRecord> GetTransactionForStopTransactionDapper(int transactionId, string chargeBoxId)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@TransactionId", transactionId, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
+
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        var record = await conn.QuerySingleAsync<TransactionRecord>("""
+            SELECT Id, ConnectorId, MeterStop, MeterStart, StartTime, StopTime FROM TransactionRecord
+            WHERE Id = @TransactionId and ChargeBoxId = @ChargeBoxId 
+            """, parameters);
+        return record;
+    }
+
+    private Task BulkInsertServerMessage(IEnumerable<ServerMessage> messages)
+    {
+        var table = new DataTable();
+        table.Columns.Add("ChargeBoxId");
+        table.Columns.Add("SerialNo");
+        table.Columns.Add("OutAction");
+        table.Columns.Add("OutRequest");
+        table.Columns.Add("InMessage");
+        table.Columns.Add("CreatedOn");
+        table.Columns.Add("CreatedBy");
+        table.Columns.Add("UpdatedOn");
+        table.Columns.Add("ReceivedOn");
+
+        foreach (var param in messages)
+        {
+            var row = table.NewRow();
+            row["ChargeBoxId"] = param.ChargeBoxId;
+            row["SerialNo"] = param.SerialNo;
+            row["OutAction"] = param.OutAction;
+            row["OutRequest"] = param.OutRequest;
+            row["InMessage"] = param.InMessage;
+            row["CreatedOn"] = param.CreatedOn;
+            row["CreatedBy"] = param.CreatedBy;
+            row["UpdatedOn"] = param.UpdatedOn;
+            row["ReceivedOn"] = param.ReceivedOn;
+
+            table.Rows.Add(row);
+        }
+
+        using SqlConnection sqlConnection = sqlConnectionFactory.Create();
+        using SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlConnection);
+
+        sqlBulkCopy.BatchSize = messages.Count();
+        sqlBulkCopy.DestinationTableName = "ServerMessage";
+
+        sqlBulkCopy.ColumnMappings.Add("ChargeBoxId", "ChargeBoxId");
+        sqlBulkCopy.ColumnMappings.Add("SerialNo", "SerialNo");
+        sqlBulkCopy.ColumnMappings.Add("OutAction", "OutAction");
+        sqlBulkCopy.ColumnMappings.Add("OutRequest", "OutRequest");
+        sqlBulkCopy.ColumnMappings.Add("InMessage", "InMessage");
+        sqlBulkCopy.ColumnMappings.Add("CreatedOn", "CreatedOn");
+        sqlBulkCopy.ColumnMappings.Add("CreatedBy", "CreatedBy");
+        sqlBulkCopy.ColumnMappings.Add("UpdatedOn", "UpdatedOn");
+        sqlBulkCopy.ColumnMappings.Add("ReceivedOn", "ReceivedOn");
+
+        return sqlBulkCopy.WriteToServerAsync(table);
+    }
+
+    private int GetStartupLimit(IConfiguration configuration)
+    {
+        var limitConfig = configuration["MainDbStartupLimit"];
+        int limit = 5;
+        if (limitConfig != default)
+        {
+            int.TryParse(limitConfig, out limit);
+        }
+        return limit;
+    }
+
+    private int GetOpLimit(IConfiguration configuration)
+    {
+        var limitConfig = configuration["MainDbOpLimit"];
+        int limit = 500;
+        if (limitConfig != default)
+        {
+            int.TryParse(limitConfig, out limit);
+        }
+        return limit;
+    }
+
+    private async Task<bool> UpdateHeartBeatsDapper(IEnumerable<Machine> heartBeatsData)
+    {
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        using var trans = await conn.BeginTransactionAsync();
+
+        try
+        {
+            foreach (var data in heartBeatsData)
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@Id", data.Id, DbType.String, ParameterDirection.Input, 36);
+                parameters.Add("@HeartbeatUpdatedOn", data.HeartbeatUpdatedOn, DbType.DateTime, ParameterDirection.Input);
+                parameters.Add("@ConnectionType", data.ConnectionType, DbType.Int32, ParameterDirection.Input);
+
+                var resultCnt = await conn.ExecuteAsync("""
+                    UPDATE Machine
+                    SET HeartbeatUpdatedOn = @HeartbeatUpdatedOn, ConnectionType = @ConnectionType
+                    WHERE Id = @Id
+                    """, parameters, trans);
+                if (resultCnt != 1)
+                {
+                    throw new Exception("Update over one columes");
+                }
+            }
+            await trans.CommitAsync();
+        }
+        catch
+        {
+            logger.LogCritical("HeartBeatCheckTrigger update fail, roll back");
+            await trans.RollbackAsync();
+            return false;
+        }
+
+        return true;
+    }
+
+    private async Task<bool> UpdateHeartBeatsDapper(List<string> machineIds)
+    {
+        using var conn = await sqlConnectionFactory.CreateAsync();
+        try
+        {
+            var parameters = new DynamicParameters();
+            parameters.Add("@Ids", machineIds, size: 36);
+            parameters.Add("@HeartbeatUpdatedOn", DateTime.UtcNow, DbType.DateTime, ParameterDirection.Input);
+            var resultCnt = await conn.ExecuteAsync("""
+                UPDATE Machine
+                SET HeartbeatUpdatedOn = @HeartbeatUpdatedOn
+                WHERE Id in @Ids
+                """, parameters);
+        }
+        catch (Exception e)
+        {
+            logger.LogError(e.Message);
+            logger.LogCritical("HeartBeatCheckTrigger update fail");
+            return false;
+        }
+        return true;
+    }
+
+    private async Task<bool> UpdateHeartBeatsEF(IEnumerable<Machine> heartBeatsData)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        using var transaction = await db.Database.BeginTransactionAsync();
+
+        try
+        {
+            foreach (var data in heartBeatsData)
+            {
+                var machine = new Machine() { Id = data.Id };
+                if (machine != null)
+                {
+                    db.Machine.Attach(machine);
+                    machine.HeartbeatUpdatedOn = DateTime.UtcNow;
+                    machine.ConnectionType = data.ConnectionType;
+                    db.Entry(machine).Property(x => x.HeartbeatUpdatedOn).IsModified = true;
+                    db.Entry(machine).Property(x => x.ConnectionType).IsModified = true;
+                }
+            }
+
+            await db.SaveChangesAsync();
+            await transaction.CommitAsync();
+            db.ChangeTracker.Clear();
+        }
+        catch (Exception ex)
+        {
+            logger.LogCritical(ex, "HeartBeatCheckTrigger update fail, roll back");
+            transaction.Rollback();
+            return false;
+        }
+
+        return true;
+    }
+
+    public async Task<string> GetMachineConnectorType(string chargeBoxId, CancellationToken token = default)
+    {
+        using var db = await contextFactory.CreateDbContextAsync(token);
+
+        var machine = await db.Machine.Where(x => x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync(token);
+        if (machine == null)
+        {
+            return null;
+        }
+        return machine.ConnectorType;
+    }
+
+    public async Task SetMachineConnectionType(string chargeBoxId, int v, CancellationToken token = default)
+    {
+        using var db = await contextFactory.CreateDbContextAsync(token);
+
+        var machine = await db.Machine.Where(x => x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync(token);
+        if (machine == null)
+        {
+            return;
+        }
+        machine.ConnectionType = v;
+        return;
+    }
+}
+
+public record MachineAndCustomerInfo(string MachineId, Guid CustomerId, string CustomerName);
+public record StatusNotificationParam(string Id, ConnectorStatus Status);
+public record UpdateMachineBasicInfoParam(string ChargeBoxId, Machine machine);
 public record UpdateErrofFinishedOnParam(int Id, DateTime finishedOn);

+ 418 - 418
EVCB_OCPP.WSServer/Service/DbService/MeterValueDbService.cs

@@ -1,419 +1,419 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.WSServer.Dto;
-using EVCB_OCPP.WSServer.Helper;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using System.Data;
-using System.Data.Common;
-using System.Diagnostics;
-
-namespace EVCB_OCPP.WSServer.Service.DbService;
-
-public class MeterValueDbService
-{
-    private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
-    private readonly ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory;
-    private readonly ILoggerFactory loggerFactory;
-    private readonly MeterValueGroupSingleHandler meterValueGroupSingleHandler;
-    private readonly QueueSemaphore insertSemaphore;
-    private readonly ILogger logger;
-    private readonly Queue<string> _existTables = new();
-    private GroupHandler<InsertMeterValueParam> insertMeterValueHandler;
-
-    public MeterValueDbService(
-        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
-        ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory,
-        ILogger<MeterValueDbService> logger,
-        ILoggerFactory loggerFactory,
-        IConfiguration configuration
-        //, MeterValueGroupSingleHandler meterValueGroupSingleHandler
-        )
-    {
-        this.meterValueDbContextFactory = meterValueDbContextFactory;
-        this.sqlConnectionFactory = sqlConnectionFactory;
-        this.loggerFactory = loggerFactory;
-        //this.meterValueGroupSingleHandler = meterValueGroupSingleHandler;
-        //this.meterValueConnectionString = configuration.GetConnectionString("MeterValueDBContext");
-        this.logger = logger;
-
-        InitInsertMeterValueHandler();
-
-        var insertLimit = GetInsertLimit(configuration);
-        insertSemaphore = new QueueSemaphore(insertLimit);
-
-    }
-
-    public Task InsertAsync(string chargeBoxId, byte connectorId, decimal value, DateTime createdOn
-            , int contextId, int formatId, int measurandId, int phaseId
-            , int locationId, int unitId, int transactionId)
-    {
-        var param = new InsertMeterValueParam(chargeBoxId, connectorId, value, createdOn, contextId, formatId, measurandId, phaseId, locationId, unitId, transactionId);
-
-        //return insertMeterValueHandler.HandleAsync(param);
-        return InsertAsync(param);
-        //return meterValueGroupSingleHandler.HandleAsync(param);
-    }
-
-    public Task InsertAsync(InsertMeterValueParam param)
-    {
-        //return InsertWithEF(param);
-        return InsertWithDapper(param);
-    }
-
-    public Task InsertBundleAsync(IEnumerable<InsertMeterValueParam> param)
-    {
-        return BundleInsertWithDapper(param);
-    }
-
-    public async Task<List<int>> GetTransactionSOC(int TxId, DateTime queryDate)
-    {
-        List<int> SOCCollection = new List<int>();
-        try
-        {
-            using var conn = await sqlConnectionFactory.CreateAsync();
-            var parameters = new DynamicParameters();
-            parameters.Add("@TransactionId", TxId, DbType.Int32, ParameterDirection.Input);
-
-            string strSql = $"""
-                SELECT [TransactionId],Min(Value) as MinSoC,Max(Value) as MaxSoC
-                FROM [dbo].ConnectorMeterValueRecord{queryDate.Date.ToString("yyMMdd")}
-                WHERE TransactionId=@TransactionId and MeasurandId=20 and Value!= 0 group by [TransactionId]
-                """;
-
-            var result = await conn.QueryFirstOrDefaultAsync<TransactionSoCDto>(strSql, parameters);
-            //  SOCCollection = results.Select(decimal.Parse).ToList();
-
-            if (result != null)
-            {
-                SOCCollection.Add(result.MaxSoC);
-                SOCCollection.Add(result.MinSoC);
-            }
-        }
-        catch (Exception ex)
-        {
-
-        }
-
-        return SOCCollection;
-    }
-
-    private void InitInsertMeterValueHandler()
-    {
-        if (insertMeterValueHandler is not null)
-        {
-            throw new Exception($"{nameof(InitInsertMeterValueHandler)} should only called once");
-        }
-
-        insertMeterValueHandler = new GroupHandler<InsertMeterValueParam>(
-            //BulkInsertWithBulkCopy,
-            BundleInsertWithDapper,
-            //loggerFactory.CreateLogger("InsertMeterValueHandler")
-            logger,
-            workerCnt: 1
-            );
-    }
-
-    private async Task BundleInsertWithStoredProcedure(IEnumerable<InsertMeterValueParam> parms)
-    {
-        foreach (var param in parms)
-        {
-            await InsertWithStoredProcedure(param);
-        }
-    }
-
-    private async Task InsertWithEF(InsertMeterValueParam param)
-    {
-        using var token = await insertSemaphore.GetToken();
-        using var db = 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: param.chargeBoxId
-            , connectorId: 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());
-    }
-
-    private async Task InsertWithDapper(InsertMeterValueParam param)
-    {
-        var watch = Stopwatch.StartNew();
-        long t0, t1;
-
-        if (!await GetTableExist(param.createdOn))
-        {
-            t0 = watch.ElapsedMilliseconds;
-            await InsertWithStoredProcedure(param);
-            watch.Stop();
-            t1 = watch.ElapsedMilliseconds;
-            if (t1 > 500)
-            {
-                logger.LogWarning("MeterValue InsertWithStoredProcedure {0}/{1}", t0, t1);
-            }
-            return;
-        }
-
-        t0 = watch.ElapsedMilliseconds;
-        var tableName = GetTableName(param.createdOn);
-
-        await InsertWithNoCheckDapper(tableName, param);
-
-        watch.Stop();
-        t1 = watch.ElapsedMilliseconds;
-        if (t1 > 700)
-        {
-            logger.LogWarning("MeterValue Dapper {0}/{1}", t0, t1);
-        }
-    }
-
-    private Task BundleInsertWithDapper(IEnumerable<InsertMeterValueParam> param)
-    {
-        return BundleInsertWithDapper(new BundleHandlerData<InsertMeterValueParam>(param.ToList()));
-    }
-
-    private async Task BundleInsertWithDapper(BundleHandlerData<InsertMeterValueParam> bundleHandlerData)
-    {
-        List<InsertMeterValueParam> completedParams = new();
-        var watch = Stopwatch.StartNew();
-        long t0, t1, t2, t3, t4;
-
-        var parmsList = bundleHandlerData.Datas.ToList();
-        foreach (var param in bundleHandlerData.Datas)
-        {
-            if (!await GetTableExist(param.createdOn))
-            {
-                await InsertWithStoredProcedure(param);
-                parmsList.Remove(param);
-                bundleHandlerData.AddCompletedData(param);
-            }
-        }
-
-        t0 = watch.ElapsedMilliseconds;
-        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
-        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
-
-        t1 = watch.ElapsedMilliseconds;
-        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
-        //using var trans = await sqlConnection.BeginTransactionAsync();
-
-        t2 = watch.ElapsedMilliseconds;
-
-        List<Task> ExecuteTasks = new List<Task>();
-        foreach (var group in gruopParams)
-        {
-            var tableName = group.Key;
-            foreach (var param in group)
-            {
-                await InsertWithNoCheckDapper(tableName, param, sqlConnection
-                    //, trans
-                    );
-                bundleHandlerData.AddCompletedData(param);
-            }
-        }
-
-        t3 = watch.ElapsedMilliseconds;
-        //await trans.CommitAsync();
-
-        watch.Stop();
-        t4 = watch.ElapsedMilliseconds;
-        if (t4 > 500)
-        {
-            logger.LogWarning("MeterValue Dapper {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, bundleHandlerData.CompletedDatas.Count());
-        }
-    }
-
-    private async Task InsertWithNoCheckDapper(string tableName, InsertMeterValueParam data, SqlConnection conn = null, DbTransaction trans = null)
-    {
-        string command = $"""
-                INSERT INTO {tableName} (ConnectorId, Value, CreatedOn, ContextId, FormatId, MeasurandId, PhaseId, LocationId, UnitId, ChargeBoxId, TransactionId)
-                VALUES (@ConnectorId, @Value, @CreatedOn, @ContextId, @FormatId, @MeasurandId, @PhaseId, @LocationId, @UnitId, @ChargeBoxId, @TransactionId);
-                """;
-
-        bool isLocalConnection = conn is null;
-        SqlConnection connection = isLocalConnection ? await sqlConnectionFactory.CreateAsync() : conn;
-
-        var parameters = new DynamicParameters();
-        parameters.Add("ConnectorId", data.connectorId, DbType.Int16);
-        parameters.Add("Value", data.value, DbType.Decimal, precision: 18, scale: 8);
-        parameters.Add("CreatedOn", data.createdOn, DbType.DateTime);
-        parameters.Add("ContextId", data.contextId, DbType.Int32);
-        parameters.Add("FormatId", data.formatId, DbType.Int32);
-        parameters.Add("MeasurandId", data.measurandId, DbType.Int32);
-        parameters.Add("PhaseId", data.phaseId, DbType.Int32);
-        parameters.Add("LocationId", data.locationId, DbType.Int32);
-        parameters.Add("UnitId", data.unitId, DbType.Int32);
-        parameters.Add("ChargeBoxId", data.chargeBoxId, DbType.String, size: 50);
-        parameters.Add("TransactionId", data.transactionId, DbType.Int32);
-
-        await connection.ExecuteAsync(command, parameters, trans);
-
-        if (isLocalConnection)
-        {
-            connection.Dispose();
-        }
-    }
-
-    private async Task BulkInsertWithBulkCopy(IEnumerable<InsertMeterValueParam> parms)
-    {
-        var watcher = Stopwatch.StartNew();
-        long t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
-
-        var parmsList = parms.ToList();
-        foreach (var param in parms)
-        {
-            if (!await GetTableExist(param.createdOn))
-            {
-                await InsertWithStoredProcedure(param);
-                parmsList.Remove(param);
-            }
-        }
-
-        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
-        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
-
-        t0 = watcher.ElapsedMilliseconds;
-        foreach (var group in gruopParams)
-        {
-            var table = new DataTable();
-            table.Columns.Add("ChargeBoxId");
-            table.Columns.Add("ConnectorId");
-            table.Columns.Add("Value");
-            table.Columns.Add("CreatedOn");
-            table.Columns.Add("ContextId");
-            table.Columns.Add("FormatId");
-            table.Columns.Add("MeasurandId");
-            table.Columns.Add("PhaseId");
-            table.Columns.Add("LocationId");
-            table.Columns.Add("UnitId");
-            table.Columns.Add("TransactionId");
-
-            foreach (var param in group)
-            {
-                var row = table.NewRow();
-                row["ChargeBoxId"] = param.chargeBoxId;
-                row["ConnectorId"] = param.connectorId;
-                row["Value"] = param.value;
-                row["CreatedOn"] = param.createdOn;
-                row["ContextId"] = param.contextId;
-                row["FormatId"] = param.formatId;
-                row["MeasurandId"] = param.measurandId;
-                row["PhaseId"] = param.phaseId;
-                row["LocationId"] = param.locationId;
-                row["UnitId"] = param.unitId;
-                row["TransactionId"] = param.transactionId;
-
-                table.Rows.Add(row);
-            }
-            t1 = watcher.ElapsedMilliseconds;
-            using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
-            using SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlConnection);
-            t2 = watcher.ElapsedMilliseconds;
-            sqlBulkCopy.BatchSize = group.Count();
-            sqlBulkCopy.DestinationTableName = group.Key;
-
-            sqlBulkCopy.ColumnMappings.Add("ChargeBoxId", "ChargeBoxId");
-            sqlBulkCopy.ColumnMappings.Add("ConnectorId", "ConnectorId");
-            sqlBulkCopy.ColumnMappings.Add("Value", "Value");
-            sqlBulkCopy.ColumnMappings.Add("CreatedOn", "CreatedOn");
-            sqlBulkCopy.ColumnMappings.Add("ContextId", "ContextId");
-            sqlBulkCopy.ColumnMappings.Add("FormatId", "FormatId");
-            sqlBulkCopy.ColumnMappings.Add("MeasurandId", "MeasurandId");
-            sqlBulkCopy.ColumnMappings.Add("PhaseId", "PhaseId");
-            sqlBulkCopy.ColumnMappings.Add("LocationId", "LocationId");
-            sqlBulkCopy.ColumnMappings.Add("UnitId", "UnitId");
-            sqlBulkCopy.ColumnMappings.Add("TransactionId", "TransactionId");
-            t3 = watcher.ElapsedMilliseconds;
-            await sqlBulkCopy.WriteToServerAsync(table);
-        }
-        watcher.Stop();
-        t4 = watcher.ElapsedMilliseconds;
-
-        if (t4 > 500)
-        {
-            logger.LogWarning("MeterValue BulkInsertWithBulkCopy Slow {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, parms.Count());
-        }
-    }
-
-    private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
-    {
-        var tableName = GetTableName(tableDateTime);
-        if (_existTables.Contains(tableName))
-        {
-            return true;
-        }
-
-        FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
-
-        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
-        var resultList = db.Database.SqlQuery<int>(checkTableSql)?.ToList();
-
-        if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
-        {
-            _existTables.Enqueue(tableName);
-            if (_existTables.Count > 30)
-            {
-                _existTables.TryDequeue(out _);
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    private async Task InsertWithStoredProcedure(InsertMeterValueParam param)
-    {
-        using var db = 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: param.chargeBoxId
-            , connectorId: 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());
-    }
-
-    private static string GetTableName(DateTime dateTime)
-        => $"ConnectorMeterValueRecord{dateTime:yyMMdd}";
-
-    private int GetInsertLimit(IConfiguration configuration)
-    {
-        var limitConfig = configuration["MeterValueDbInsertLimit"];
-        int limit = 10;
-        if (limitConfig != default)
-        {
-            int.TryParse(limitConfig, out limit);
-        }
-        return limit;
-    }
-}
-
-public record InsertMeterValueParam(string chargeBoxId, byte connectorId, decimal value, DateTime createdOn
-            , int contextId, int formatId, int measurandId, int phaseId
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.WSServer.Dto;
+using EVCB_OCPP.WSServer.Helper;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+
+namespace EVCB_OCPP.WSServer.Service.DbService;
+
+public class MeterValueDbService
+{
+    private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
+    private readonly ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory;
+    private readonly ILoggerFactory loggerFactory;
+    private readonly MeterValueGroupSingleHandler meterValueGroupSingleHandler;
+    private readonly QueueSemaphore insertSemaphore;
+    private readonly ILogger logger;
+    private readonly Queue<string> _existTables = new();
+    private GroupHandler<InsertMeterValueParam> insertMeterValueHandler;
+
+    public MeterValueDbService(
+        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
+        ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory,
+        ILogger<MeterValueDbService> logger,
+        ILoggerFactory loggerFactory,
+        IConfiguration configuration
+        //, MeterValueGroupSingleHandler meterValueGroupSingleHandler
+        )
+    {
+        this.meterValueDbContextFactory = meterValueDbContextFactory;
+        this.sqlConnectionFactory = sqlConnectionFactory;
+        this.loggerFactory = loggerFactory;
+        //this.meterValueGroupSingleHandler = meterValueGroupSingleHandler;
+        //this.meterValueConnectionString = configuration.GetConnectionString("MeterValueDBContext");
+        this.logger = logger;
+
+        InitInsertMeterValueHandler();
+
+        var insertLimit = GetInsertLimit(configuration);
+        insertSemaphore = new QueueSemaphore(insertLimit);
+
+    }
+
+    public Task InsertAsync(string chargeBoxId, byte connectorId, decimal value, DateTime createdOn
+            , int contextId, int formatId, int measurandId, int phaseId
+            , int locationId, int unitId, int transactionId)
+    {
+        var param = new InsertMeterValueParam(chargeBoxId, connectorId, value, createdOn, contextId, formatId, measurandId, phaseId, locationId, unitId, transactionId);
+
+        //return insertMeterValueHandler.HandleAsync(param);
+        return InsertAsync(param);
+        //return meterValueGroupSingleHandler.HandleAsync(param);
+    }
+
+    public Task InsertAsync(InsertMeterValueParam param)
+    {
+        //return InsertWithEF(param);
+        return InsertWithDapper(param);
+    }
+
+    public Task InsertBundleAsync(IEnumerable<InsertMeterValueParam> param)
+    {
+        return BundleInsertWithDapper(param);
+    }
+
+    public async Task<List<int>> GetTransactionSOC(int TxId, DateTime queryDate)
+    {
+        List<int> SOCCollection = new List<int>();
+        try
+        {
+            using var conn = await sqlConnectionFactory.CreateAsync();
+            var parameters = new DynamicParameters();
+            parameters.Add("@TransactionId", TxId, DbType.Int32, ParameterDirection.Input);
+
+            string strSql = $"""
+                SELECT [TransactionId],Min(Value) as MinSoC,Max(Value) as MaxSoC
+                FROM [dbo].ConnectorMeterValueRecord{queryDate.Date.ToString("yyMMdd")}
+                WHERE TransactionId=@TransactionId and MeasurandId=20 and Value!= 0 group by [TransactionId]
+                """;
+
+            var result = await conn.QueryFirstOrDefaultAsync<TransactionSoCDto>(strSql, parameters);
+            //  SOCCollection = results.Select(decimal.Parse).ToList();
+
+            if (result != null)
+            {
+                SOCCollection.Add(result.MaxSoC);
+                SOCCollection.Add(result.MinSoC);
+            }
+        }
+        catch (Exception ex)
+        {
+
+        }
+
+        return SOCCollection;
+    }
+
+    private void InitInsertMeterValueHandler()
+    {
+        if (insertMeterValueHandler is not null)
+        {
+            throw new Exception($"{nameof(InitInsertMeterValueHandler)} should only called once");
+        }
+
+        insertMeterValueHandler = new GroupHandler<InsertMeterValueParam>(
+            //BulkInsertWithBulkCopy,
+            BundleInsertWithDapper,
+            //loggerFactory.CreateLogger("InsertMeterValueHandler")
+            logger,
+            workerCnt: 1
+            );
+    }
+
+    private async Task BundleInsertWithStoredProcedure(IEnumerable<InsertMeterValueParam> parms)
+    {
+        foreach (var param in parms)
+        {
+            await InsertWithStoredProcedure(param);
+        }
+    }
+
+    private async Task InsertWithEF(InsertMeterValueParam param)
+    {
+        using var token = await insertSemaphore.GetToken();
+        using var db = 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: param.chargeBoxId
+            , connectorId: 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());
+    }
+
+    private async Task InsertWithDapper(InsertMeterValueParam param)
+    {
+        var watch = Stopwatch.StartNew();
+        long t0, t1;
+
+        if (!await GetTableExist(param.createdOn))
+        {
+            t0 = watch.ElapsedMilliseconds;
+            await InsertWithStoredProcedure(param);
+            watch.Stop();
+            t1 = watch.ElapsedMilliseconds;
+            if (t1 > 500)
+            {
+                logger.LogWarning("MeterValue InsertWithStoredProcedure {0}/{1}", t0, t1);
+            }
+            return;
+        }
+
+        t0 = watch.ElapsedMilliseconds;
+        var tableName = GetTableName(param.createdOn);
+
+        await InsertWithNoCheckDapper(tableName, param);
+
+        watch.Stop();
+        t1 = watch.ElapsedMilliseconds;
+        if (t1 > 700)
+        {
+            logger.LogWarning("MeterValue Dapper {0}/{1}", t0, t1);
+        }
+    }
+
+    private Task BundleInsertWithDapper(IEnumerable<InsertMeterValueParam> param)
+    {
+        return BundleInsertWithDapper(new BundleHandlerData<InsertMeterValueParam>(param.ToList()));
+    }
+
+    private async Task BundleInsertWithDapper(BundleHandlerData<InsertMeterValueParam> bundleHandlerData)
+    {
+        List<InsertMeterValueParam> completedParams = new();
+        var watch = Stopwatch.StartNew();
+        long t0, t1, t2, t3, t4;
+
+        var parmsList = bundleHandlerData.Datas.ToList();
+        foreach (var param in bundleHandlerData.Datas)
+        {
+            if (!await GetTableExist(param.createdOn))
+            {
+                await InsertWithStoredProcedure(param);
+                parmsList.Remove(param);
+                bundleHandlerData.AddCompletedData(param);
+            }
+        }
+
+        t0 = watch.ElapsedMilliseconds;
+        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
+        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
+
+        t1 = watch.ElapsedMilliseconds;
+        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
+        //using var trans = await sqlConnection.BeginTransactionAsync();
+
+        t2 = watch.ElapsedMilliseconds;
+
+        List<Task> ExecuteTasks = new List<Task>();
+        foreach (var group in gruopParams)
+        {
+            var tableName = group.Key;
+            foreach (var param in group)
+            {
+                await InsertWithNoCheckDapper(tableName, param, sqlConnection
+                    //, trans
+                    );
+                bundleHandlerData.AddCompletedData(param);
+            }
+        }
+
+        t3 = watch.ElapsedMilliseconds;
+        //await trans.CommitAsync();
+
+        watch.Stop();
+        t4 = watch.ElapsedMilliseconds;
+        if (t4 > 500)
+        {
+            logger.LogWarning("MeterValue Dapper {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, bundleHandlerData.CompletedDatas.Count());
+        }
+    }
+
+    private async Task InsertWithNoCheckDapper(string tableName, InsertMeterValueParam data, SqlConnection conn = null, DbTransaction trans = null)
+    {
+        string command = $"""
+                INSERT INTO {tableName} (ConnectorId, Value, CreatedOn, ContextId, FormatId, MeasurandId, PhaseId, LocationId, UnitId, ChargeBoxId, TransactionId)
+                VALUES (@ConnectorId, @Value, @CreatedOn, @ContextId, @FormatId, @MeasurandId, @PhaseId, @LocationId, @UnitId, @ChargeBoxId, @TransactionId);
+                """;
+
+        bool isLocalConnection = conn is null;
+        SqlConnection connection = isLocalConnection ? await sqlConnectionFactory.CreateAsync() : conn;
+
+        var parameters = new DynamicParameters();
+        parameters.Add("ConnectorId", data.connectorId, DbType.Int16);
+        parameters.Add("Value", data.value, DbType.Decimal, precision: 18, scale: 8);
+        parameters.Add("CreatedOn", data.createdOn, DbType.DateTime);
+        parameters.Add("ContextId", data.contextId, DbType.Int32);
+        parameters.Add("FormatId", data.formatId, DbType.Int32);
+        parameters.Add("MeasurandId", data.measurandId, DbType.Int32);
+        parameters.Add("PhaseId", data.phaseId, DbType.Int32);
+        parameters.Add("LocationId", data.locationId, DbType.Int32);
+        parameters.Add("UnitId", data.unitId, DbType.Int32);
+        parameters.Add("ChargeBoxId", data.chargeBoxId, DbType.String, size: 50);
+        parameters.Add("TransactionId", data.transactionId, DbType.Int32);
+
+        await connection.ExecuteAsync(command, parameters, trans);
+
+        if (isLocalConnection)
+        {
+            connection.Dispose();
+        }
+    }
+
+    private async Task BulkInsertWithBulkCopy(IEnumerable<InsertMeterValueParam> parms)
+    {
+        var watcher = Stopwatch.StartNew();
+        long t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
+
+        var parmsList = parms.ToList();
+        foreach (var param in parms)
+        {
+            if (!await GetTableExist(param.createdOn))
+            {
+                await InsertWithStoredProcedure(param);
+                parmsList.Remove(param);
+            }
+        }
+
+        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
+        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
+
+        t0 = watcher.ElapsedMilliseconds;
+        foreach (var group in gruopParams)
+        {
+            var table = new DataTable();
+            table.Columns.Add("ChargeBoxId");
+            table.Columns.Add("ConnectorId");
+            table.Columns.Add("Value");
+            table.Columns.Add("CreatedOn");
+            table.Columns.Add("ContextId");
+            table.Columns.Add("FormatId");
+            table.Columns.Add("MeasurandId");
+            table.Columns.Add("PhaseId");
+            table.Columns.Add("LocationId");
+            table.Columns.Add("UnitId");
+            table.Columns.Add("TransactionId");
+
+            foreach (var param in group)
+            {
+                var row = table.NewRow();
+                row["ChargeBoxId"] = param.chargeBoxId;
+                row["ConnectorId"] = param.connectorId;
+                row["Value"] = param.value;
+                row["CreatedOn"] = param.createdOn;
+                row["ContextId"] = param.contextId;
+                row["FormatId"] = param.formatId;
+                row["MeasurandId"] = param.measurandId;
+                row["PhaseId"] = param.phaseId;
+                row["LocationId"] = param.locationId;
+                row["UnitId"] = param.unitId;
+                row["TransactionId"] = param.transactionId;
+
+                table.Rows.Add(row);
+            }
+            t1 = watcher.ElapsedMilliseconds;
+            using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
+            using SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlConnection);
+            t2 = watcher.ElapsedMilliseconds;
+            sqlBulkCopy.BatchSize = group.Count();
+            sqlBulkCopy.DestinationTableName = group.Key;
+
+            sqlBulkCopy.ColumnMappings.Add("ChargeBoxId", "ChargeBoxId");
+            sqlBulkCopy.ColumnMappings.Add("ConnectorId", "ConnectorId");
+            sqlBulkCopy.ColumnMappings.Add("Value", "Value");
+            sqlBulkCopy.ColumnMappings.Add("CreatedOn", "CreatedOn");
+            sqlBulkCopy.ColumnMappings.Add("ContextId", "ContextId");
+            sqlBulkCopy.ColumnMappings.Add("FormatId", "FormatId");
+            sqlBulkCopy.ColumnMappings.Add("MeasurandId", "MeasurandId");
+            sqlBulkCopy.ColumnMappings.Add("PhaseId", "PhaseId");
+            sqlBulkCopy.ColumnMappings.Add("LocationId", "LocationId");
+            sqlBulkCopy.ColumnMappings.Add("UnitId", "UnitId");
+            sqlBulkCopy.ColumnMappings.Add("TransactionId", "TransactionId");
+            t3 = watcher.ElapsedMilliseconds;
+            await sqlBulkCopy.WriteToServerAsync(table);
+        }
+        watcher.Stop();
+        t4 = watcher.ElapsedMilliseconds;
+
+        if (t4 > 500)
+        {
+            logger.LogWarning("MeterValue BulkInsertWithBulkCopy Slow {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, parms.Count());
+        }
+    }
+
+    private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
+    {
+        var tableName = GetTableName(tableDateTime);
+        if (_existTables.Contains(tableName))
+        {
+            return true;
+        }
+
+        FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
+
+        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
+        var resultList = db.Database.SqlQuery<int>(checkTableSql)?.ToList();
+
+        if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
+        {
+            _existTables.Enqueue(tableName);
+            if (_existTables.Count > 30)
+            {
+                _existTables.TryDequeue(out _);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    private async Task InsertWithStoredProcedure(InsertMeterValueParam param)
+    {
+        using var db = 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: param.chargeBoxId
+            , connectorId: 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());
+    }
+
+    private static string GetTableName(DateTime dateTime)
+        => $"ConnectorMeterValueRecord{dateTime:yyMMdd}";
+
+    private int GetInsertLimit(IConfiguration configuration)
+    {
+        var limitConfig = configuration["MeterValueDbInsertLimit"];
+        int limit = 10;
+        if (limitConfig != default)
+        {
+            int.TryParse(limitConfig, out limit);
+        }
+        return limit;
+    }
+}
+
+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);

+ 236 - 236
EVCB_OCPP.WSServer/Service/DbService/WebDbService.cs

@@ -1,236 +1,236 @@
-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.QueryFirstOrDefaultAsync<StationFee>(displayPricestrSql, parameters);
-                if (result == default)
-                {
-                    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<Dictionary<string, string>> GetCustomerStationEvseConfig(string chargeBoxId, CancellationToken token = default)
-    {
-        string strSql = """
-                SELECT [ConfigureName], [ConfigureSetting]
-                FROM [dbo].[StationMachine]
-                INNER Join [dbo].[StationMachineConfig] on [dbo].[StationMachine].StationId = [dbo].[StationMachineConfig].StationId
-                WHERE [ChargeBoxId] = @ChargeBoxId
-                """;
-
-        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
-        DynamicParameters parameters = new DynamicParameters();
-        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
-        var configs = await conn.QueryAsync<StationMachineConfig>(new CommandDefinition(strSql, parameters: parameters, cancellationToken: token));
-        return configs.ToDictionary(x=>x.ConfigureName, x=>x.ConfigureSetting);
-    }
-
-    internal async Task<Dictionary<string, string>> GetEvseStationConfig(int stationId, CancellationToken token = default)
-    {
-        string strSql = """
-                SELECT [ConfigureName], [ConfigureSetting]
-                FROM [dbo].[StationMachineConfig]
-                WHERE [StationId] = @StationId
-                """;
-
-        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
-        DynamicParameters parameters = new DynamicParameters();
-        parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
-        var configs = await conn.QueryAsync<StationMachineConfig>(new CommandDefinition(strSql, parameters: parameters, cancellationToken: token));
-        return configs.ToDictionary(x => x.ConfigureName, x => x.ConfigureSetting);
-    }
-
-    internal async Task<int?> GetEvseStation(string chargeboxId, CancellationToken token = default)
-    {
-        string getStationStrSql = """
-                SELECT [StationId]
-                FROM [dbo].[StationMachine]
-                WHERE [ChargeBoxId] = @ChargeBoxIds;
-                """;
-        var parameters = new DynamicParameters();
-        parameters.Add("@ChargeBoxIds", chargeboxId, direction: ParameterDirection.Input, size: 50);
-        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
-        var stationId = await conn.QueryFirstOrDefaultAsync<int?>(new CommandDefinition(getStationStrSql, parameters, cancellationToken: token));
-        return stationId;
-    }
-
-    internal async Task<Dictionary<string, int>> GetEvseStationPair(List<string> chargeboxIds)
-    {
-        string getStationStrSql = """
-                SELECT [StationId],[ChargeBoxId]
-                FROM [dbo].[StationMachine]
-                WHERE [ChargeBoxId] IN @ChargeBoxIds;
-                """;
-        var parameters = new DynamicParameters();
-        parameters.Add("@ChargeBoxIds", chargeboxIds, direction: ParameterDirection.Input, size: 50);
-        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
-        var configs = await conn.QueryAsync<StationMachine>(getStationStrSql, parameters);
-        return configs.ToDictionary(x => x.ChargeBoxId, x => x.StationId);
-    }
-
-    internal async Task<Dictionary<int, Dictionary<string, string>>> GetStationEvseConfigs(List<int> stationIds, CancellationToken token = default)
-    {
-        string getSql = """
-                SELECT [StationId],[ConfigureName],[ConfigureSetting]
-                FROM [dbo].[StationMachine]
-                WHERE [StationId] in @StationIds
-                """;
-        using SqlConnection conn = await webDbConnectionFactory.CreateAsync(); 
-        var parameters = new DynamicParameters();
-        parameters.Add("@StationIds", stationIds, direction: ParameterDirection.Input);
-        var configs = await conn.QueryAsync<StationMachineConfig>(new CommandDefinition(getSql, parameters, cancellationToken: token));
-        return configs
-            .GroupBy(x => x.StationId)
-            .ToDictionary(
-                x => x.Key,
-                x => x.ToDictionary(
-                    x => x.ConfigureName, 
-                    x => x.ConfigureSetting
-            ));
-    }
-
-    internal async Task<Dictionary<int, Dictionary<string, string>>> GetStationEvseConfigs(CancellationToken token = default)
-    {
-        string getSql = """
-                SELECT [StationId],[ConfigureName],[ConfigureSetting]
-                FROM [dbo].[StationMachineConfig]
-                """;
-        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
-        try
-        {
-            var configs = await conn.QueryAsync<StationMachineConfig>(new CommandDefinition(getSql, cancellationToken: token));
-            return configs
-                .GroupBy(x => x.StationId)
-                .ToDictionary(
-                    x => x.Key,
-                    x => x.ToDictionary(
-                        x => x.ConfigureName,
-                        x => x.ConfigureSetting
-                ));
-        }
-        catch(Exception e)
-        {
-            logger.LogWarning("StationMachineConfig get failed");
-            return new Dictionary<int, Dictionary<string, string>>();
-        }
-    }
-}
+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.QueryFirstOrDefaultAsync<StationFee>(displayPricestrSql, parameters);
+                if (result == default)
+                {
+                    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<Dictionary<string, string>> GetCustomerStationEvseConfig(string chargeBoxId, CancellationToken token = default)
+    {
+        string strSql = """
+                SELECT [ConfigureName], [ConfigureSetting]
+                FROM [dbo].[StationMachine]
+                INNER Join [dbo].[StationMachineConfig] on [dbo].[StationMachine].StationId = [dbo].[StationMachineConfig].StationId
+                WHERE [ChargeBoxId] = @ChargeBoxId
+                """;
+
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        DynamicParameters parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
+        var configs = await conn.QueryAsync<StationMachineConfig>(new CommandDefinition(strSql, parameters: parameters, cancellationToken: token));
+        return configs.ToDictionary(x=>x.ConfigureName, x=>x.ConfigureSetting);
+    }
+
+    internal async Task<Dictionary<string, string>> GetEvseStationConfig(int stationId, CancellationToken token = default)
+    {
+        string strSql = """
+                SELECT [ConfigureName], [ConfigureSetting]
+                FROM [dbo].[StationMachineConfig]
+                WHERE [StationId] = @StationId
+                """;
+
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        DynamicParameters parameters = new DynamicParameters();
+        parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
+        var configs = await conn.QueryAsync<StationMachineConfig>(new CommandDefinition(strSql, parameters: parameters, cancellationToken: token));
+        return configs.ToDictionary(x => x.ConfigureName, x => x.ConfigureSetting);
+    }
+
+    internal async Task<int?> GetEvseStation(string chargeboxId, CancellationToken token = default)
+    {
+        string getStationStrSql = """
+                SELECT [StationId]
+                FROM [dbo].[StationMachine]
+                WHERE [ChargeBoxId] = @ChargeBoxIds;
+                """;
+        var parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxIds", chargeboxId, direction: ParameterDirection.Input, size: 50);
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        var stationId = await conn.QueryFirstOrDefaultAsync<int?>(new CommandDefinition(getStationStrSql, parameters, cancellationToken: token));
+        return stationId;
+    }
+
+    internal async Task<Dictionary<string, int>> GetEvseStationPair(List<string> chargeboxIds)
+    {
+        string getStationStrSql = """
+                SELECT [StationId],[ChargeBoxId]
+                FROM [dbo].[StationMachine]
+                WHERE [ChargeBoxId] IN @ChargeBoxIds;
+                """;
+        var parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxIds", chargeboxIds, direction: ParameterDirection.Input, size: 50);
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        var configs = await conn.QueryAsync<StationMachine>(getStationStrSql, parameters);
+        return configs.ToDictionary(x => x.ChargeBoxId, x => x.StationId);
+    }
+
+    internal async Task<Dictionary<int, Dictionary<string, string>>> GetStationEvseConfigs(List<int> stationIds, CancellationToken token = default)
+    {
+        string getSql = """
+                SELECT [StationId],[ConfigureName],[ConfigureSetting]
+                FROM [dbo].[StationMachine]
+                WHERE [StationId] in @StationIds
+                """;
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync(); 
+        var parameters = new DynamicParameters();
+        parameters.Add("@StationIds", stationIds, direction: ParameterDirection.Input);
+        var configs = await conn.QueryAsync<StationMachineConfig>(new CommandDefinition(getSql, parameters, cancellationToken: token));
+        return configs
+            .GroupBy(x => x.StationId)
+            .ToDictionary(
+                x => x.Key,
+                x => x.ToDictionary(
+                    x => x.ConfigureName, 
+                    x => x.ConfigureSetting
+            ));
+    }
+
+    internal async Task<Dictionary<int, Dictionary<string, string>>> GetStationEvseConfigs(CancellationToken token = default)
+    {
+        string getSql = """
+                SELECT [StationId],[ConfigureName],[ConfigureSetting]
+                FROM [dbo].[StationMachineConfig]
+                """;
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        try
+        {
+            var configs = await conn.QueryAsync<StationMachineConfig>(new CommandDefinition(getSql, cancellationToken: token));
+            return configs
+                .GroupBy(x => x.StationId)
+                .ToDictionary(
+                    x => x.Key,
+                    x => x.ToDictionary(
+                        x => x.ConfigureName,
+                        x => x.ConfigureSetting
+                ));
+        }
+        catch(Exception e)
+        {
+            logger.LogWarning("StationMachineConfig get failed");
+            return new Dictionary<int, Dictionary<string, string>>();
+        }
+    }
+}

+ 31 - 31
EVCB_OCPP.WSServer/Service/GoogleGetTimePrintService.cs

@@ -1,31 +1,31 @@
-using Microsoft.Extensions.Logging;
-using RestSharp;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-
-namespace EVCB_OCPP.WSServer.Service
-{
-    public class GoogleGetTimePrintService
-    {
-        private readonly ILogger<GoogleGetTimePrintService> logger;
-
-        public GoogleGetTimePrintService(ILogger<GoogleGetTimePrintService> logger)
-        {
-            this.logger = logger;
-        }
-
-        public async Task Print()
-        {
-            var client = new RestClient("http://www.google.com");
-            var stopWatch = Stopwatch.StartNew();
-            await client.ExecuteAsync(new RestRequest("",method: Method.Get));
-            stopWatch.Stop();
-            logger.LogInformation($"Google Get Cost {stopWatch.ElapsedMilliseconds}ms");
-        }
-    }
-}
+using Microsoft.Extensions.Logging;
+using RestSharp;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace EVCB_OCPP.WSServer.Service
+{
+    public class GoogleGetTimePrintService
+    {
+        private readonly ILogger<GoogleGetTimePrintService> logger;
+
+        public GoogleGetTimePrintService(ILogger<GoogleGetTimePrintService> logger)
+        {
+            this.logger = logger;
+        }
+
+        public async Task Print()
+        {
+            var client = new RestClient("http://www.google.com");
+            var stopWatch = Stopwatch.StartNew();
+            await client.ExecuteAsync(new RestRequest("",method: Method.Get));
+            stopWatch.Stop();
+            logger.LogInformation($"Google Get Cost {stopWatch.ElapsedMilliseconds}ms");
+        }
+    }
+}

+ 68 - 68
EVCB_OCPP.WSServer/Service/HeaderRecordService.cs

@@ -1,68 +1,68 @@
-using Azure.Core;
-using HeaderRecord;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using System.Net.Http;
-
-namespace EVCB_OCPP.Service
-{
-    public static partial class AppExtention
-    {
-        public static void AddHeaderRecordService(this IServiceCollection services)
-        {
-            services.AddTransient<HeaderRecordService>();
-        }
-
-        public static void UseHeaderRecordService(this WebApplication webApplication)
-        {
-            webApplication.Use(async (context, next) =>
-            {
-                var servcie = context.RequestServices.GetService<HeaderRecordService>();
-                servcie.LogRequest(context.TraceIdentifier, context.Request);
-                await next(context);
-                servcie.LogResponse(context.TraceIdentifier, context.Response);
-                return;
-            });
-        }
-    }
-}
-
-namespace HeaderRecord
-{
-
-    public class HeaderRecordService
-    {
-        private readonly ILogger<HeaderRecordService> logger;
-
-        public HeaderRecordService(ILogger<HeaderRecordService> logger)
-        {
-            this.logger = logger;
-        }
-
-        internal void LogRequest(string traceIdentifier, HttpRequest request)
-        {
-            logger.LogInformation("LogRequest============================================================");
-            logger.LogInformation("{id} {method} {path} {protocol}", traceIdentifier, request.Method, request.Path, request.Protocol);
-            logger.LogInformation("{id} Headers {headers}", traceIdentifier, JsonConvert.SerializeObject(request.Headers));
-            //foreach (var headerKey in request.Headers.Keys)
-            //{
-            //    logger.LogInformation("{id} {key} {value}", traceIdentifier, headerKey, request.Headers[headerKey]);
-            //}
-            logger.LogInformation("LogRequest============================================================");
-        }
-
-        internal void LogResponse(string traceIdentifier, HttpResponse response)
-        {
-            logger.LogInformation("LogResponse============================================================");
-            logger.LogInformation("{id} {key} {value}", traceIdentifier, "statuscode", response.StatusCode);
-            foreach (var headerKey in response.Headers.Keys)
-            {
-                logger.LogInformation("{id} {key} {value}", traceIdentifier, headerKey, response.Headers[headerKey]);
-            }
-            logger.LogInformation("LogResponse============================================================");
-        }
-    }
-}
+using Azure.Core;
+using HeaderRecord;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using System.Net.Http;
+
+namespace EVCB_OCPP.Service
+{
+    public static partial class AppExtention
+    {
+        public static void AddHeaderRecordService(this IServiceCollection services)
+        {
+            services.AddTransient<HeaderRecordService>();
+        }
+
+        public static void UseHeaderRecordService(this WebApplication webApplication)
+        {
+            webApplication.Use(async (context, next) =>
+            {
+                var servcie = context.RequestServices.GetService<HeaderRecordService>();
+                servcie.LogRequest(context.TraceIdentifier, context.Request);
+                await next(context);
+                servcie.LogResponse(context.TraceIdentifier, context.Response);
+                return;
+            });
+        }
+    }
+}
+
+namespace HeaderRecord
+{
+
+    public class HeaderRecordService
+    {
+        private readonly ILogger<HeaderRecordService> logger;
+
+        public HeaderRecordService(ILogger<HeaderRecordService> logger)
+        {
+            this.logger = logger;
+        }
+
+        internal void LogRequest(string traceIdentifier, HttpRequest request)
+        {
+            logger.LogInformation("LogRequest============================================================");
+            logger.LogInformation("{id} {method} {path} {protocol}", traceIdentifier, request.Method, request.Path, request.Protocol);
+            logger.LogInformation("{id} Headers {headers}", traceIdentifier, JsonConvert.SerializeObject(request.Headers));
+            //foreach (var headerKey in request.Headers.Keys)
+            //{
+            //    logger.LogInformation("{id} {key} {value}", traceIdentifier, headerKey, request.Headers[headerKey]);
+            //}
+            logger.LogInformation("LogRequest============================================================");
+        }
+
+        internal void LogResponse(string traceIdentifier, HttpResponse response)
+        {
+            logger.LogInformation("LogResponse============================================================");
+            logger.LogInformation("{id} {key} {value}", traceIdentifier, "statuscode", response.StatusCode);
+            foreach (var headerKey in response.Headers.Keys)
+            {
+                logger.LogInformation("{id} {key} {value}", traceIdentifier, headerKey, response.Headers[headerKey]);
+            }
+            logger.LogInformation("LogResponse============================================================");
+        }
+    }
+}

+ 372 - 372
EVCB_OCPP.WSServer/Service/LoadingBalanceService.cs

@@ -1,372 +1,372 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.ConnectionFactory;
-using EVCB_OCPP.WSServer.Helper;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Data;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Service
-{
-    public class LoadBalanceSetting
-    {
-        public int StationId { set; get; }
-
-        public int LBMode { set; get; }
-
-        public int LBCurrent { set; get; }
-
-    }
-
-    public class LoadingBalanceService
-    {
-        //ConcurrentDictionary<int, object> _lockDic = new ConcurrentDictionary<int, object>();
-        ConcurrentDictionary<int, SemaphoreSlim> _semaphoreDic = new ConcurrentDictionary<int, SemaphoreSlim>();
-        private readonly ISqlConnectionFactory<MainDBContext> mainDbConnectionFactory;
-        private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
-
-        //private readonly string mainConnectionString;
-        //private readonly string webConnectionString;
-
-        public LoadingBalanceService(ISqlConnectionFactory<MainDBContext> mainDbConnectionFactory, ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory)
-        {
-            this.mainDbConnectionFactory = mainDbConnectionFactory;
-            this.webDbConnectionFactory = webDbConnectionFactory;
-            //mainConnectionString = configuration.GetConnectionString("MainDBContext");
-            //webConnectionString = configuration.GetConnectionString("WebDBContext");
-        }
-
-        public async Task<int> GetStationIdByMachineId(string machineId)
-        {
-            int stationId = 0;
-            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
-                string strSql = "Select StationId from [dbo].[StationMachine] where MachineId=@MachineId ; ";
-                stationId = await conn.ExecuteScalarAsync<Int32>(strSql, parameters);
-            }
-            return stationId;
-        }
-
-
-        async public Task<bool> IsNeedtoCancelSetting(int stationId, string machineId, string chargeBoxId)
-        {
-            var setting = await GetLoadBalance(stationId);
-            if (setting == null) return false;
-
-            var semaphore = GetSemaphore(stationId);
-            await semaphore.WaitAsync();
-            if (setting.LBMode > 0 && setting.LBMode < 3 && !await IsStillInTransactions(chargeBoxId))
-            {
-                // renew table
-                //    UpdateLoadbalanceRecord(stationId, machineId, 0, DateTime.UtcNow);
-                return true;
-
-            }
-
-            if (setting.LBMode >= 3 || setting.LBMode < 1)
-            {
-                //   CloseLoadbalanceRecord(stationId);
-
-            }
-            semaphore.Release();
-
-            return false;
-
-        }
-
-        //private object GetLock(int stationId)
-        //{
-
-        //    if (!_lockDic.ContainsKey(stationId))
-        //    {
-        //        _lockDic.TryAdd(stationId, new object());
-        //    }
-
-        //    return _lockDic[stationId];
-
-        //}
-
-        private SemaphoreSlim GetSemaphore(int stationId)
-        {
-
-            if (!_semaphoreDic.ContainsKey(stationId))
-            {
-                _semaphoreDic.TryAdd(stationId, new SemaphoreSlim(1));
-            }
-
-            return _semaphoreDic[stationId];
-
-        }
-
-        private void CloseLoadbalanceRecord(int stationId)
-        {
-            using (SqlConnection conn = mainDbConnectionFactory.Create())
-            {
-
-                var parameters = new DynamicParameters();
-                parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
-                parameters.Add("@FinishedOn", DateTime.UtcNow, DbType.DateTime, ParameterDirection.Input);
-                string strSql = "Update  [dbo].[LoadingBalance]  SET FinishedOn=@FinishedOn where StationId=@StationId and FinishedOn='1991/01/01'; ";
-                conn.Execute(strSql, parameters);
-
-            }
-        }
-
-
-        private void UpdateLoadbalanceRecord(int stationId, string machineId, decimal power, DateTime? finishedOn, bool keepgoing = false)
-        {
-            using (SqlConnection conn = mainDbConnectionFactory.Create())
-            {
-                if (finishedOn.HasValue)
-                {
-                    var parameters = new DynamicParameters();
-                    parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
-                    parameters.Add("@FinishedOn", finishedOn.Value, DbType.DateTime, ParameterDirection.Input);
-                    string strSql = "Update  [dbo].[LoadingBalance]  SET FinishedOn=@FinishedOn where MachineId=@MachineId and FinishedOn='1991/01/01'; ";
-                    conn.Execute(strSql, parameters);
-                }
-                else
-                {
-                    if (keepgoing)
-                    {
-                        var parameters = new DynamicParameters();
-                        parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
-                        parameters.Add("@Power", power, DbType.Decimal, ParameterDirection.Input);
-                        string strSql = "Update  [dbo].[LoadingBalance]  SET Power=@Power where MachineId=@MachineId and FinishedOn='1991/01/01'; ";
-                        conn.Execute(strSql, parameters);
-                    }
-                    else
-                    {
-                        var parameters = new DynamicParameters();
-                        parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
-                        parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
-                        parameters.Add("@Power", power, DbType.Decimal, ParameterDirection.Input);
-                        parameters.Add("@CreatedOn", DateTime.UtcNow, DbType.DateTime, ParameterDirection.Input);
-                        parameters.Add("@FinishedOn", new DateTime(1991, 1, 1, 0, 0, 0, DateTimeKind.Utc), DbType.DateTime, ParameterDirection.Input);
-
-                        string strSql = "INSERT INTO [dbo].[LoadingBalance] " +
-                         "([StationId],[MachineId],[Power],[CreatedOn],[FinishedOn]) " +
-                         "VALUES(@StationId,@MachineId,@Power,@CreatedOn,@FinishedOn);";
-
-                        conn.Execute(strSql, parameters);
-                    }
-
-                }
-
-            }
-        }
-
-
-        private async Task<bool> IsStillInTransactions(string chargeBoxId)
-        {
-            bool result = false;
-
-            using (SqlConnection conn = await mainDbConnectionFactory.CreateAsync())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
-                string strSql = "Select count(*) from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime='1991/01/01'; ";
-                result = await conn.ExecuteScalarAsync<bool>(strSql, parameters);
-            }
-
-            return result;
-        }
-
-        private decimal? GetCurrentSetting(string machineId)
-        {
-            decimal? result = (decimal?)null;
-
-            using (SqlConnection conn = mainDbConnectionFactory.Create())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
-                string strSql = "Select Power from [dbo].[LoadingBalance] where MachineId=@MachineId and FinishedOn='1991/01/01'; ";
-                result = conn.ExecuteScalar<decimal>(strSql, parameters);
-            }
-
-            return result;
-        }
-
-        async public Task<Dictionary<string, decimal?>> GetSettingPower(int stationId)
-        {
-            Dictionary<string, decimal?> dic = new Dictionary<string, decimal?>();
-            var setting = await GetLoadBalance(stationId);
-            if (setting == null) return null;
-
-            var semaphore = GetSemaphore(stationId);
-            await semaphore.WaitAsync();
-            if (setting != null)
-            {
-                if (setting.LBMode == 1)
-                {
-                    dic = await GetAveragePower(stationId, setting.LBCurrent);
-                }
-
-            }
-            semaphore.Release();
-
-            return dic;
-        }
-
-
-        async public Task<LoadBalanceSetting> GetLoadBalance(int stationId)
-        {
-            LoadBalanceSetting setting = null;
-            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
-
-                string strSql = "Select LBMode,LBCurrent from [dbo].[Station] where Id=@StationId ; ";
-                setting = await conn.QueryFirstOrDefaultAsync<LoadBalanceSetting>(strSql, parameters);
-                //etting = result.FirstOrDefaultAsync();
-            }
-            return setting;
-        }
-
-        async private Task<List<string>> GetIdsbyStationId(int stationId)
-        {
-            List<string> machineIds = new List<string>();
-            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@StationId", stationId, DbType.Int16, ParameterDirection.Input);
-                string strSql = "Select MachineId from [dbo].[StationMachine] where StationId=@StationId; ";
-                var result = await conn.QueryAsync<String>(strSql, parameters);
-                machineIds = result.ToList();
-            }
-            return machineIds;
-        }
-
-        async private Task<Dictionary<string, decimal?>> GetAveragePower(int stationId, int availableCapacity)
-        {
-            Dictionary<string, decimal?> dic = new Dictionary<string, decimal?>();
-            availableCapacity = (int)(availableCapacity * 1000 / 1.05M);
-            int keepPower = 0;
-            //讀取上一次斷線但還沒充完電的分配量
-            (var offlineCPs, keepPower) = await GetChargeBoxIdbyOfflineCharging(stationId);
-            //扣除Keep充電功率  =  分配充電量
-            var totalPower = availableCapacity - keepPower;
-
-            if (totalPower > 0)
-            {
-                //總量 * 該樁的額定功率/該站充電中樁的總額定功率
-                var onlineChargingCPs = await GetOnlineChargerwithCharging(stationId);
-                if (onlineChargingCPs.Count > 0)
-                {
-                    int singlePower = (int)Decimal.Divide(totalPower, onlineChargingCPs.Count);
-
-                    foreach (var id in onlineChargingCPs)
-                    {
-                        dic.Add(id, singlePower);
-                    }
-                }
-
-            }
-
-            return dic;
-
-        }
-
-        async private Task<List<string>> GetOnlineChargerwithCharging(int stationId)
-        {
-            List<string> results = new List<string>();
-            List<string> machineIds = await GetIdsbyStationId(stationId);
-            List<string> chargeboxids = new List<string>();
-            using (SqlConnection conn = await mainDbConnectionFactory.CreateAsync())
-            {
-                string onlineChargerSql = "Select ChargeBoxId from [dbo].[Machine] where Id in @machineIds and [Online]=1; ";
-                var sqlParams = new DynamicParameters();
-                sqlParams.Add("@machineIds", machineIds, size: 36);
-                var onlineResult = await conn.QueryAsync<string>(onlineChargerSql, sqlParams);
-                chargeboxids = onlineResult.ToList();
-                foreach (var chargeboxid in chargeboxids)
-                {
-                    string txSql = "SELECT TOP(1) [Id] from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime = '1991-01-01 00:00:00.000'; ";
-                    var param = new DynamicParameters();
-                    param.Add("ChargeBoxId", chargeboxid, DbType.String, ParameterDirection.Input, size: 50);
-                    var txId = await conn.ExecuteScalarAsync<Int64>(txSql, param);
-                    if (txId > 0)
-                    {
-                        results.Add(chargeboxid);
-                    }
-                }
-
-            }
-            return results;
-        }
-
-        /// <summary>
-        /// 取得斷線樁號
-        /// </summary>
-        /// <param name="stationId">站點代號</param>
-        /// <param name="ratedPowers">總額定功率</param>
-        /// <returns></returns>
-        private async Task<(List<string>,int ratedPowers)> GetChargeBoxIdbyOfflineCharging(int stationId)
-        {
-            List<string> machineIds = await GetIdsbyStationId(stationId);
-            List<string> result = new List<string>();
-            int ratedPowers = 0;
-            using (SqlConnection conn = await mainDbConnectionFactory.CreateAsync())
-            {
-
-                string offlineChargerSql = "Select ChargeBoxId from [dbo].[Machine] where Id in @machineIds and [Online]=0; ";
-                var sqlParams = new DynamicParameters();
-                sqlParams.Add("@machineIds", machineIds, size: 36);
-                result = (await conn.QueryAsync<string>(offlineChargerSql, sqlParams)).ToList();
-                foreach (var charger in result)
-                {
-                    string txSql = "SELECT TOP(1) [Id] from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime = '1991-01-01 00:00:00.000'; ";
-                    var param = new DynamicParameters();
-                    param.Add("ChargeBoxId", charger, DbType.String, ParameterDirection.Input, 50);
-                    var txId = await conn.ExecuteScalarAsync<Int64>(txSql, param);
-                    if (txId > 0)
-                    {
-                        string ratedPowerSql = "Select Sum(RatedPower) from [dbo].[Machine] where ChargeBoxId=@ChargeBoxId and [Online]=0; ";
-                        ratedPowers += await conn.ExecuteScalarAsync<int>(ratedPowerSql, param);
-                    }
-                }
-            }
-            ratedPowers *= 1000;
-            return (result, ratedPowers);
-        }
-
-        private decimal GetRatedPowerbyChargeBoxId(string chargeBoxId)
-        {
-            decimal ratedPower = 0;
-            using (SqlConnection conn = mainDbConnectionFactory.Create())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@machineId", chargeBoxId, DbType.String, ParameterDirection.Input, 36);
-                string strSql = "Select RatedPower from [dbo].[Machine] where Id=@machineId; ";
-                ratedPower = conn.ExecuteScalar<Int32>(strSql, parameters);
-            }
-            return ratedPower;
-        }
-
-        private decimal GetRatedPowerbyId(string machineId)
-        {
-            decimal ratedPower = 0;
-            using (SqlConnection conn = mainDbConnectionFactory.Create())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@machineId", machineId, DbType.String, ParameterDirection.Input, 36);
-                string strSql = "Select RatedPower from [dbo].[Machine] where Id=@machineId; ";
-                ratedPower = conn.ExecuteScalar<Int32>(strSql, parameters);
-            }
-            return ratedPower;
-        }
-
-
-
-    }
-}
-
+using Dapper;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.WSServer.Helper;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service
+{
+    public class LoadBalanceSetting
+    {
+        public int StationId { set; get; }
+
+        public int LBMode { set; get; }
+
+        public int LBCurrent { set; get; }
+
+    }
+
+    public class LoadingBalanceService
+    {
+        //ConcurrentDictionary<int, object> _lockDic = new ConcurrentDictionary<int, object>();
+        ConcurrentDictionary<int, SemaphoreSlim> _semaphoreDic = new ConcurrentDictionary<int, SemaphoreSlim>();
+        private readonly ISqlConnectionFactory<MainDBContext> mainDbConnectionFactory;
+        private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
+
+        //private readonly string mainConnectionString;
+        //private readonly string webConnectionString;
+
+        public LoadingBalanceService(ISqlConnectionFactory<MainDBContext> mainDbConnectionFactory, ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory)
+        {
+            this.mainDbConnectionFactory = mainDbConnectionFactory;
+            this.webDbConnectionFactory = webDbConnectionFactory;
+            //mainConnectionString = configuration.GetConnectionString("MainDBContext");
+            //webConnectionString = configuration.GetConnectionString("WebDBContext");
+        }
+
+        public async Task<int> GetStationIdByMachineId(string machineId)
+        {
+            int stationId = 0;
+            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
+                string strSql = "Select StationId from [dbo].[StationMachine] where MachineId=@MachineId ; ";
+                stationId = await conn.ExecuteScalarAsync<Int32>(strSql, parameters);
+            }
+            return stationId;
+        }
+
+
+        async public Task<bool> IsNeedtoCancelSetting(int stationId, string machineId, string chargeBoxId)
+        {
+            var setting = await GetLoadBalance(stationId);
+            if (setting == null) return false;
+
+            var semaphore = GetSemaphore(stationId);
+            await semaphore.WaitAsync();
+            if (setting.LBMode > 0 && setting.LBMode < 3 && !await IsStillInTransactions(chargeBoxId))
+            {
+                // renew table
+                //    UpdateLoadbalanceRecord(stationId, machineId, 0, DateTime.UtcNow);
+                return true;
+
+            }
+
+            if (setting.LBMode >= 3 || setting.LBMode < 1)
+            {
+                //   CloseLoadbalanceRecord(stationId);
+
+            }
+            semaphore.Release();
+
+            return false;
+
+        }
+
+        //private object GetLock(int stationId)
+        //{
+
+        //    if (!_lockDic.ContainsKey(stationId))
+        //    {
+        //        _lockDic.TryAdd(stationId, new object());
+        //    }
+
+        //    return _lockDic[stationId];
+
+        //}
+
+        private SemaphoreSlim GetSemaphore(int stationId)
+        {
+
+            if (!_semaphoreDic.ContainsKey(stationId))
+            {
+                _semaphoreDic.TryAdd(stationId, new SemaphoreSlim(1));
+            }
+
+            return _semaphoreDic[stationId];
+
+        }
+
+        private void CloseLoadbalanceRecord(int stationId)
+        {
+            using (SqlConnection conn = mainDbConnectionFactory.Create())
+            {
+
+                var parameters = new DynamicParameters();
+                parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
+                parameters.Add("@FinishedOn", DateTime.UtcNow, DbType.DateTime, ParameterDirection.Input);
+                string strSql = "Update  [dbo].[LoadingBalance]  SET FinishedOn=@FinishedOn where StationId=@StationId and FinishedOn='1991/01/01'; ";
+                conn.Execute(strSql, parameters);
+
+            }
+        }
+
+
+        private void UpdateLoadbalanceRecord(int stationId, string machineId, decimal power, DateTime? finishedOn, bool keepgoing = false)
+        {
+            using (SqlConnection conn = mainDbConnectionFactory.Create())
+            {
+                if (finishedOn.HasValue)
+                {
+                    var parameters = new DynamicParameters();
+                    parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
+                    parameters.Add("@FinishedOn", finishedOn.Value, DbType.DateTime, ParameterDirection.Input);
+                    string strSql = "Update  [dbo].[LoadingBalance]  SET FinishedOn=@FinishedOn where MachineId=@MachineId and FinishedOn='1991/01/01'; ";
+                    conn.Execute(strSql, parameters);
+                }
+                else
+                {
+                    if (keepgoing)
+                    {
+                        var parameters = new DynamicParameters();
+                        parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
+                        parameters.Add("@Power", power, DbType.Decimal, ParameterDirection.Input);
+                        string strSql = "Update  [dbo].[LoadingBalance]  SET Power=@Power where MachineId=@MachineId and FinishedOn='1991/01/01'; ";
+                        conn.Execute(strSql, parameters);
+                    }
+                    else
+                    {
+                        var parameters = new DynamicParameters();
+                        parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
+                        parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
+                        parameters.Add("@Power", power, DbType.Decimal, ParameterDirection.Input);
+                        parameters.Add("@CreatedOn", DateTime.UtcNow, DbType.DateTime, ParameterDirection.Input);
+                        parameters.Add("@FinishedOn", new DateTime(1991, 1, 1, 0, 0, 0, DateTimeKind.Utc), DbType.DateTime, ParameterDirection.Input);
+
+                        string strSql = "INSERT INTO [dbo].[LoadingBalance] " +
+                         "([StationId],[MachineId],[Power],[CreatedOn],[FinishedOn]) " +
+                         "VALUES(@StationId,@MachineId,@Power,@CreatedOn,@FinishedOn);";
+
+                        conn.Execute(strSql, parameters);
+                    }
+
+                }
+
+            }
+        }
+
+
+        private async Task<bool> IsStillInTransactions(string chargeBoxId)
+        {
+            bool result = false;
+
+            using (SqlConnection conn = await mainDbConnectionFactory.CreateAsync())
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50);
+                string strSql = "Select count(*) from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime='1991/01/01'; ";
+                result = await conn.ExecuteScalarAsync<bool>(strSql, parameters);
+            }
+
+            return result;
+        }
+
+        private decimal? GetCurrentSetting(string machineId)
+        {
+            decimal? result = (decimal?)null;
+
+            using (SqlConnection conn = mainDbConnectionFactory.Create())
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input, 36);
+                string strSql = "Select Power from [dbo].[LoadingBalance] where MachineId=@MachineId and FinishedOn='1991/01/01'; ";
+                result = conn.ExecuteScalar<decimal>(strSql, parameters);
+            }
+
+            return result;
+        }
+
+        async public Task<Dictionary<string, decimal?>> GetSettingPower(int stationId)
+        {
+            Dictionary<string, decimal?> dic = new Dictionary<string, decimal?>();
+            var setting = await GetLoadBalance(stationId);
+            if (setting == null) return null;
+
+            var semaphore = GetSemaphore(stationId);
+            await semaphore.WaitAsync();
+            if (setting != null)
+            {
+                if (setting.LBMode == 1)
+                {
+                    dic = await GetAveragePower(stationId, setting.LBCurrent);
+                }
+
+            }
+            semaphore.Release();
+
+            return dic;
+        }
+
+
+        async public Task<LoadBalanceSetting> GetLoadBalance(int stationId)
+        {
+            LoadBalanceSetting setting = null;
+            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@StationId", stationId, DbType.Int32, ParameterDirection.Input);
+
+                string strSql = "Select LBMode,LBCurrent from [dbo].[Station] where Id=@StationId ; ";
+                setting = await conn.QueryFirstOrDefaultAsync<LoadBalanceSetting>(strSql, parameters);
+                //etting = result.FirstOrDefaultAsync();
+            }
+            return setting;
+        }
+
+        async private Task<List<string>> GetIdsbyStationId(int stationId)
+        {
+            List<string> machineIds = new List<string>();
+            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@StationId", stationId, DbType.Int16, ParameterDirection.Input);
+                string strSql = "Select MachineId from [dbo].[StationMachine] where StationId=@StationId; ";
+                var result = await conn.QueryAsync<String>(strSql, parameters);
+                machineIds = result.ToList();
+            }
+            return machineIds;
+        }
+
+        async private Task<Dictionary<string, decimal?>> GetAveragePower(int stationId, int availableCapacity)
+        {
+            Dictionary<string, decimal?> dic = new Dictionary<string, decimal?>();
+            availableCapacity = (int)(availableCapacity * 1000 / 1.05M);
+            int keepPower = 0;
+            //讀取上一次斷線但還沒充完電的分配量
+            (var offlineCPs, keepPower) = await GetChargeBoxIdbyOfflineCharging(stationId);
+            //扣除Keep充電功率  =  分配充電量
+            var totalPower = availableCapacity - keepPower;
+
+            if (totalPower > 0)
+            {
+                //總量 * 該樁的額定功率/該站充電中樁的總額定功率
+                var onlineChargingCPs = await GetOnlineChargerwithCharging(stationId);
+                if (onlineChargingCPs.Count > 0)
+                {
+                    int singlePower = (int)Decimal.Divide(totalPower, onlineChargingCPs.Count);
+
+                    foreach (var id in onlineChargingCPs)
+                    {
+                        dic.Add(id, singlePower);
+                    }
+                }
+
+            }
+
+            return dic;
+
+        }
+
+        async private Task<List<string>> GetOnlineChargerwithCharging(int stationId)
+        {
+            List<string> results = new List<string>();
+            List<string> machineIds = await GetIdsbyStationId(stationId);
+            List<string> chargeboxids = new List<string>();
+            using (SqlConnection conn = await mainDbConnectionFactory.CreateAsync())
+            {
+                string onlineChargerSql = "Select ChargeBoxId from [dbo].[Machine] where Id in @machineIds and [Online]=1; ";
+                var sqlParams = new DynamicParameters();
+                sqlParams.Add("@machineIds", machineIds, size: 36);
+                var onlineResult = await conn.QueryAsync<string>(onlineChargerSql, sqlParams);
+                chargeboxids = onlineResult.ToList();
+                foreach (var chargeboxid in chargeboxids)
+                {
+                    string txSql = "SELECT TOP(1) [Id] from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime = '1991-01-01 00:00:00.000'; ";
+                    var param = new DynamicParameters();
+                    param.Add("ChargeBoxId", chargeboxid, DbType.String, ParameterDirection.Input, size: 50);
+                    var txId = await conn.ExecuteScalarAsync<Int64>(txSql, param);
+                    if (txId > 0)
+                    {
+                        results.Add(chargeboxid);
+                    }
+                }
+
+            }
+            return results;
+        }
+
+        /// <summary>
+        /// 取得斷線樁號
+        /// </summary>
+        /// <param name="stationId">站點代號</param>
+        /// <param name="ratedPowers">總額定功率</param>
+        /// <returns></returns>
+        private async Task<(List<string>,int ratedPowers)> GetChargeBoxIdbyOfflineCharging(int stationId)
+        {
+            List<string> machineIds = await GetIdsbyStationId(stationId);
+            List<string> result = new List<string>();
+            int ratedPowers = 0;
+            using (SqlConnection conn = await mainDbConnectionFactory.CreateAsync())
+            {
+
+                string offlineChargerSql = "Select ChargeBoxId from [dbo].[Machine] where Id in @machineIds and [Online]=0; ";
+                var sqlParams = new DynamicParameters();
+                sqlParams.Add("@machineIds", machineIds, size: 36);
+                result = (await conn.QueryAsync<string>(offlineChargerSql, sqlParams)).ToList();
+                foreach (var charger in result)
+                {
+                    string txSql = "SELECT TOP(1) [Id] from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime = '1991-01-01 00:00:00.000'; ";
+                    var param = new DynamicParameters();
+                    param.Add("ChargeBoxId", charger, DbType.String, ParameterDirection.Input, 50);
+                    var txId = await conn.ExecuteScalarAsync<Int64>(txSql, param);
+                    if (txId > 0)
+                    {
+                        string ratedPowerSql = "Select Sum(RatedPower) from [dbo].[Machine] where ChargeBoxId=@ChargeBoxId and [Online]=0; ";
+                        ratedPowers += await conn.ExecuteScalarAsync<int>(ratedPowerSql, param);
+                    }
+                }
+            }
+            ratedPowers *= 1000;
+            return (result, ratedPowers);
+        }
+
+        private decimal GetRatedPowerbyChargeBoxId(string chargeBoxId)
+        {
+            decimal ratedPower = 0;
+            using (SqlConnection conn = mainDbConnectionFactory.Create())
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@machineId", chargeBoxId, DbType.String, ParameterDirection.Input, 36);
+                string strSql = "Select RatedPower from [dbo].[Machine] where Id=@machineId; ";
+                ratedPower = conn.ExecuteScalar<Int32>(strSql, parameters);
+            }
+            return ratedPower;
+        }
+
+        private decimal GetRatedPowerbyId(string machineId)
+        {
+            decimal ratedPower = 0;
+            using (SqlConnection conn = mainDbConnectionFactory.Create())
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("@machineId", machineId, DbType.String, ParameterDirection.Input, 36);
+                string strSql = "Select RatedPower from [dbo].[Machine] where Id=@machineId; ";
+                ratedPower = conn.ExecuteScalar<Int32>(strSql, parameters);
+            }
+            return ratedPower;
+        }
+
+
+
+    }
+}
+

+ 93 - 93
EVCB_OCPP.WSServer/Service/LocalBusinessService.cs

@@ -1,93 +1,93 @@
-using EVCB_OCPP.Packet.Messages.SubTypes;
-using EVCB_OCPP.WSServer.Dto;
-using Microsoft.Extensions.Configuration;
-using Newtonsoft.Json.Linq;
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Net;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Service
-{
-    public class LocalBusinessService : IBusinessService
-    {
-        public string CustomerId { get; set; }
-        private readonly IConfiguration configuration;
-        private readonly HttpClientService service;
-        private readonly OuterHttpClient _client;
-
-        public LocalBusinessService(IConfiguration configuration, HttpClientService service, OuterHttpClient client)
-        {
-            this.configuration = configuration;
-            this.service = service;
-            _client = client;
-        }
-
-        async public Task<IdTokenInfo> Authorize(string chargeBoxId, string idTag, int? connectorId = null, string source = null)
-        {
-            await Task.Delay(10);
-            IdTokenInfo info = new IdTokenInfo() { IdTagInfo = new IdTagInfo() { status = AuthorizationStatus.Invalid } };
-
-            try
-            {
-                if (CustomerId.ToUpper() == "009E603C-79CD-4620-A2B8-D9349C0E8AD8")
-                {
-                    info.IdTagInfo = new IdTagInfo() { status = AuthorizationStatus.Accepted };
-                    return info;
-                }
-
-
-                //OuterHttpClient _client = new OuterHttpClient();
-
-
-                string url = configuration["LocalAuthAPI"];
-
-
-                //HttpClientService service = new HttpClientService();
-
-                Dictionary<string, string> postData = new Dictionary<string, string>()
-                {
-                  { "ChargeBoxId", chargeBoxId },
-                  { "IdTag", idTag },
-
-
-                };
-                var _innerresult = await service.PostFormDataAsync(url, postData, null);
-
-
-                if (_innerresult.StatusCode == HttpStatusCode.OK)
-                {
-                    JObject jo = JObject.Parse(_innerresult.Response);
-                    if (jo["code"].ToString() == "1")
-                    {
-                        try
-                        {
-                            info.IdTagInfo.status = (AuthorizationStatus)Enum.Parse(typeof(AuthorizationStatus), jo["message"].ToString());
-                        }
-                        catch (Exception)
-                        {
-                            ;
-                        }
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-                ;
-            }
-            return info;
-
-        }
-
-        async public Task NotifyConnectorUnplugged(string chargeBoxId, string data)
-        {
-            await Task.Delay(10);
-        }
-
-        async public Task NotifyFaultStatus(ErrorDetails details)
-        {
-            await Task.Delay(10);
-        }
-    }
-}
+using EVCB_OCPP.Packet.Messages.SubTypes;
+using EVCB_OCPP.WSServer.Dto;
+using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service
+{
+    public class LocalBusinessService : IBusinessService
+    {
+        public string CustomerId { get; set; }
+        private readonly IConfiguration configuration;
+        private readonly HttpClientService service;
+        private readonly OuterHttpClient _client;
+
+        public LocalBusinessService(IConfiguration configuration, HttpClientService service, OuterHttpClient client)
+        {
+            this.configuration = configuration;
+            this.service = service;
+            _client = client;
+        }
+
+        async public Task<IdTokenInfo> Authorize(string chargeBoxId, string idTag, int? connectorId = null, string source = null)
+        {
+            await Task.Delay(10);
+            IdTokenInfo info = new IdTokenInfo() { IdTagInfo = new IdTagInfo() { status = AuthorizationStatus.Invalid } };
+
+            try
+            {
+                if (CustomerId.ToUpper() == "009E603C-79CD-4620-A2B8-D9349C0E8AD8")
+                {
+                    info.IdTagInfo = new IdTagInfo() { status = AuthorizationStatus.Accepted };
+                    return info;
+                }
+
+
+                //OuterHttpClient _client = new OuterHttpClient();
+
+
+                string url = configuration["LocalAuthAPI"];
+
+
+                //HttpClientService service = new HttpClientService();
+
+                Dictionary<string, string> postData = new Dictionary<string, string>()
+                {
+                  { "ChargeBoxId", chargeBoxId },
+                  { "IdTag", idTag },
+
+
+                };
+                var _innerresult = await service.PostFormDataAsync(url, postData, null);
+
+
+                if (_innerresult.StatusCode == HttpStatusCode.OK)
+                {
+                    JObject jo = JObject.Parse(_innerresult.Response);
+                    if (jo["code"].ToString() == "1")
+                    {
+                        try
+                        {
+                            info.IdTagInfo.status = (AuthorizationStatus)Enum.Parse(typeof(AuthorizationStatus), jo["message"].ToString());
+                        }
+                        catch (Exception)
+                        {
+                            ;
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                ;
+            }
+            return info;
+
+        }
+
+        async public Task NotifyConnectorUnplugged(string chargeBoxId, string data)
+        {
+            await Task.Delay(10);
+        }
+
+        async public Task NotifyFaultStatus(ErrorDetails details)
+        {
+            await Task.Delay(10);
+        }
+    }
+}

+ 150 - 150
EVCB_OCPP.WSServer/Service/MapApiServce.cs

@@ -1,150 +1,150 @@
-using EVCB_OCPP.WSServer.Service.WsService;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using NLog;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Service;
-
-public static class MapApiServceExtention
-{
-    public static void MapApiServce(this WebApplication webApplication)
-    {
-        var pass = webApplication.Configuration["apipass"];
-
-        var helpFunc = () =>
-        {
-            string sshaString = "";
-
-            if (File.Exists("ssha"))
-            {
-                sshaString = File.ReadAllText("ssha");
-            }
-
-            return string.Join("\r\n", new[] {
-                $"Git commit:{sshaString}",
-                "Command help!!",
-                "lcn : List Customer Name",
-                "gc : GC Collect",
-                "lc : List Clients",
-                "silent : silent",
-                "show : show log"
-            });
-        };
-        webApplication.MapGet("/", helpFunc);
-        webApplication.MapGet("/help", helpFunc);
-
-        webApplication.MapPost("/stop", (ProtalServer server) =>
-        {
-            server.Stop();
-            return "Command stop";
-        }).AddAuthFilter(pass);
-
-        webApplication.MapPost("/gc", () =>
-        {
-            GC.Collect();
-            return "Command GC";
-        }).AddAuthFilter(pass);
-
-        webApplication.MapPost("/lc", (ProtalServer server) =>
-        {
-            List<string> toReturn = new List<string>() { "Command List Clients" };
-            Dictionary<string, WsClientData> clientDic = server.GetClientDic();
-            var list = clientDic.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);
-        }).AddAuthFilter(pass);
-
-        webApplication.MapPost("/lcn", (ProtalServer server) =>
-        {
-            List<string> toReturn = new List<string> { "Command List Customer Name" };
-            Dictionary<string, WsClientData> clientDic = server.GetClientDic();
-            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);
-        }).AddAuthFilter(pass);
-
-        webApplication.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";
-        }).AddAuthFilter(pass);
-
-        webApplication.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";
-        }).AddAuthFilter(pass);
-
-        webApplication.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}";
-        }).AddAuthFilter(pass);
-
-        webApplication.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}";
-        }).AddAuthFilter(pass);
-    }
-
-    public static void AddAuthFilter(this RouteHandlerBuilder routeHandlerBuilder, string pass)
-    {
-        routeHandlerBuilder.AddEndpointFilter(async (context, next) =>
-        {
-            string key = context.HttpContext.Request.Headers["key"];
-            if (!string.IsNullOrEmpty(pass) &&
-                key != pass)
-            {
-                return Results.BadRequest();
-            }
-            return await next(context);
-        });
-    }
-}
+using EVCB_OCPP.WSServer.Service.WsService;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service;
+
+public static class MapApiServceExtention
+{
+    public static void MapApiServce(this WebApplication webApplication)
+    {
+        var pass = webApplication.Configuration["apipass"];
+
+        var helpFunc = () =>
+        {
+            string sshaString = "";
+
+            if (File.Exists("ssha"))
+            {
+                sshaString = File.ReadAllText("ssha");
+            }
+
+            return string.Join("\r\n", new[] {
+                $"Git commit:{sshaString}",
+                "Command help!!",
+                "lcn : List Customer Name",
+                "gc : GC Collect",
+                "lc : List Clients",
+                "silent : silent",
+                "show : show log"
+            });
+        };
+        webApplication.MapGet("/", helpFunc);
+        webApplication.MapGet("/help", helpFunc);
+
+        webApplication.MapPost("/stop", (ProtalServer server) =>
+        {
+            server.Stop();
+            return "Command stop";
+        }).AddAuthFilter(pass);
+
+        webApplication.MapPost("/gc", () =>
+        {
+            GC.Collect();
+            return "Command GC";
+        }).AddAuthFilter(pass);
+
+        webApplication.MapPost("/lc", (ProtalServer server) =>
+        {
+            List<string> toReturn = new List<string>() { "Command List Clients" };
+            Dictionary<string, WsClientData> clientDic = server.GetClientDic();
+            var list = clientDic.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);
+        }).AddAuthFilter(pass);
+
+        webApplication.MapPost("/lcn", (ProtalServer server) =>
+        {
+            List<string> toReturn = new List<string> { "Command List Customer Name" };
+            Dictionary<string, WsClientData> clientDic = server.GetClientDic();
+            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);
+        }).AddAuthFilter(pass);
+
+        webApplication.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";
+        }).AddAuthFilter(pass);
+
+        webApplication.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";
+        }).AddAuthFilter(pass);
+
+        webApplication.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}";
+        }).AddAuthFilter(pass);
+
+        webApplication.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}";
+        }).AddAuthFilter(pass);
+    }
+
+    public static void AddAuthFilter(this RouteHandlerBuilder routeHandlerBuilder, string pass)
+    {
+        routeHandlerBuilder.AddEndpointFilter(async (context, next) =>
+        {
+            string key = context.HttpContext.Request.Headers["key"];
+            if (!string.IsNullOrEmpty(pass) &&
+                key != pass)
+            {
+                return Results.BadRequest();
+            }
+            return await next(context);
+        });
+    }
+}

+ 166 - 166
EVCB_OCPP.WSServer/Service/MeterValueInsertHandler.cs

@@ -1,166 +1,166 @@
-using Dapper;
-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;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Service;
-
-public interface IHandler<T>
-{
-    public void Handle(IEnumerable<T> parms);
-}
-
-public class MeterValueInsertHandler : IHandler<InsertMeterValueParam>
-{
-    private static Queue<string> _existTables = new();
-    private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
-    private readonly ILogger<MeterValueInsertHandler> logger;
-    private readonly ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory;
-
-    //private readonly string meterValueConnectionString;
-
-    public MeterValueInsertHandler(
-        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
-        ILogger<MeterValueInsertHandler> logger,
-		ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory
-        //IConfiguration configuration
-        )
-    {
-        this.meterValueDbContextFactory = meterValueDbContextFactory;
-        this.logger = logger;
-        this.sqlConnectionFactory = sqlConnectionFactory;
-        //this.meterValueConnectionString = configuration.GetConnectionString("MeterValueDBContext");
-    }
-
-    public void Handle(IEnumerable<InsertMeterValueParam> parms)
-    {
-        //var watch = Stopwatch.StartNew();
-        //long t0, t1, t2, t3;
-
-        var parmsList = parms.ToList();
-        //t0 = watch.ElapsedMilliseconds;
-        foreach (var param in parms)
-        {
-            if (!GetTableExist(param.createdOn).Result)
-            {
-                InsertWithStoredProcedure(param).Wait();
-                parmsList.Remove(param);
-            }
-            //t1 = watch.ElapsedMilliseconds;
-            //watch.Stop();
-            //if (t1 > 500)
-            //{
-            //    logger.LogWarning("MeterValue InsertWithStoredProcedure {0}/{1}", t0, t1);
-            //}
-        }
-
-        //t1 = watch.ElapsedMilliseconds;
-        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
-        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
-
-        //t2 = watch.ElapsedMilliseconds;
-        foreach (var group in gruopParams)
-        {
-            using SqlConnection sqlConnection = sqlConnectionFactory.Create();
-            using var tans = sqlConnection.BeginTransaction();
-
-            var tableName = group.Key;
-            string command = $"""
-                INSERT INTO {tableName} (ConnectorId, Value, CreatedOn, ContextId, FormatId, MeasurandId, PhaseId, LocationId, UnitId, ChargeBoxId, TransactionId)
-                VALUES (@ConnectorId, @Value, @CreatedOn, @ContextId, @FormatId, @MeasurandId, @PhaseId, @LocationId, @UnitId, @ChargeBoxId, @TransactionId);
-                """;
-            foreach (var param in group)
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("ConnectorId", param.connectorId, DbType.Int16);
-                parameters.Add("Value", param.value, DbType.Decimal, precision: 18, scale: 8);
-                parameters.Add("CreatedOn", param.createdOn, DbType.DateTime);
-                parameters.Add("ContextId", param.contextId, DbType.Int32);
-                parameters.Add("FormatId", param.formatId, DbType.Int32);
-                parameters.Add("MeasurandId", param.measurandId, DbType.Int32);
-                parameters.Add("PhaseId", param.phaseId, DbType.Int32);
-                parameters.Add("LocationId", param.locationId, DbType.Int32);
-                parameters.Add("UnitId", param.unitId, DbType.Int32);
-                parameters.Add("ChargeBoxId", param.chargeBoxId, DbType.String, size: 50);
-                parameters.Add("TransactionId", param.transactionId, DbType.Int32);
-                sqlConnection.Execute(command, parameters, tans);
-            }
-
-            tans.Commit();
-
-        }
-
-        //watch.Stop();
-        //t3 = watch.ElapsedMilliseconds;
-        //if (t3 > 700)
-        //{
-        //    logger.LogWarning("MeterValue Dapper {0}/{1}/{2}/{3}", t0, t1, t2, t3);
-        //}
-        return ;
-    }
-
-    private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
-    {
-        var tableName = GetTableName(tableDateTime);
-        if (_existTables.Contains(tableName))
-        {
-            return true;
-        }
-
-        FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
-
-        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
-        var resultList = db.Database.SqlQuery<int>(checkTableSql)?.ToList();
-
-        if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
-        {
-            _existTables.Enqueue(tableName);
-            if (_existTables.Count > 30)
-            {
-                _existTables.TryDequeue(out _);
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    private async Task InsertWithStoredProcedure(InsertMeterValueParam param)
-    {
-        using var db = 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: 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);
-
-        db.Database.ExecuteSqlRaw(sp, parameter.ToArray());
-    }
-
-    private static string GetTableName(DateTime dateTime)
-        => $"ConnectorMeterValueRecord{dateTime:yyMMdd}";
-}
+using Dapper;
+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;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service;
+
+public interface IHandler<T>
+{
+    public void Handle(IEnumerable<T> parms);
+}
+
+public class MeterValueInsertHandler : IHandler<InsertMeterValueParam>
+{
+    private static Queue<string> _existTables = new();
+    private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
+    private readonly ILogger<MeterValueInsertHandler> logger;
+    private readonly ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory;
+
+    //private readonly string meterValueConnectionString;
+
+    public MeterValueInsertHandler(
+        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
+        ILogger<MeterValueInsertHandler> logger,
+		ISqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory
+        //IConfiguration configuration
+        )
+    {
+        this.meterValueDbContextFactory = meterValueDbContextFactory;
+        this.logger = logger;
+        this.sqlConnectionFactory = sqlConnectionFactory;
+        //this.meterValueConnectionString = configuration.GetConnectionString("MeterValueDBContext");
+    }
+
+    public void Handle(IEnumerable<InsertMeterValueParam> parms)
+    {
+        //var watch = Stopwatch.StartNew();
+        //long t0, t1, t2, t3;
+
+        var parmsList = parms.ToList();
+        //t0 = watch.ElapsedMilliseconds;
+        foreach (var param in parms)
+        {
+            if (!GetTableExist(param.createdOn).Result)
+            {
+                InsertWithStoredProcedure(param).Wait();
+                parmsList.Remove(param);
+            }
+            //t1 = watch.ElapsedMilliseconds;
+            //watch.Stop();
+            //if (t1 > 500)
+            //{
+            //    logger.LogWarning("MeterValue InsertWithStoredProcedure {0}/{1}", t0, t1);
+            //}
+        }
+
+        //t1 = watch.ElapsedMilliseconds;
+        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
+        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
+
+        //t2 = watch.ElapsedMilliseconds;
+        foreach (var group in gruopParams)
+        {
+            using SqlConnection sqlConnection = sqlConnectionFactory.Create();
+            using var tans = sqlConnection.BeginTransaction();
+
+            var tableName = group.Key;
+            string command = $"""
+                INSERT INTO {tableName} (ConnectorId, Value, CreatedOn, ContextId, FormatId, MeasurandId, PhaseId, LocationId, UnitId, ChargeBoxId, TransactionId)
+                VALUES (@ConnectorId, @Value, @CreatedOn, @ContextId, @FormatId, @MeasurandId, @PhaseId, @LocationId, @UnitId, @ChargeBoxId, @TransactionId);
+                """;
+            foreach (var param in group)
+            {
+                var parameters = new DynamicParameters();
+                parameters.Add("ConnectorId", param.connectorId, DbType.Int16);
+                parameters.Add("Value", param.value, DbType.Decimal, precision: 18, scale: 8);
+                parameters.Add("CreatedOn", param.createdOn, DbType.DateTime);
+                parameters.Add("ContextId", param.contextId, DbType.Int32);
+                parameters.Add("FormatId", param.formatId, DbType.Int32);
+                parameters.Add("MeasurandId", param.measurandId, DbType.Int32);
+                parameters.Add("PhaseId", param.phaseId, DbType.Int32);
+                parameters.Add("LocationId", param.locationId, DbType.Int32);
+                parameters.Add("UnitId", param.unitId, DbType.Int32);
+                parameters.Add("ChargeBoxId", param.chargeBoxId, DbType.String, size: 50);
+                parameters.Add("TransactionId", param.transactionId, DbType.Int32);
+                sqlConnection.Execute(command, parameters, tans);
+            }
+
+            tans.Commit();
+
+        }
+
+        //watch.Stop();
+        //t3 = watch.ElapsedMilliseconds;
+        //if (t3 > 700)
+        //{
+        //    logger.LogWarning("MeterValue Dapper {0}/{1}/{2}/{3}", t0, t1, t2, t3);
+        //}
+        return ;
+    }
+
+    private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
+    {
+        var tableName = GetTableName(tableDateTime);
+        if (_existTables.Contains(tableName))
+        {
+            return true;
+        }
+
+        FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
+
+        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
+        var resultList = db.Database.SqlQuery<int>(checkTableSql)?.ToList();
+
+        if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
+        {
+            _existTables.Enqueue(tableName);
+            if (_existTables.Count > 30)
+            {
+                _existTables.TryDequeue(out _);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    private async Task InsertWithStoredProcedure(InsertMeterValueParam param)
+    {
+        using var db = 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: 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);
+
+        db.Database.ExecuteSqlRaw(sp, parameter.ToArray());
+    }
+
+    private static string GetTableName(DateTime dateTime)
+        => $"ConnectorMeterValueRecord{dateTime:yyMMdd}";
+}

+ 316 - 316
EVCB_OCPP.WSServer/Service/OuterBusinessService.cs

@@ -1,316 +1,316 @@
-using EVCB_OCPP.Domain;
-
-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;
-using Newtonsoft.Json.Linq;
-using NLog;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Service
-{
-    internal class CPOOuterResponse
-    {
-        public CPOOuterResponse()
-        {
-            StatusCode = 0;
-
-        }
-
-        public int StatusCode { set; get; }
-
-        public string StatusMessage { set; get; }
-
-        public string Data { set; get; }
-
-        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
-        public string SerialNo { set; get; }
-
-
-        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
-        public string ErrorDetail { set; get; }
-
-
-    }
-    internal class CustomerSignMaterial
-    {
-        internal bool CallsThirdParty { set; get; }
-
-        internal string Id { set; get; }
-
-        internal string APIUrl { set; get; }
-
-        internal string SaltKey { set; get; }
-    }
-    public class OuterBusinessService : IBusinessService
-    {
-
-        private readonly ILogger<OuterBusinessService> logger;
-        private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
-        private readonly IMainDbService mainDbService;
-        private readonly OuterHttpClient httpClient;
-
-        private string _CustomerId = string.Empty;
-        private CustomerSignMaterial signMaterial = null;
-
-        public string CustomerId
-        {
-            get => _CustomerId;
-            set
-            {
-                _CustomerId = value;
-                signMaterial = GetSign(_CustomerId).Result;
-            }
-        }
-
-        internal CustomerSignMaterial CustomerSignMaterial
-        {
-            get => signMaterial;
-            set
-            {
-                signMaterial = value;
-                _CustomerId = signMaterial.Id;
-            }
-        }
-
-        public OuterBusinessService(
-            ILogger<OuterBusinessService> logger,
-            IDbContextFactory<MainDBContext> maindbContextFactory,
-            IMainDbService mainDbService,
-            OuterHttpClient httpClient)
-        {
-            this.logger = logger;
-            this.maindbContextFactory = maindbContextFactory;
-            this.mainDbService = mainDbService;
-            this.httpClient = httpClient;
-        }
-
-
-        async public Task<IdTokenInfo> Authorize(string chargeBoxId, string idTag, int? connectorId = null, string source = null)
-        {
-            //return new IdTokenInfo() { IdTagInfo = new IdTagInfo()
-            //{
-            //    expiryDate = DateTime.UtcNow.AddDays(1),
-            //    status = AuthorizationStatus.Accepted 
-            //} };
-
-            //await Task.Delay(10);
-            IdTokenInfo result = new IdTokenInfo() { IdTagInfo = new IdTagInfo() { status = AuthorizationStatus.Invalid } };
-
-            try
-            {
-                logger.LogInformation(chargeBoxId + " Charging Monitor======================================>");
-
-                string requestParams = idTag.StartsWith("vid:") ? await GetRequestParamsAsPnC(chargeBoxId, idTag, connectorId, source) : GetRequestParamsAsNormal(chargeBoxId, idTag, source);
-				logger.LogInformation($"{chargeBoxId} Authorize : {signMaterial.APIUrl + requestParams}");
-				HttpResult response = await httpClient.Post(signMaterial.APIUrl + requestParams, new Dictionary<string, string>()
-                            {
-                                { "PartnerId",signMaterial.Id}
-                            }, requestBody: null, saltkey: signMaterial.SaltKey).ConfigureAwait(false);
-                logger.LogInformation($"{chargeBoxId} response : {JsonConvert.SerializeObject(response)}");
-
-                if (response.Success)
-                {
-                    //Console.WriteLine(response.Response);
-                    var _httpResult = JsonConvert.DeserializeObject<CPOOuterResponse>(response.Response);
-                    JObject jo = JObject.Parse(_httpResult.Data);
-
-                    if (jo.ContainsKey("ExpiryDate"))
-                    {
-                        DateTime dt = jo["ExpiryDate"].Value<DateTime>();
-                        result.IdTagInfo.expiryDate = dt;
-                    }
-
-                    if (jo.ContainsKey("ParentIdTag"))
-                    {
-                        string _Message = jo["ParentIdTag"].Value<string>();
-                        result.IdTagInfo.parentIdTag = _Message;
-
-                    }
-
-                    if (jo.ContainsKey("ChargePointFee"))
-                    {
-                       
-
-                        for(int i=0;i< jo["ChargePointFee"].Count();i++)
-                        {
-                            if(i==0)
-                            {
-                                result.ChargePointFee = new List<ChargePointFee>();
-                            }
-                            result.ChargePointFee.Add(jo["ChargePointFee"][i].ToObject<ChargePointFee>());
-                        }
-                     
-                    }
-
-                    if (jo.ContainsKey("ChargepointFee"))
-                    {                       
-
-                        for (int i = 0; i < jo["ChargepointFee"].Count(); i++)
-                        {
-                            if (i == 0)
-                            {
-                                result.ChargePointFee = new List<ChargePointFee>();
-                            }
-
-                            result.ChargePointFee.Add(jo["ChargepointFee"][i].ToObject<ChargePointFee>());
-                        }
-
-                    }
-
-                    if (jo.ContainsKey("AccountBalance"))
-                    {
-                        decimal accountBalance = jo["AccountBalance"].Value<decimal>();
-                        result.AccountBalance = accountBalance;
-                    }
-
-
-                    if (jo.ContainsKey("Status"))
-                    {
-                        string _Message = jo["Status"].Value<string>();
-                        result.IdTagInfo.status = (AuthorizationStatus)Enum.Parse(typeof(AuthorizationStatus), _Message);
-                    }
-                }
-                else
-                {
-                    logger.LogError(chargeBoxId + " OuterBusinessService.Authorize Fail: " + response.Response);
-                }
-
-            }
-            catch (Exception ex)
-            {
-                result.IdTagInfo.status = AuthorizationStatus.Invalid;
-
-                logger.LogError(chargeBoxId + " OuterBusinessService.Authorize Ex: " + ex.ToString());
-            }
-
-            return result;
-
-        }
-
-        async public Task NotifyFaultStatus(ErrorDetails details)
-        {
-
-            try
-            {
-                if (signMaterial.CallsThirdParty)
-                {
-                    var response = await httpClient.Post(signMaterial.APIUrl + "connectorfault", new Dictionary<string, string>()
-                            {
-                                { "PartnerId",signMaterial.Id}
-
-                            }, details, signMaterial.SaltKey).ConfigureAwait(false);
-                }
-            }
-            catch (Exception ex)
-            {
-
-                logger.LogError(details.ChargeBoxId + " OuterBusinessService.NotifyFaultStatus Ex: " + ex.ToString());
-            }
-
-
-        }
-
-        async public Task NotifyConnectorUnplugged(string chargeBoxId, string data)
-        {
-            try
-            {
-                JObject jo = JObject.Parse(data);
-
-                var details = new { ChargeBoxId = chargeBoxId, SessionId = jo["idTx"].Value<Int32>(), Timestamp = jo["timestamp"].Value<DateTime>() };
-                if (signMaterial.CallsThirdParty)
-                {
-                    var response = await httpClient.Post(signMaterial.APIUrl + "connectorunplugged", new Dictionary<string, string>()
-                            {
-                                { "PartnerId",signMaterial.Id}
-
-                            }, details, signMaterial.SaltKey).ConfigureAwait(false);
-
-
-                }
-
-
-            }
-            catch (Exception ex)
-            {
-
-                logger.LogError(chargeBoxId + " OuterBusinessService.NotifyConnectorUnplugged Ex: " + ex.ToString());
-            }
-
-        }
-
-        private async Task<CustomerSignMaterial> GetSign(string customerId)
-        {
-            Guid Id = new Guid(customerId);
-            CustomerSignMaterial _customer = new CustomerSignMaterial();
-
-
-            //using (var db = new MainDBContext())
-            //using (var db = maindbContextFactory.CreateDbContextAsync())
-            //{
-            //    _customer = await db.Customer.Where(x => x.Id == Id).Select(x => new CustomerSignMaterial() { Id = x.Id.ToString(), APIUrl = x.ApiUrl, SaltKey = x.ApiKey, CallsThirdParty = x.CallPartnerApiOnSchedule }).FirstOrDefaultAsync();
-            //}
-            var _customerDb = await mainDbService.GetCustomer(Id);
-            if (_customerDb is not null)
-            {
-                _customer.Id = _customerDb.Id.ToString();
-                _customer.APIUrl = _customerDb.ApiUrl;
-                _customer.SaltKey = _customerDb.ApiKey;
-                _customer.CallsThirdParty = _customerDb.CallPartnerApiOnSchedule;
-            }
-            return _customer;
-        }
-
-        private async ValueTask<string> GetRequestParamsAsPnC(string chargeBoxId, string idTag, int? connectorId, string source)
-        {
-            idTag = idTag.Replace("vid:", "");
-
-            if (connectorId is null)
-            {
-                using (var db = await maindbContextFactory.CreateDbContextAsync())
-                {
-                    var connectorStatuses = await db.ConnectorStatus.Where(x => x.ChargeBoxId == chargeBoxId).
-                         Select(x => new { x.ConnectorId, x.Status, x.CreatedOn }).ToListAsync();
-
-                    var connectorStatus = connectorStatuses.Where(x => x.Status == 2).OrderByDescending(x => x.CreatedOn).FirstOrDefault();
-                    if (connectorStatus != null)
-                    {
-                        connectorId = connectorStatus.ConnectorId;
-                    }
-                }
-
-                if (connectorId is null)
-                {
-                    connectorId = -1;
-                }
-            }
-            var requestParamString = string.Format("charging_auth?ChargeBoxId={0}&ConnectorId={1}&IdTag={2}", chargeBoxId, connectorId, idTag);
-            if (!string.IsNullOrEmpty(source))
-            {
-                requestParamString += $"&Action={source}";
-            }
-            return requestParamString;
-        }
-
-        private string GetRequestParamsAsNormal(string chargeBoxId, string idTag, string source)
-        {
-            var requestParamString = string.Format("charging_auth?ChargeBoxId={0}&IdTag={1}", chargeBoxId, idTag);
-            if (!string.IsNullOrEmpty(source))
-            {
-                requestParamString += $"&Action={source}";
-            }
-            return requestParamString;
-        }
-
-        public Task NotifyConnectorUnplugged(string data)
-        {
-            throw new NotImplementedException();
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+
+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;
+using Newtonsoft.Json.Linq;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service
+{
+    internal class CPOOuterResponse
+    {
+        public CPOOuterResponse()
+        {
+            StatusCode = 0;
+
+        }
+
+        public int StatusCode { set; get; }
+
+        public string StatusMessage { set; get; }
+
+        public string Data { set; get; }
+
+        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+        public string SerialNo { set; get; }
+
+
+        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+        public string ErrorDetail { set; get; }
+
+
+    }
+    internal class CustomerSignMaterial
+    {
+        internal bool CallsThirdParty { set; get; }
+
+        internal string Id { set; get; }
+
+        internal string APIUrl { set; get; }
+
+        internal string SaltKey { set; get; }
+    }
+    public class OuterBusinessService : IBusinessService
+    {
+
+        private readonly ILogger<OuterBusinessService> logger;
+        private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+        private readonly IMainDbService mainDbService;
+        private readonly OuterHttpClient httpClient;
+
+        private string _CustomerId = string.Empty;
+        private CustomerSignMaterial signMaterial = null;
+
+        public string CustomerId
+        {
+            get => _CustomerId;
+            set
+            {
+                _CustomerId = value;
+                signMaterial = GetSign(_CustomerId).Result;
+            }
+        }
+
+        internal CustomerSignMaterial CustomerSignMaterial
+        {
+            get => signMaterial;
+            set
+            {
+                signMaterial = value;
+                _CustomerId = signMaterial.Id;
+            }
+        }
+
+        public OuterBusinessService(
+            ILogger<OuterBusinessService> logger,
+            IDbContextFactory<MainDBContext> maindbContextFactory,
+            IMainDbService mainDbService,
+            OuterHttpClient httpClient)
+        {
+            this.logger = logger;
+            this.maindbContextFactory = maindbContextFactory;
+            this.mainDbService = mainDbService;
+            this.httpClient = httpClient;
+        }
+
+
+        async public Task<IdTokenInfo> Authorize(string chargeBoxId, string idTag, int? connectorId = null, string source = null)
+        {
+            //return new IdTokenInfo() { IdTagInfo = new IdTagInfo()
+            //{
+            //    expiryDate = DateTime.UtcNow.AddDays(1),
+            //    status = AuthorizationStatus.Accepted 
+            //} };
+
+            //await Task.Delay(10);
+            IdTokenInfo result = new IdTokenInfo() { IdTagInfo = new IdTagInfo() { status = AuthorizationStatus.Invalid } };
+
+            try
+            {
+                logger.LogInformation(chargeBoxId + " Charging Monitor======================================>");
+
+                string requestParams = idTag.StartsWith("vid:") ? await GetRequestParamsAsPnC(chargeBoxId, idTag, connectorId, source) : GetRequestParamsAsNormal(chargeBoxId, idTag, source);
+				logger.LogInformation($"{chargeBoxId} Authorize : {signMaterial.APIUrl + requestParams}");
+				HttpResult response = await httpClient.Post(signMaterial.APIUrl + requestParams, new Dictionary<string, string>()
+                            {
+                                { "PartnerId",signMaterial.Id}
+                            }, requestBody: null, saltkey: signMaterial.SaltKey).ConfigureAwait(false);
+                logger.LogInformation($"{chargeBoxId} response : {JsonConvert.SerializeObject(response)}");
+
+                if (response.Success)
+                {
+                    //Console.WriteLine(response.Response);
+                    var _httpResult = JsonConvert.DeserializeObject<CPOOuterResponse>(response.Response);
+                    JObject jo = JObject.Parse(_httpResult.Data);
+
+                    if (jo.ContainsKey("ExpiryDate"))
+                    {
+                        DateTime dt = jo["ExpiryDate"].Value<DateTime>();
+                        result.IdTagInfo.expiryDate = dt;
+                    }
+
+                    if (jo.ContainsKey("ParentIdTag"))
+                    {
+                        string _Message = jo["ParentIdTag"].Value<string>();
+                        result.IdTagInfo.parentIdTag = _Message;
+
+                    }
+
+                    if (jo.ContainsKey("ChargePointFee"))
+                    {
+                       
+
+                        for(int i=0;i< jo["ChargePointFee"].Count();i++)
+                        {
+                            if(i==0)
+                            {
+                                result.ChargePointFee = new List<ChargePointFee>();
+                            }
+                            result.ChargePointFee.Add(jo["ChargePointFee"][i].ToObject<ChargePointFee>());
+                        }
+                     
+                    }
+
+                    if (jo.ContainsKey("ChargepointFee"))
+                    {                       
+
+                        for (int i = 0; i < jo["ChargepointFee"].Count(); i++)
+                        {
+                            if (i == 0)
+                            {
+                                result.ChargePointFee = new List<ChargePointFee>();
+                            }
+
+                            result.ChargePointFee.Add(jo["ChargepointFee"][i].ToObject<ChargePointFee>());
+                        }
+
+                    }
+
+                    if (jo.ContainsKey("AccountBalance"))
+                    {
+                        decimal accountBalance = jo["AccountBalance"].Value<decimal>();
+                        result.AccountBalance = accountBalance;
+                    }
+
+
+                    if (jo.ContainsKey("Status"))
+                    {
+                        string _Message = jo["Status"].Value<string>();
+                        result.IdTagInfo.status = (AuthorizationStatus)Enum.Parse(typeof(AuthorizationStatus), _Message);
+                    }
+                }
+                else
+                {
+                    logger.LogError(chargeBoxId + " OuterBusinessService.Authorize Fail: " + response.Response);
+                }
+
+            }
+            catch (Exception ex)
+            {
+                result.IdTagInfo.status = AuthorizationStatus.Invalid;
+
+                logger.LogError(chargeBoxId + " OuterBusinessService.Authorize Ex: " + ex.ToString());
+            }
+
+            return result;
+
+        }
+
+        async public Task NotifyFaultStatus(ErrorDetails details)
+        {
+
+            try
+            {
+                if (signMaterial.CallsThirdParty)
+                {
+                    var response = await httpClient.Post(signMaterial.APIUrl + "connectorfault", new Dictionary<string, string>()
+                            {
+                                { "PartnerId",signMaterial.Id}
+
+                            }, details, signMaterial.SaltKey).ConfigureAwait(false);
+                }
+            }
+            catch (Exception ex)
+            {
+
+                logger.LogError(details.ChargeBoxId + " OuterBusinessService.NotifyFaultStatus Ex: " + ex.ToString());
+            }
+
+
+        }
+
+        async public Task NotifyConnectorUnplugged(string chargeBoxId, string data)
+        {
+            try
+            {
+                JObject jo = JObject.Parse(data);
+
+                var details = new { ChargeBoxId = chargeBoxId, SessionId = jo["idTx"].Value<Int32>(), Timestamp = jo["timestamp"].Value<DateTime>() };
+                if (signMaterial.CallsThirdParty)
+                {
+                    var response = await httpClient.Post(signMaterial.APIUrl + "connectorunplugged", new Dictionary<string, string>()
+                            {
+                                { "PartnerId",signMaterial.Id}
+
+                            }, details, signMaterial.SaltKey).ConfigureAwait(false);
+
+
+                }
+
+
+            }
+            catch (Exception ex)
+            {
+
+                logger.LogError(chargeBoxId + " OuterBusinessService.NotifyConnectorUnplugged Ex: " + ex.ToString());
+            }
+
+        }
+
+        private async Task<CustomerSignMaterial> GetSign(string customerId)
+        {
+            Guid Id = new Guid(customerId);
+            CustomerSignMaterial _customer = new CustomerSignMaterial();
+
+
+            //using (var db = new MainDBContext())
+            //using (var db = maindbContextFactory.CreateDbContextAsync())
+            //{
+            //    _customer = await db.Customer.Where(x => x.Id == Id).Select(x => new CustomerSignMaterial() { Id = x.Id.ToString(), APIUrl = x.ApiUrl, SaltKey = x.ApiKey, CallsThirdParty = x.CallPartnerApiOnSchedule }).FirstOrDefaultAsync();
+            //}
+            var _customerDb = await mainDbService.GetCustomer(Id);
+            if (_customerDb is not null)
+            {
+                _customer.Id = _customerDb.Id.ToString();
+                _customer.APIUrl = _customerDb.ApiUrl;
+                _customer.SaltKey = _customerDb.ApiKey;
+                _customer.CallsThirdParty = _customerDb.CallPartnerApiOnSchedule;
+            }
+            return _customer;
+        }
+
+        private async ValueTask<string> GetRequestParamsAsPnC(string chargeBoxId, string idTag, int? connectorId, string source)
+        {
+            idTag = idTag.Replace("vid:", "");
+
+            if (connectorId is null)
+            {
+                using (var db = await maindbContextFactory.CreateDbContextAsync())
+                {
+                    var connectorStatuses = await db.ConnectorStatus.Where(x => x.ChargeBoxId == chargeBoxId).
+                         Select(x => new { x.ConnectorId, x.Status, x.CreatedOn }).ToListAsync();
+
+                    var connectorStatus = connectorStatuses.Where(x => x.Status == 2).OrderByDescending(x => x.CreatedOn).FirstOrDefault();
+                    if (connectorStatus != null)
+                    {
+                        connectorId = connectorStatus.ConnectorId;
+                    }
+                }
+
+                if (connectorId is null)
+                {
+                    connectorId = -1;
+                }
+            }
+            var requestParamString = string.Format("charging_auth?ChargeBoxId={0}&ConnectorId={1}&IdTag={2}", chargeBoxId, connectorId, idTag);
+            if (!string.IsNullOrEmpty(source))
+            {
+                requestParamString += $"&Action={source}";
+            }
+            return requestParamString;
+        }
+
+        private string GetRequestParamsAsNormal(string chargeBoxId, string idTag, string source)
+        {
+            var requestParamString = string.Format("charging_auth?ChargeBoxId={0}&IdTag={1}", chargeBoxId, idTag);
+            if (!string.IsNullOrEmpty(source))
+            {
+                requestParamString += $"&Action={source}";
+            }
+            return requestParamString;
+        }
+
+        public Task NotifyConnectorUnplugged(string data)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 64 - 64
EVCB_OCPP.WSServer/Service/ServerMessageService.cs

@@ -1,64 +1,64 @@
-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;
-using Azure;
-using EVCB_OCPP.WSServer.Message;
-
-namespace EVCB_OCPP.WSServer.Service;
-
-public class ServerMessageService
-{
-    public ServerMessageService(
-        IMainDbService mainDbService
-        , ILogger<ServerMessageService> logger)
-    {
-        this.mainDbService = mainDbService;
-        this.logger = logger;
-    }
-
-    private readonly IMainDbService mainDbService;
-    private readonly ILogger<ServerMessageService> logger;
-
-    internal Task<string> SendGetEVSEConfigureRequest(string chargeBoxId, List<string> configKeys = default , string serialNo = "")
-    {
-        if (string.IsNullOrEmpty(chargeBoxId)) return null;
-        List<string> toPassConfig = configKeys == default ? new List<string>() : configKeys;
-        return mainDbService.AddServerMessage(
-            ChargeBoxId: chargeBoxId,
-            OutAction: Actions.GetConfiguration.ToString(),
-            OutRequest: new GetConfigurationRequest() { key = toPassConfig },
-            SerialNo: serialNo
-            );
-    }
-
-    internal Task<string> SendChangeConfigurationRequest(string chargeBoxId, string key, string value, string serialNo = "")
-    {
-        return mainDbService.AddServerMessage(
-            ChargeBoxId: chargeBoxId,
-            OutAction: Actions.ChangeConfiguration.ToString(),
-            OutRequest: new ChangeConfigurationRequest()
-            {
-                key = key,
-                value = value
-            },
-            SerialNo: serialNo
-            );
-    }
-
-    internal Task<string> SendDataTransferRequest(string chargeBoxId, string messageId, string vendorId, string data)
-    {
-        return mainDbService.AddServerMessage(
-            ChargeBoxId: chargeBoxId,
-            OutAction: Actions.DataTransfer.ToString(),
-            OutRequest: new DataTransferRequest()
-            {
-                messageId = messageId,
-                vendorId = vendorId,
-                data = data
-            }
-            );
-    }
-}
+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;
+using Azure;
+using EVCB_OCPP.WSServer.Message;
+
+namespace EVCB_OCPP.WSServer.Service;
+
+public class ServerMessageService
+{
+    public ServerMessageService(
+        IMainDbService mainDbService
+        , ILogger<ServerMessageService> logger)
+    {
+        this.mainDbService = mainDbService;
+        this.logger = logger;
+    }
+
+    private readonly IMainDbService mainDbService;
+    private readonly ILogger<ServerMessageService> logger;
+
+    internal Task<string> SendGetEVSEConfigureRequest(string chargeBoxId, List<string> configKeys = default , string serialNo = "")
+    {
+        if (string.IsNullOrEmpty(chargeBoxId)) return null;
+        List<string> toPassConfig = configKeys == default ? new List<string>() : configKeys;
+        return mainDbService.AddServerMessage(
+            ChargeBoxId: chargeBoxId,
+            OutAction: Actions.GetConfiguration.ToString(),
+            OutRequest: new GetConfigurationRequest() { key = toPassConfig },
+            SerialNo: serialNo
+            );
+    }
+
+    internal Task<string> SendChangeConfigurationRequest(string chargeBoxId, string key, string value, string serialNo = "")
+    {
+        return mainDbService.AddServerMessage(
+            ChargeBoxId: chargeBoxId,
+            OutAction: Actions.ChangeConfiguration.ToString(),
+            OutRequest: new ChangeConfigurationRequest()
+            {
+                key = key,
+                value = value
+            },
+            SerialNo: serialNo
+            );
+    }
+
+    internal Task<string> SendDataTransferRequest(string chargeBoxId, string messageId, string vendorId, string data)
+    {
+        return mainDbService.AddServerMessage(
+            ChargeBoxId: chargeBoxId,
+            OutAction: Actions.DataTransfer.ToString(),
+            OutRequest: new DataTransferRequest()
+            {
+                messageId = messageId,
+                vendorId = vendorId,
+                data = data
+            }
+            );
+    }
+}

+ 247 - 247
EVCB_OCPP.WSServer/Service/StationConfigService.cs

@@ -1,247 +1,247 @@
-using EVCB_OCPP.Packet.Messages.Core;
-using EVCB_OCPP.WSServer.Message;
-using EVCB_OCPP.WSServer.Service.DbService;
-using EVCB_OCPP.WSServer.Service.WsService;
-using log4net.Core;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-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 static class StationConfigServiceExt
-{
-    public static Task InitStationConfigService(this IHost host)
-    {
-        var server = host.Services.GetRequiredService<ProtalServer>();
-        var stationConfigService = host.Services.GetRequiredService<StationConfigService>();
-        server.InitActions.Add(stationConfigService.CheckAndUpdateEvseConfig);
-        return host.Services.GetRequiredService<StationConfigService>().Init();
-    }
-}
-
-public class StationConfigService
-{
-    public StationConfigService(
-        ProtalServer portalServer
-        , WebDbService webDbService
-        , ServerMessageService messageService
-        , ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice
-        , ILogger<StationConfigService> logger)
-    {
-        this.portalServer = portalServer;
-        this.webDbService = webDbService;
-        this.messageService = messageService;
-        this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
-        this.logger = logger;
-    }
-
-    private static string Session_Station_Key = "StationConfigService_Station";
-
-    private readonly ProtalServer portalServer;
-    private readonly WebDbService webDbService;
-    private readonly ServerMessageService messageService;
-    private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
-    private readonly ILogger<StationConfigService> logger;
-
-    internal static Dictionary<int, Dictionary<string, string>> stationConfigRecord = null;
-
-    public async Task Init()
-    {
-        stationConfigRecord = await webDbService.GetStationEvseConfigs();
-    }
-
-    public async Task CheckAndUpdateEvseConfig(WsClientData session, CancellationToken token = default)
-    {
-        var chargeBoxId = session.ChargeBoxId;
-        var stationId = await webDbService.GetEvseStation(chargeBoxId, token);
-        if (stationId is null)
-        {
-            logger.LogInformation("{chargeBoxId} doesn't belongs to any station", chargeBoxId);
-            return;
-        }
-
-        int? sessionStationId = GetSessionStation(session);
-        if (sessionStationId != stationId)
-        {
-            SetSessionStation(session, stationId);
-            await UpdateEvseConfig(chargeBoxId, stationId.Value, token);
-        }
-        return;
-    }
-
-    public async Task CheckAndUpdateStationConfig()
-    {
-        await UpdateStationConfigChangedEvses();
-        await UpdateStationChangedEvses();
-        return;
-    }
-
-    private async Task UpdateStationConfigChangedEvses()
-    {
-        List<int> modifiedStations = new();
-        var dbStationEvseConfig = await webDbService.GetStationEvseConfigs();
-        foreach (var stationConfig in dbStationEvseConfig)
-        {
-            if (!stationConfigRecord.ContainsKey(stationConfig.Key) ||
-                !CheckIsEqual(stationConfig.Value, stationConfigRecord[stationConfig.Key]))
-            {
-                modifiedStations.Add(stationConfig.Key);
-            }
-        }
-        if (modifiedStations.Count == 0)
-        {
-            return;
-        }
-
-        stationConfigRecord = dbStationEvseConfig;
-
-        Dictionary<string, WsClientData>.ValueCollection connectedEvses = portalServer.GetClientDic().Values;
-        List<Task> updateTasks = new List<Task>();
-        foreach (WsClientData evse in connectedEvses)
-        {
-            int? sessionStationId = GetSessionStation(evse);
-            if (sessionStationId is not null &&
-                modifiedStations.Contains(sessionStationId.Value))
-            {
-                var tmp = UpdateEvseConfig(evse.ChargeBoxId, sessionStationId.Value);
-                updateTasks.Add(tmp);
-            }
-        }
-        await Task.WhenAll(updateTasks);
-    }
-
-    private async Task UpdateStationChangedEvses()
-    {
-        List<string> modifiedEvses = new();
-        var connectedEvses = portalServer.GetClientDic().Values.Where(x => x.IsCheckIn).ToList();
-        var evseStationPair = await webDbService.GetEvseStationPair(connectedEvses.Select(x => x.ChargeBoxId).ToList());
-        foreach (var evse in connectedEvses)
-        {
-            //var currentStation = await webDbService.GetEvseStation(evse.ChargeBoxId);
-            int? currentStation = evseStationPair.ContainsKey(evse.ChargeBoxId) ? evseStationPair[evse.ChargeBoxId] : null;
-            if (currentStation is null)
-            {
-                SetSessionStation(evse, null);
-                continue;
-            }
-
-            int? sessionStationId = GetSessionStation(evse);
-            if (sessionStationId != currentStation)
-            {
-                sessionStationId = currentStation;
-                await UpdateEvseConfig(evse.ChargeBoxId, currentStation.Value);
-                SetSessionStation(evse, sessionStationId);
-            }
-        }
-    }
-
-    private async Task UpdateEvseConfig(string chargeBoxId, int stationId, CancellationToken token = default)
-    {
-        Dictionary<string, string> dbConfigs = null;
-
-        if (!stationConfigRecord.ContainsKey(stationId))
-        {
-            logger.LogInformation("{chargeBoxId} doesnt has station config", chargeBoxId);
-            return;
-        }
-        dbConfigs = stationConfigRecord[stationId];
-
-        GetConfigurationConfirmation confirmation = await GetEvseCurrentConfig(chargeBoxId, ConfigKeys: dbConfigs.Keys.ToList(), token: token);
-        if (confirmation is null)
-        {
-            logger.LogWarning("{chargeBoxId} get config from evse failed", chargeBoxId);
-            return;
-        }
-
-        Dictionary<string, string> evseCurrentConfigs = new Dictionary<string, string>();
-        evseCurrentConfigs = confirmation.configurationKey.DistinctBy(x=>x.key).ToDictionary(x => x.key, x => x.value);
-
-        await CompareAndUpdateConfig(chargeBoxId,
-            evseCurrentConfigs: evseCurrentConfigs,
-            evseDbConfigs: dbConfigs,
-            token);
-    }
-
-
-    private async Task<GetConfigurationConfirmation> GetEvseCurrentConfig(string chargeBoxId, List<string> ConfigKeys = default, CancellationToken token = default)
-    {
-        var sendTask = async (string serialNo) => await messageService.SendGetEVSEConfigureRequest(chargeBoxId, configKeys: ConfigKeys, serialNo: serialNo);
-        var response = await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, token);
-        if (response is GetConfigurationConfirmation confirmation)
-        {
-            return confirmation;
-        }
-        return null;
-    }
-
-    private async Task<Dictionary<string, string>> GetEvseDBCurrentConfig(string chargeBoxId, CancellationToken token = default)
-    {
-        var receivedStaionID = await webDbService.GetEvseStation(chargeBoxId, token);
-        if (receivedStaionID is null)
-        {
-            logger.LogInformation("{chargeBoxId} station not found", chargeBoxId);
-            return null;
-        }
-        var staionID = receivedStaionID.Value;
-
-        if (!stationConfigRecord.Keys.Contains(staionID))
-        {
-            stationConfigRecord[staionID] = await webDbService.GetEvseStationConfig(staionID);
-        }
-
-        return stationConfigRecord[staionID];
-    }
-
-    internal async Task CompareAndUpdateConfig(string chargeBoxId,
-        Dictionary<string, string> evseCurrentConfigs,
-        Dictionary<string, string> evseDbConfigs,
-        CancellationToken token = default)
-    {
-        foreach (var config in evseDbConfigs)
-        {
-            if (evseCurrentConfigs.Keys.Contains(config.Key) &&
-                evseCurrentConfigs[config.Key] == config.Value)
-            {
-                continue;
-            }
-
-            object response = null;
-            var sendTask = async (string serialNo) => await messageService.SendChangeConfigurationRequest(chargeBoxId, config.Key, config.Value, serialNo: serialNo);
-            response = await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, token);
-        }
-    }
-
-    private bool CheckIsEqual(Dictionary<string, string> d1, Dictionary<string, string> d2)
-    {
-        return d1.Count == d2.Count && d1.All(
-             (d1KV) => d2.TryGetValue(d1KV.Key, out var d2Value) && (
-                  d1KV.Value == d2Value ||
-                  d1KV.Value?.Equals(d2Value) == true)
-        );
-    }
-
-    private int? GetSessionStation(WsClientData session)
-    {
-        if (session is null ||
-            !session.Data.ContainsKey(Session_Station_Key))
-        {
-            return null;
-        }
-        return (int?)session.Data[Session_Station_Key];
-    }
-
-    private void SetSessionStation(WsClientData session, int? stationId)
-    {
-        if (session is null)
-        {
-            return;
-        }
-        session.Data[Session_Station_Key] = stationId;
-    }
-}
+using EVCB_OCPP.Packet.Messages.Core;
+using EVCB_OCPP.WSServer.Message;
+using EVCB_OCPP.WSServer.Service.DbService;
+using EVCB_OCPP.WSServer.Service.WsService;
+using log4net.Core;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+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 static class StationConfigServiceExt
+{
+    public static Task InitStationConfigService(this IHost host)
+    {
+        var server = host.Services.GetRequiredService<ProtalServer>();
+        var stationConfigService = host.Services.GetRequiredService<StationConfigService>();
+        server.InitActions.Add(stationConfigService.CheckAndUpdateEvseConfig);
+        return host.Services.GetRequiredService<StationConfigService>().Init();
+    }
+}
+
+public class StationConfigService
+{
+    public StationConfigService(
+        ProtalServer portalServer
+        , WebDbService webDbService
+        , ServerMessageService messageService
+        , ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice
+        , ILogger<StationConfigService> logger)
+    {
+        this.portalServer = portalServer;
+        this.webDbService = webDbService;
+        this.messageService = messageService;
+        this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
+        this.logger = logger;
+    }
+
+    private static string Session_Station_Key = "StationConfigService_Station";
+
+    private readonly ProtalServer portalServer;
+    private readonly WebDbService webDbService;
+    private readonly ServerMessageService messageService;
+    private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
+    private readonly ILogger<StationConfigService> logger;
+
+    internal static Dictionary<int, Dictionary<string, string>> stationConfigRecord = null;
+
+    public async Task Init()
+    {
+        stationConfigRecord = await webDbService.GetStationEvseConfigs();
+    }
+
+    public async Task CheckAndUpdateEvseConfig(WsClientData session, CancellationToken token = default)
+    {
+        var chargeBoxId = session.ChargeBoxId;
+        var stationId = await webDbService.GetEvseStation(chargeBoxId, token);
+        if (stationId is null)
+        {
+            logger.LogInformation("{chargeBoxId} doesn't belongs to any station", chargeBoxId);
+            return;
+        }
+
+        int? sessionStationId = GetSessionStation(session);
+        if (sessionStationId != stationId)
+        {
+            SetSessionStation(session, stationId);
+            await UpdateEvseConfig(chargeBoxId, stationId.Value, token);
+        }
+        return;
+    }
+
+    public async Task CheckAndUpdateStationConfig()
+    {
+        await UpdateStationConfigChangedEvses();
+        await UpdateStationChangedEvses();
+        return;
+    }
+
+    private async Task UpdateStationConfigChangedEvses()
+    {
+        List<int> modifiedStations = new();
+        var dbStationEvseConfig = await webDbService.GetStationEvseConfigs();
+        foreach (var stationConfig in dbStationEvseConfig)
+        {
+            if (!stationConfigRecord.ContainsKey(stationConfig.Key) ||
+                !CheckIsEqual(stationConfig.Value, stationConfigRecord[stationConfig.Key]))
+            {
+                modifiedStations.Add(stationConfig.Key);
+            }
+        }
+        if (modifiedStations.Count == 0)
+        {
+            return;
+        }
+
+        stationConfigRecord = dbStationEvseConfig;
+
+        Dictionary<string, WsClientData>.ValueCollection connectedEvses = portalServer.GetClientDic().Values;
+        List<Task> updateTasks = new List<Task>();
+        foreach (WsClientData evse in connectedEvses)
+        {
+            int? sessionStationId = GetSessionStation(evse);
+            if (sessionStationId is not null &&
+                modifiedStations.Contains(sessionStationId.Value))
+            {
+                var tmp = UpdateEvseConfig(evse.ChargeBoxId, sessionStationId.Value);
+                updateTasks.Add(tmp);
+            }
+        }
+        await Task.WhenAll(updateTasks);
+    }
+
+    private async Task UpdateStationChangedEvses()
+    {
+        List<string> modifiedEvses = new();
+        var connectedEvses = portalServer.GetClientDic().Values.Where(x => x.IsCheckIn).ToList();
+        var evseStationPair = await webDbService.GetEvseStationPair(connectedEvses.Select(x => x.ChargeBoxId).ToList());
+        foreach (var evse in connectedEvses)
+        {
+            //var currentStation = await webDbService.GetEvseStation(evse.ChargeBoxId);
+            int? currentStation = evseStationPair.ContainsKey(evse.ChargeBoxId) ? evseStationPair[evse.ChargeBoxId] : null;
+            if (currentStation is null)
+            {
+                SetSessionStation(evse, null);
+                continue;
+            }
+
+            int? sessionStationId = GetSessionStation(evse);
+            if (sessionStationId != currentStation)
+            {
+                sessionStationId = currentStation;
+                await UpdateEvseConfig(evse.ChargeBoxId, currentStation.Value);
+                SetSessionStation(evse, sessionStationId);
+            }
+        }
+    }
+
+    private async Task UpdateEvseConfig(string chargeBoxId, int stationId, CancellationToken token = default)
+    {
+        Dictionary<string, string> dbConfigs = null;
+
+        if (!stationConfigRecord.ContainsKey(stationId))
+        {
+            logger.LogInformation("{chargeBoxId} doesnt has station config", chargeBoxId);
+            return;
+        }
+        dbConfigs = stationConfigRecord[stationId];
+
+        GetConfigurationConfirmation confirmation = await GetEvseCurrentConfig(chargeBoxId, ConfigKeys: dbConfigs.Keys.ToList(), token: token);
+        if (confirmation is null)
+        {
+            logger.LogWarning("{chargeBoxId} get config from evse failed", chargeBoxId);
+            return;
+        }
+
+        Dictionary<string, string> evseCurrentConfigs = new Dictionary<string, string>();
+        evseCurrentConfigs = confirmation.configurationKey.DistinctBy(x=>x.key).ToDictionary(x => x.key, x => x.value);
+
+        await CompareAndUpdateConfig(chargeBoxId,
+            evseCurrentConfigs: evseCurrentConfigs,
+            evseDbConfigs: dbConfigs,
+            token);
+    }
+
+
+    private async Task<GetConfigurationConfirmation> GetEvseCurrentConfig(string chargeBoxId, List<string> ConfigKeys = default, CancellationToken token = default)
+    {
+        var sendTask = async (string serialNo) => await messageService.SendGetEVSEConfigureRequest(chargeBoxId, configKeys: ConfigKeys, serialNo: serialNo);
+        var response = await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, token);
+        if (response is GetConfigurationConfirmation confirmation)
+        {
+            return confirmation;
+        }
+        return null;
+    }
+
+    private async Task<Dictionary<string, string>> GetEvseDBCurrentConfig(string chargeBoxId, CancellationToken token = default)
+    {
+        var receivedStaionID = await webDbService.GetEvseStation(chargeBoxId, token);
+        if (receivedStaionID is null)
+        {
+            logger.LogInformation("{chargeBoxId} station not found", chargeBoxId);
+            return null;
+        }
+        var staionID = receivedStaionID.Value;
+
+        if (!stationConfigRecord.Keys.Contains(staionID))
+        {
+            stationConfigRecord[staionID] = await webDbService.GetEvseStationConfig(staionID);
+        }
+
+        return stationConfigRecord[staionID];
+    }
+
+    internal async Task CompareAndUpdateConfig(string chargeBoxId,
+        Dictionary<string, string> evseCurrentConfigs,
+        Dictionary<string, string> evseDbConfigs,
+        CancellationToken token = default)
+    {
+        foreach (var config in evseDbConfigs)
+        {
+            if (evseCurrentConfigs.Keys.Contains(config.Key) &&
+                evseCurrentConfigs[config.Key] == config.Value)
+            {
+                continue;
+            }
+
+            object response = null;
+            var sendTask = async (string serialNo) => await messageService.SendChangeConfigurationRequest(chargeBoxId, config.Key, config.Value, serialNo: serialNo);
+            response = await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, token);
+        }
+    }
+
+    private bool CheckIsEqual(Dictionary<string, string> d1, Dictionary<string, string> d2)
+    {
+        return d1.Count == d2.Count && d1.All(
+             (d1KV) => d2.TryGetValue(d1KV.Key, out var d2Value) && (
+                  d1KV.Value == d2Value ||
+                  d1KV.Value?.Equals(d2Value) == true)
+        );
+    }
+
+    private int? GetSessionStation(WsClientData session)
+    {
+        if (session is null ||
+            !session.Data.ContainsKey(Session_Station_Key))
+        {
+            return null;
+        }
+        return (int?)session.Data[Session_Station_Key];
+    }
+
+    private void SetSessionStation(WsClientData session, int? stationId)
+    {
+        if (session is null)
+        {
+            return;
+        }
+        session.Data[Session_Station_Key] = stationId;
+    }
+}

+ 271 - 271
EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs

@@ -1,271 +1,271 @@
-using EVCB_OCPP.WSServer.Helper;
-using EVCB_OCPP.WSServer.Service.DbService;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System.Net.WebSockets;
-using System.Text;
-
-namespace EVCB_OCPP.WSServer.Service.WsService;
-
-public static partial class AppExtention
-{
-    public static void AddOcppWsServer(this IServiceCollection services)
-    {
-        services.AddTransient<WsClientData>();
-        services.AddSingleton<OcppWebsocketService>();
-    }
-
-    public static void UseOcppWsService(this WebApplication webApplication)
-    {
-        webApplication.UseWebSockets(new WebSocketOptions()
-        {
-            KeepAliveInterval = TimeSpan.FromDays(1)
-        });
-
-        webApplication.Use(async (context, next) =>
-        {
-            if (!context.WebSockets.IsWebSocketRequest)
-            {
-                await next(context);
-                return;
-            }
-
-            var servcie = context.RequestServices.GetService<OcppWebsocketService>();
-            await servcie.AcceptWebSocket(context);
-            return;
-        });
-    }
-}
-
-public class OcppWebsocketService : WebsocketService<WsClientData>
-{
-    public static List<string> protocals = new List<string>() {  "ocpp1.6"};
-
-    private readonly IConfiguration configuration;
-    private readonly IMainDbService mainDbService;
-    private readonly ILogger<OcppWebsocketService> logger;
-
-    private readonly QueueSemaphore handshakeSemaphore;
-
-    public OcppWebsocketService(
-        IConfiguration configuration,
-        IServiceProvider serviceProvider,
-        IMainDbService mainDbService,
-        ILogger<OcppWebsocketService> logger
-        ) : base(serviceProvider, logger)
-    {
-        this.configuration = configuration;
-        this.mainDbService = mainDbService;
-        this.logger = logger;
-
-        handshakeSemaphore = new QueueSemaphore(5);
-    }
-
-    internal override async ValueTask<string> ValidateSupportedPortocol(HttpContext context)
-    {
-        logger.LogInformation("{id} {function}:{Path}/{SubProtocol}", context.TraceIdentifier, nameof(ValidateSupportedPortocol), context.Request.Path, context.WebSockets.WebSocketRequestedProtocols);
-
-        var protocol = GetSupportedPortocol(context.WebSockets.WebSocketRequestedProtocols, protocals);
-        if (string.IsNullOrEmpty(protocol))
-        {
-            logger.LogInformation("{id} {function}:{Path} Protocol Not Supported, Disconnecting", context.TraceIdentifier, nameof(ValidateSupportedPortocol), context.Request.Path);
-
-            using WebSocket toRejectwebSocket = await context.WebSockets.AcceptWebSocketAsync();
-            await toRejectwebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, default);
-            return string.Empty;
-        }
-        return protocol;
-    }
-
-    internal override async ValueTask<bool> ValidateHandshake(HttpContext context, WsClientData session)
-    {
-        try
-        {
-            using var canValidate = await handshakeSemaphore.GetToken();
-            var result = await ValidateHandshakeUnsafe(context, session);
-            return result;
-        }
-        catch (Exception ex)
-        {
-            logger.LogError(ex.Message);
-            logger.LogError(ex.StackTrace);
-        }
-        return false;
-    }
-
-    internal async ValueTask<bool> ValidateHandshakeUnsafe(HttpContext context, WsClientData session)
-    {
-        if (context.RequestAborted.IsCancellationRequested) return false;
-
-        session.ISOCPP20 = context.WebSockets.WebSocketRequestedProtocols.Any(x => x.ToLower() == "ocpp2.0");
-
-        int securityProfile = 0;
-        string authorizationKey = string.Empty;
-        if (string.IsNullOrEmpty(session.Path))
-        {
-            //logger.Log();
-            logger.LogWarning("===========================================");
-            logger.LogWarning("{id} session.Path EMPTY", context.TraceIdentifier);
-            logger.LogWarning("===========================================");
-        }
-
-        string[] words = session.Path.ToString().Split('/');
-        session.ChargeBoxId = words.Last();
-        logger.LogDebug("{id} {0}:{1}", context.TraceIdentifier, session.ChargeBoxId, session.UriScheme);
-
-        foreach (var denyModel in GlobalConfig.DenyModelNames)
-        {
-            if (string.IsNullOrEmpty(denyModel))
-            {
-                continue;
-            }
-
-            if (session.ChargeBoxId.StartsWith(denyModel))
-            {
-                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
-
-                logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
-                return false;
-            }
-        }
-
-        if (configuration["MaintainMode"] == "1")
-        {
-            session.ChargeBoxId = session.ChargeBoxId + "_2";
-        }
-
-        logger.LogInformation("{id} ValidateHandshake: {0}", context.TraceIdentifier, session.Path);
-        bool isExistedSN = false;
-        bool authorizated = false;
-
-        var info = await mainDbService.GetMachineIdAndCustomerInfo(session.ChargeBoxId, context.RequestAborted);
-        if (context.RequestAborted.IsCancellationRequested) return false;
-        //var machine = db.Machine.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.IsDelete == false).Select(x => new { x.CustomerId, x.Id }).AsNoTracking().FirstOrDefault();
-        //session.CustomerName = machine == null ? "Unknown" : db.Customer.Where(x => x.Id == machine.CustomerId).Select(x => x.Name).FirstOrDefault();
-        //session.CustomerId = machine == null ? Guid.Empty : machine.CustomerId;
-        //session.MachineId = machine == null ? String.Empty : machine.Id;
-        //isExistedSN = machine == null ? false : true;
-        session.CustomerName = info.CustomerName;
-        session.CustomerId = info.CustomerId;
-        session.MachineId = info.MachineId;
-        isExistedSN = !string.IsNullOrEmpty(info.MachineId);// machine == null ? false : true;
-
-        if (!isExistedSN)
-        {
-            logger.LogInformation("{id} {func} not found", context.TraceIdentifier, nameof(ValidateHandshake));
-            context.Response.StatusCode = StatusCodes.Status404NotFound;
-            return false;
-        }
-
-        //var configVaule = db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.ConfigureName == StandardConfiguration.SecurityProfile)
-        //                  .Select(x => x.ConfigureSetting).FirstOrDefault();
-        var configVaule = await mainDbService.GetMachineSecurityProfile(session.ChargeBoxId, context.RequestAborted);
-        if (context.RequestAborted.IsCancellationRequested) return false;
-        if (string.IsNullOrEmpty(configVaule))
-        {
-            logger.LogInformation("{id} {func} security profile not set", context.TraceIdentifier, nameof(ValidateHandshake));
-            return true;
-        }
-
-        if (!int.TryParse(configVaule, out securityProfile))
-        {
-            logger.LogCritical("ERROR securityProfile setted {securityProfile}", configVaule);
-            return false;
-        }
-
-        if (session.ISOCPP20)
-        {
-            // 1.6 server only support change server  function
-            logger.LogInformation("{id} {func} {securityProfile} is ocpp20", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
-            securityProfile = 0;
-        }
-
-        if (securityProfile == 0)
-        {
-            authorizated = true;
-        }
-
-        if (securityProfile == 3 && session.UriScheme == "ws")
-        {
-            logger.LogInformation("{id} {func} {securityProfile} connection unsecured", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
-            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
-            return false;
-        }
-
-        if (securityProfile == 2 && session.UriScheme == "ws")
-        {
-            logger.LogInformation("{id} {func} {securityProfile} connection unsecured", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
-            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
-            return false;
-        }
-
-        if (securityProfile == 1 || securityProfile == 2)
-        {
-            string authHeader = context?.Request?.Headers?.Authorization;
-
-            //if (session.Items.ContainsKey("Authorization") || session.Items.ContainsKey("authorization"))
-            if (string.IsNullOrEmpty(authHeader))
-            {
-                logger.LogInformation("{id} {func} {securityProfile} missing auth header", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
-                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
-                return false;
-            }
-            //authorizationKey = db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.ConfigureName == StandardConfiguration.AuthorizationKey)
-            //                    .Select(x => x.ConfigureSetting).FirstOrDefault();
-            authorizationKey = await mainDbService.GetMachineAuthorizationKey(session.ChargeBoxId, context.RequestAborted);
-            if (context.RequestAborted.IsCancellationRequested) return false;
-
-            if (session.ISOCPP20)
-            {
-                // 1.6 server only support change server  function
-                securityProfile = 0;
-            }
-
-            logger.LogInformation("{id} ***********Authorization   ", context.TraceIdentifier);
-
-            if (string.IsNullOrEmpty(authorizationKey))
-            {
-                logger.LogInformation("{id} {func} {securityProfile} auth key not defined in server", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
-                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
-                return false;
-            }
-
-            //string base64Encoded = session.Items.ContainsKey("Authorization") ? session.Items["Authorization"].ToString().Replace("Basic ", "") : session.Items["authorization"].ToString().Replace("Basic ", "");
-            string base64Encoded = authHeader.Replace("Basic ", "");
-            byte[] data = Convert.FromBase64String(base64Encoded);
-            string[] base64Decoded = Encoding.ASCII.GetString(data).Split(':');
-            logger.LogInformation("{id} ***********Authorization   " + Encoding.ASCII.GetString(data), context.TraceIdentifier);
-            if (base64Decoded.Count() == 2 && base64Decoded[0] == session.ChargeBoxId && base64Decoded[1] == authorizationKey)
-            {
-                authorizated = true;
-            }
-
-            if (!authorizated)
-            {
-                logger.LogInformation("{id} {func} {securityProfile} auth failed", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
-                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
-                return false;
-            }
-        }
-
-        logger.LogInformation("{id} ValidateHandshake PASS: {0}", context.TraceIdentifier, session.Path);
-        return true;
-    }
-
-    private static string GetSupportedPortocol(IList<string> clientProtocols, IList<string> supportedProtocols)
-    {
-        int supportedProtocolIndex = supportedProtocols.Count - 1;
-        for (; supportedProtocolIndex >= 0; supportedProtocolIndex--)
-        {
-            var testProtocol = supportedProtocols[supportedProtocolIndex];
-            if (clientProtocols.Contains(testProtocol))
-            {
-                return testProtocol;
-            }
-        }
-        return string.Empty;
-    }
-}
+using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Service.DbService;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System.Net.WebSockets;
+using System.Text;
+
+namespace EVCB_OCPP.WSServer.Service.WsService;
+
+public static partial class AppExtention
+{
+    public static void AddOcppWsServer(this IServiceCollection services)
+    {
+        services.AddTransient<WsClientData>();
+        services.AddSingleton<OcppWebsocketService>();
+    }
+
+    public static void UseOcppWsService(this WebApplication webApplication)
+    {
+        webApplication.UseWebSockets(new WebSocketOptions()
+        {
+            KeepAliveInterval = TimeSpan.FromDays(1)
+        });
+
+        webApplication.Use(async (context, next) =>
+        {
+            if (!context.WebSockets.IsWebSocketRequest)
+            {
+                await next(context);
+                return;
+            }
+
+            var servcie = context.RequestServices.GetService<OcppWebsocketService>();
+            await servcie.AcceptWebSocket(context);
+            return;
+        });
+    }
+}
+
+public class OcppWebsocketService : WebsocketService<WsClientData>
+{
+    public static List<string> protocals = new List<string>() {  "ocpp1.6"};
+
+    private readonly IConfiguration configuration;
+    private readonly IMainDbService mainDbService;
+    private readonly ILogger<OcppWebsocketService> logger;
+
+    private readonly QueueSemaphore handshakeSemaphore;
+
+    public OcppWebsocketService(
+        IConfiguration configuration,
+        IServiceProvider serviceProvider,
+        IMainDbService mainDbService,
+        ILogger<OcppWebsocketService> logger
+        ) : base(serviceProvider, logger)
+    {
+        this.configuration = configuration;
+        this.mainDbService = mainDbService;
+        this.logger = logger;
+
+        handshakeSemaphore = new QueueSemaphore(5);
+    }
+
+    internal override async ValueTask<string> ValidateSupportedPortocol(HttpContext context)
+    {
+        logger.LogInformation("{id} {function}:{Path}/{SubProtocol}", context.TraceIdentifier, nameof(ValidateSupportedPortocol), context.Request.Path, context.WebSockets.WebSocketRequestedProtocols);
+
+        var protocol = GetSupportedPortocol(context.WebSockets.WebSocketRequestedProtocols, protocals);
+        if (string.IsNullOrEmpty(protocol))
+        {
+            logger.LogInformation("{id} {function}:{Path} Protocol Not Supported, Disconnecting", context.TraceIdentifier, nameof(ValidateSupportedPortocol), context.Request.Path);
+
+            using WebSocket toRejectwebSocket = await context.WebSockets.AcceptWebSocketAsync();
+            await toRejectwebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, default);
+            return string.Empty;
+        }
+        return protocol;
+    }
+
+    internal override async ValueTask<bool> ValidateHandshake(HttpContext context, WsClientData session)
+    {
+        try
+        {
+            using var canValidate = await handshakeSemaphore.GetToken();
+            var result = await ValidateHandshakeUnsafe(context, session);
+            return result;
+        }
+        catch (Exception ex)
+        {
+            logger.LogError(ex.Message);
+            logger.LogError(ex.StackTrace);
+        }
+        return false;
+    }
+
+    internal async ValueTask<bool> ValidateHandshakeUnsafe(HttpContext context, WsClientData session)
+    {
+        if (context.RequestAborted.IsCancellationRequested) return false;
+
+        session.ISOCPP20 = context.WebSockets.WebSocketRequestedProtocols.Any(x => x.ToLower() == "ocpp2.0");
+
+        int securityProfile = 0;
+        string authorizationKey = string.Empty;
+        if (string.IsNullOrEmpty(session.Path))
+        {
+            //logger.Log();
+            logger.LogWarning("===========================================");
+            logger.LogWarning("{id} session.Path EMPTY", context.TraceIdentifier);
+            logger.LogWarning("===========================================");
+        }
+
+        string[] words = session.Path.ToString().Split('/');
+        session.ChargeBoxId = words.Last();
+        logger.LogDebug("{id} {0}:{1}", context.TraceIdentifier, session.ChargeBoxId, session.UriScheme);
+
+        foreach (var denyModel in GlobalConfig.DenyModelNames)
+        {
+            if (string.IsNullOrEmpty(denyModel))
+            {
+                continue;
+            }
+
+            if (session.ChargeBoxId.StartsWith(denyModel))
+            {
+                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+
+                logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
+                return false;
+            }
+        }
+
+        if (configuration["MaintainMode"] == "1")
+        {
+            session.ChargeBoxId = session.ChargeBoxId + "_2";
+        }
+
+        logger.LogInformation("{id} ValidateHandshake: {0}", context.TraceIdentifier, session.Path);
+        bool isExistedSN = false;
+        bool authorizated = false;
+
+        var info = await mainDbService.GetMachineIdAndCustomerInfo(session.ChargeBoxId, context.RequestAborted);
+        if (context.RequestAborted.IsCancellationRequested) return false;
+        //var machine = db.Machine.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.IsDelete == false).Select(x => new { x.CustomerId, x.Id }).AsNoTracking().FirstOrDefault();
+        //session.CustomerName = machine == null ? "Unknown" : db.Customer.Where(x => x.Id == machine.CustomerId).Select(x => x.Name).FirstOrDefault();
+        //session.CustomerId = machine == null ? Guid.Empty : machine.CustomerId;
+        //session.MachineId = machine == null ? String.Empty : machine.Id;
+        //isExistedSN = machine == null ? false : true;
+        session.CustomerName = info.CustomerName;
+        session.CustomerId = info.CustomerId;
+        session.MachineId = info.MachineId;
+        isExistedSN = !string.IsNullOrEmpty(info.MachineId);// machine == null ? false : true;
+
+        if (!isExistedSN)
+        {
+            logger.LogInformation("{id} {func} not found", context.TraceIdentifier, nameof(ValidateHandshake));
+            context.Response.StatusCode = StatusCodes.Status404NotFound;
+            return false;
+        }
+
+        //var configVaule = db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.ConfigureName == StandardConfiguration.SecurityProfile)
+        //                  .Select(x => x.ConfigureSetting).FirstOrDefault();
+        var configVaule = await mainDbService.GetMachineSecurityProfile(session.ChargeBoxId, context.RequestAborted);
+        if (context.RequestAborted.IsCancellationRequested) return false;
+        if (string.IsNullOrEmpty(configVaule))
+        {
+            logger.LogInformation("{id} {func} security profile not set", context.TraceIdentifier, nameof(ValidateHandshake));
+            return true;
+        }
+
+        if (!int.TryParse(configVaule, out securityProfile))
+        {
+            logger.LogCritical("ERROR securityProfile setted {securityProfile}", configVaule);
+            return false;
+        }
+
+        if (session.ISOCPP20)
+        {
+            // 1.6 server only support change server  function
+            logger.LogInformation("{id} {func} {securityProfile} is ocpp20", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
+            securityProfile = 0;
+        }
+
+        if (securityProfile == 0)
+        {
+            authorizated = true;
+        }
+
+        if (securityProfile == 3 && session.UriScheme == "ws")
+        {
+            logger.LogInformation("{id} {func} {securityProfile} connection unsecured", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
+            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+            return false;
+        }
+
+        if (securityProfile == 2 && session.UriScheme == "ws")
+        {
+            logger.LogInformation("{id} {func} {securityProfile} connection unsecured", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
+            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+            return false;
+        }
+
+        if (securityProfile == 1 || securityProfile == 2)
+        {
+            string authHeader = context?.Request?.Headers?.Authorization;
+
+            //if (session.Items.ContainsKey("Authorization") || session.Items.ContainsKey("authorization"))
+            if (string.IsNullOrEmpty(authHeader))
+            {
+                logger.LogInformation("{id} {func} {securityProfile} missing auth header", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
+                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+                return false;
+            }
+            //authorizationKey = db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.ConfigureName == StandardConfiguration.AuthorizationKey)
+            //                    .Select(x => x.ConfigureSetting).FirstOrDefault();
+            authorizationKey = await mainDbService.GetMachineAuthorizationKey(session.ChargeBoxId, context.RequestAborted);
+            if (context.RequestAborted.IsCancellationRequested) return false;
+
+            if (session.ISOCPP20)
+            {
+                // 1.6 server only support change server  function
+                securityProfile = 0;
+            }
+
+            logger.LogInformation("{id} ***********Authorization   ", context.TraceIdentifier);
+
+            if (string.IsNullOrEmpty(authorizationKey))
+            {
+                logger.LogInformation("{id} {func} {securityProfile} auth key not defined in server", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
+                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+                return false;
+            }
+
+            //string base64Encoded = session.Items.ContainsKey("Authorization") ? session.Items["Authorization"].ToString().Replace("Basic ", "") : session.Items["authorization"].ToString().Replace("Basic ", "");
+            string base64Encoded = authHeader.Replace("Basic ", "");
+            byte[] data = Convert.FromBase64String(base64Encoded);
+            string[] base64Decoded = Encoding.ASCII.GetString(data).Split(':');
+            logger.LogInformation("{id} ***********Authorization   " + Encoding.ASCII.GetString(data), context.TraceIdentifier);
+            if (base64Decoded.Count() == 2 && base64Decoded[0] == session.ChargeBoxId && base64Decoded[1] == authorizationKey)
+            {
+                authorizated = true;
+            }
+
+            if (!authorizated)
+            {
+                logger.LogInformation("{id} {func} {securityProfile} auth failed", context.TraceIdentifier, nameof(ValidateHandshake), securityProfile);
+                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+                return false;
+            }
+        }
+
+        logger.LogInformation("{id} ValidateHandshake PASS: {0}", context.TraceIdentifier, session.Path);
+        return true;
+    }
+
+    private static string GetSupportedPortocol(IList<string> clientProtocols, IList<string> supportedProtocols)
+    {
+        int supportedProtocolIndex = supportedProtocols.Count - 1;
+        for (; supportedProtocolIndex >= 0; supportedProtocolIndex--)
+        {
+            var testProtocol = supportedProtocols[supportedProtocolIndex];
+            if (clientProtocols.Contains(testProtocol))
+            {
+                return testProtocol;
+            }
+        }
+        return string.Empty;
+    }
+}

+ 167 - 167
EVCB_OCPP.WSServer/Service/WsService/WebsocketService.cs

@@ -1,168 +1,168 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System.Data.Entity.Core.Common.EntitySql;
-using System.Net;
-using System.Net.Http;
-using System.Net.WebSockets;
-using System.ServiceModel.Channels;
-
-namespace EVCB_OCPP.WSServer.Service.WsService;
-
-public class WebsocketService<T> where T : WsSession
-{
-    public WebsocketService(IServiceProvider serviceProvider, ILogger logger)
-    {
-        this.serviceProvider = serviceProvider;
-        this.logger = logger;
-    }
-
-    private readonly IServiceProvider serviceProvider;
-    private readonly ILogger logger;
-
-    public event EventHandler<T> NewSessionConnected;
-
-    public async Task AcceptWebSocket(HttpContext context)
-    {
-        if (!context.WebSockets.IsWebSocketRequest)
-        {
-            return;
-        }
-        var portocol = await ValidateSupportedPortocol(context);
-        if (string.IsNullOrEmpty(portocol))
-        {
-            return;
-        }
-
-        T data = GetSession(context);
-
-        if (!await ValidateHandshake(context, data))
-        {
-            return;
-        }
-
-        using WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(portocol);
-        LogHandshakeResponse(context);
-
-        await AddWebSocket(webSocket, data);
-    }
-
-    internal virtual ValueTask<bool> ValidateHandshake(HttpContext context, T data)
-    {
-        return ValueTask.FromResult(true);
-    }
-
-    internal virtual ValueTask<string> ValidateSupportedPortocol(HttpContext context)
-    {
-        return ValueTask.FromResult(string.Empty);
-    }
-
-
-    private async Task AddWebSocket(WebSocket webSocket, T data)
-    {
-        data.ClientWebSocket = webSocket;
-
-        NewSessionConnected?.Invoke(this, data);
-        await data.EndConnSemaphore.WaitAsync();
-        return;
-    }
-
-    private T GetSession(HttpContext context)
-    {
-        T data = serviceProvider.GetRequiredService<T>();
-        data.Path = context?.Request?.Path;
-        data.SessionID = context.TraceIdentifier;
-        data.UriScheme = GetScheme(context);
-
-        try
-        {
-            var proxyPassClientIp = context.Request.Headers["X-Forwarded-For"];
-            foreach (var infoString in proxyPassClientIp)
-            {
-                foreach (var testIp in infoString.Split(','))
-                {
-                    logger.LogDebug("X-Forwarded-For {ip}", testIp);
-                    if (IPEndPoint.TryParse(testIp, out var parseResult) &&
-                        (parseResult.AddressFamily is System.Net.Sockets.AddressFamily.InterNetwork or System.Net.Sockets.AddressFamily.InterNetworkV6)
-                        )
-                    {
-                        data.Endpoint = parseResult;
-                        break;
-                    }
-                }
-
-                if (data.Endpoint != null)
-                {
-                    break;
-                }
-            }
-
-            if (data.Endpoint is null)
-            {
-                var ipaddress = context.Connection.RemoteIpAddress;
-                var port = context.Connection.RemotePort;
-                data.Endpoint = new IPEndPoint(ipaddress, port);
-            }
-        }
-        catch
-        {
-            data.Endpoint = null;
-        }
-
-        return data;
-    }
-
-    private string GetScheme(HttpContext context)
-    {
-        string toReturn = string.Empty;
-        var rawScheme = string.Empty;
-
-        if (context.Request.Headers.ContainsKey("x-original-host"))
-        {
-            rawScheme = new Uri(context.Request.Headers["x-original-host"]).Scheme;
-        }
-        
-        if (context.Request.Headers.ContainsKey("X-Forwarded-Proto"))
-        {
-            rawScheme = context.Request.Headers["X-Forwarded-Proto"];
-        }
-
-        var origin = context.Request.Headers.Origin.FirstOrDefault();
-        try
-        {
-            toReturn = new Uri(origin).Scheme;
-            return toReturn;
-        }
-        catch
-        {
-        }
-
-        if (string.IsNullOrEmpty(rawScheme))
-        {
-            rawScheme = context.Request.Scheme;
-        }
-        rawScheme = rawScheme.ToLower();
-        if (rawScheme == "http" ||
-            rawScheme == "ws")
-        {
-            return "ws";
-        }
-        if (rawScheme == "https" ||
-            rawScheme == "wss")
-        {
-            return "wss";
-        }
-
-        return toReturn;
-    }
-
-    private void LogHandshakeResponse(HttpContext context)
-    {
-        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "Date:", context.Response.Headers["Date"]);
-        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, context.Request.Protocol + " " + context.Response.StatusCode, "Switching Protocols");
-        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "Upgrade:", context.Response.Headers.Upgrade);
-        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "Connection:", context.Response.Headers.Connection);
-        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "SecWebSocketAccept:", context.Response.Headers.SecWebSocketAccept);
-        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "SecWebSocketProtocol:", context.Response.Headers.SecWebSocketProtocol);
-    }
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System.Data.Entity.Core.Common.EntitySql;
+using System.Net;
+using System.Net.Http;
+using System.Net.WebSockets;
+using System.ServiceModel.Channels;
+
+namespace EVCB_OCPP.WSServer.Service.WsService;
+
+public class WebsocketService<T> where T : WsSession
+{
+    public WebsocketService(IServiceProvider serviceProvider, ILogger logger)
+    {
+        this.serviceProvider = serviceProvider;
+        this.logger = logger;
+    }
+
+    private readonly IServiceProvider serviceProvider;
+    private readonly ILogger logger;
+
+    public event EventHandler<T> NewSessionConnected;
+
+    public async Task AcceptWebSocket(HttpContext context)
+    {
+        if (!context.WebSockets.IsWebSocketRequest)
+        {
+            return;
+        }
+        var portocol = await ValidateSupportedPortocol(context);
+        if (string.IsNullOrEmpty(portocol))
+        {
+            return;
+        }
+
+        T data = GetSession(context);
+
+        if (!await ValidateHandshake(context, data))
+        {
+            return;
+        }
+
+        using WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(portocol);
+        LogHandshakeResponse(context);
+
+        await AddWebSocket(webSocket, data);
+    }
+
+    internal virtual ValueTask<bool> ValidateHandshake(HttpContext context, T data)
+    {
+        return ValueTask.FromResult(true);
+    }
+
+    internal virtual ValueTask<string> ValidateSupportedPortocol(HttpContext context)
+    {
+        return ValueTask.FromResult(string.Empty);
+    }
+
+
+    private async Task AddWebSocket(WebSocket webSocket, T data)
+    {
+        data.ClientWebSocket = webSocket;
+
+        NewSessionConnected?.Invoke(this, data);
+        await data.EndConnSemaphore.WaitAsync();
+        return;
+    }
+
+    private T GetSession(HttpContext context)
+    {
+        T data = serviceProvider.GetRequiredService<T>();
+        data.Path = context?.Request?.Path;
+        data.SessionID = context.TraceIdentifier;
+        data.UriScheme = GetScheme(context);
+
+        try
+        {
+            var proxyPassClientIp = context.Request.Headers["X-Forwarded-For"];
+            foreach (var infoString in proxyPassClientIp)
+            {
+                foreach (var testIp in infoString.Split(','))
+                {
+                    logger.LogDebug("X-Forwarded-For {ip}", testIp);
+                    if (IPEndPoint.TryParse(testIp, out var parseResult) &&
+                        (parseResult.AddressFamily is System.Net.Sockets.AddressFamily.InterNetwork or System.Net.Sockets.AddressFamily.InterNetworkV6)
+                        )
+                    {
+                        data.Endpoint = parseResult;
+                        break;
+                    }
+                }
+
+                if (data.Endpoint != null)
+                {
+                    break;
+                }
+            }
+
+            if (data.Endpoint is null)
+            {
+                var ipaddress = context.Connection.RemoteIpAddress;
+                var port = context.Connection.RemotePort;
+                data.Endpoint = new IPEndPoint(ipaddress, port);
+            }
+        }
+        catch
+        {
+            data.Endpoint = null;
+        }
+
+        return data;
+    }
+
+    private string GetScheme(HttpContext context)
+    {
+        string toReturn = string.Empty;
+        var rawScheme = string.Empty;
+
+        if (context.Request.Headers.ContainsKey("x-original-host"))
+        {
+            rawScheme = new Uri(context.Request.Headers["x-original-host"]).Scheme;
+        }
+        
+        if (context.Request.Headers.ContainsKey("X-Forwarded-Proto"))
+        {
+            rawScheme = context.Request.Headers["X-Forwarded-Proto"];
+        }
+
+        var origin = context.Request.Headers.Origin.FirstOrDefault();
+        try
+        {
+            toReturn = new Uri(origin).Scheme;
+            return toReturn;
+        }
+        catch
+        {
+        }
+
+        if (string.IsNullOrEmpty(rawScheme))
+        {
+            rawScheme = context.Request.Scheme;
+        }
+        rawScheme = rawScheme.ToLower();
+        if (rawScheme == "http" ||
+            rawScheme == "ws")
+        {
+            return "ws";
+        }
+        if (rawScheme == "https" ||
+            rawScheme == "wss")
+        {
+            return "wss";
+        }
+
+        return toReturn;
+    }
+
+    private void LogHandshakeResponse(HttpContext context)
+    {
+        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "Date:", context.Response.Headers["Date"]);
+        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, context.Request.Protocol + " " + context.Response.StatusCode, "Switching Protocols");
+        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "Upgrade:", context.Response.Headers.Upgrade);
+        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "Connection:", context.Response.Headers.Connection);
+        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "SecWebSocketAccept:", context.Response.Headers.SecWebSocketAccept);
+        logger.LogInformation("{0} {1} {2}", context.TraceIdentifier, "SecWebSocketProtocol:", context.Response.Headers.SecWebSocketProtocol);
+    }
 }

+ 158 - 158
EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs

@@ -1,159 +1,159 @@
-
-using EVCB_OCPP.Packet.Messages.Basic;
-using EVCB_OCPP.WSServer.Dto;
-using log4net.Core;
-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
-  /// </summary>
-    public Queue queue = new Queue();
-
-    public EVCB_OCPP20.Packet.Messages.Basic.Queue queue20 = new EVCB_OCPP20.Packet.Messages.Basic.Queue();
-
-    public BootStatus BootStatus { get; set; } = BootStatus.Startup;
-    //public bool? IsPending { set; get; }
-    public bool IsCheckIn { set; get; } = false;
-
-    public string ChargeBoxId { set; get; }
-
-    public Guid CustomerId { get; set; }
-
-    public string MachineId { set; get; }
-
-    public bool ISOCPP20 { set; get; }
-
-    public bool ResetSecurityProfile { set; get; }
-
-
-    public bool IsAC { set; get; } = true;
-
-    #region Billing
-
-    public Dictionary<string, string> UserPrices { set; get; } = new Dictionary<string, string>();
-
-    public Dictionary<string, string> UserDisplayPrices { set; get; } = new Dictionary<string, string>();
-
-    public List<ChargingPrice> ChargingPrices { set; get; }
-
-    /// <summary>
-    /// 電樁顯示費率
-    /// </summary>
-    public string DisplayPrice { set; get; }
-
-    /// <summary>
-    /// 充電費率 以小時計費
-    /// </summary>
-    public decimal ChargingFeebyHour { set; get; }
-
-    /// <summary>
-    /// 停車費率 以小時計費
-    /// </summary>
-    public decimal ParkingFee { set; get; }
-
-    /// <summary>
-    /// 電樁是否計費
-    /// </summary>
-    public bool IsBilling { set; get; }
-
-    /// <summary>
-    /// 收費方式 1: 以度計費 2:以小時計費
-    /// </summary>
-    public int BillingMethod { set; get; }
-
-
-    /// <summary>
-    /// 電樁適用幣別
-    /// </summary>
-    public string Currency { get; internal set; }
-
-    #endregion
-
-    public string CustomerName { get; set; }
-    public string ChargePointVendor { get; set; }
-
-    //public int? StationId { set; get; } = null;
-    public Dictionary<string, object> Data = new Dictionary<string, object>();
-
-    public event EventHandler<string> m_ReceiveData;
-
-    private string stringBuffer = string.Empty;
-    private readonly ILogger<WsClientData> logger;
-    private List<Task> AttachedTasks = new List<Task>();
-
-    public WsClientData(ILogger<WsClientData> logger) : base(logger)
-    {
-        ChargeBoxId = SessionID;
-        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)
-    {
-        stringBuffer += data;
-        //logger.LogInformation("{StringBuffer}", stringBuffer);
-        while (TryGetOCPPMsg(ref stringBuffer, out var msg))
-        {
-            m_ReceiveData?.Invoke(this, msg);
-        }
-    }
-
-    private bool TryGetOCPPMsg(ref string buffer, out string msg)
-    {
-        msg = string.Empty;
-        int? startIndex = null;
-        int? stopIndex = null;
-        uint cnt = 0;
-
-        for (int index = 0; index < buffer.Length; index++)
-        {
-            if (buffer[index] == '[')
-            {
-                cnt++;
-                if (startIndex == null)
-                {
-                    startIndex = index;
-                }
-            }
-
-            if (startIndex != null && buffer[index] == ']')
-            {
-                cnt--;
-            }
-
-            if (startIndex != null && cnt == 0)
-            {
-                stopIndex = index;
-                break;
-            }
-        }
-
-        if (startIndex is not null && stopIndex is not null)
-        {
-            msg = buffer.Substring(startIndex.Value, (stopIndex.Value - startIndex.Value)+1);
-            buffer = buffer.Substring(stopIndex.Value + 1);
-            return true;
-        }
-
-        return false;
-    }
+
+using EVCB_OCPP.Packet.Messages.Basic;
+using EVCB_OCPP.WSServer.Dto;
+using log4net.Core;
+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
+  /// </summary>
+    public Queue queue = new Queue();
+
+    public EVCB_OCPP20.Packet.Messages.Basic.Queue queue20 = new EVCB_OCPP20.Packet.Messages.Basic.Queue();
+
+    public BootStatus BootStatus { get; set; } = BootStatus.Startup;
+    //public bool? IsPending { set; get; }
+    public bool IsCheckIn { set; get; } = false;
+
+    public string ChargeBoxId { set; get; }
+
+    public Guid CustomerId { get; set; }
+
+    public string MachineId { set; get; }
+
+    public bool ISOCPP20 { set; get; }
+
+    public bool ResetSecurityProfile { set; get; }
+
+
+    public bool IsAC { set; get; } = true;
+
+    #region Billing
+
+    public Dictionary<string, string> UserPrices { set; get; } = new Dictionary<string, string>();
+
+    public Dictionary<string, string> UserDisplayPrices { set; get; } = new Dictionary<string, string>();
+
+    public List<ChargingPrice> ChargingPrices { set; get; }
+
+    /// <summary>
+    /// 電樁顯示費率
+    /// </summary>
+    public string DisplayPrice { set; get; }
+
+    /// <summary>
+    /// 充電費率 以小時計費
+    /// </summary>
+    public decimal ChargingFeebyHour { set; get; }
+
+    /// <summary>
+    /// 停車費率 以小時計費
+    /// </summary>
+    public decimal ParkingFee { set; get; }
+
+    /// <summary>
+    /// 電樁是否計費
+    /// </summary>
+    public bool IsBilling { set; get; }
+
+    /// <summary>
+    /// 收費方式 1: 以度計費 2:以小時計費
+    /// </summary>
+    public int BillingMethod { set; get; }
+
+
+    /// <summary>
+    /// 電樁適用幣別
+    /// </summary>
+    public string Currency { get; internal set; }
+
+    #endregion
+
+    public string CustomerName { get; set; }
+    public string ChargePointVendor { get; set; }
+
+    //public int? StationId { set; get; } = null;
+    public Dictionary<string, object> Data = new Dictionary<string, object>();
+
+    public event EventHandler<string> m_ReceiveData;
+
+    private string stringBuffer = string.Empty;
+    private readonly ILogger<WsClientData> logger;
+    private List<Task> AttachedTasks = new List<Task>();
+
+    public WsClientData(ILogger<WsClientData> logger) : base(logger)
+    {
+        ChargeBoxId = SessionID;
+        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)
+    {
+        stringBuffer += data;
+        //logger.LogInformation("{StringBuffer}", stringBuffer);
+        while (TryGetOCPPMsg(ref stringBuffer, out var msg))
+        {
+            m_ReceiveData?.Invoke(this, msg);
+        }
+    }
+
+    private bool TryGetOCPPMsg(ref string buffer, out string msg)
+    {
+        msg = string.Empty;
+        int? startIndex = null;
+        int? stopIndex = null;
+        uint cnt = 0;
+
+        for (int index = 0; index < buffer.Length; index++)
+        {
+            if (buffer[index] == '[')
+            {
+                cnt++;
+                if (startIndex == null)
+                {
+                    startIndex = index;
+                }
+            }
+
+            if (startIndex != null && buffer[index] == ']')
+            {
+                cnt--;
+            }
+
+            if (startIndex != null && cnt == 0)
+            {
+                stopIndex = index;
+                break;
+            }
+        }
+
+        if (startIndex is not null && stopIndex is not null)
+        {
+            msg = buffer.Substring(startIndex.Value, (stopIndex.Value - startIndex.Value)+1);
+            buffer = buffer.Substring(stopIndex.Value + 1);
+            return true;
+        }
+
+        return false;
+    }
 }

+ 146 - 146
EVCB_OCPP.WSServer/appsettings.json

@@ -1,147 +1,147 @@
-{
-  "Kestrel": {
-    "Endpoints": {
-      "Http": {
-        "Url": "http://*:80"
-      }
-    }
-  },
-  "WSPort": 54089,
-  "WSSPort": "",
-  "LocalAuthAPI": "",
-  "apipass": "12345678",
-  "LogProvider": "NLog",
-  "OCPP20_WSUrl": "ws://ocpp.phihong.com.tw:5004",
-  "OCPP20_WSSUrl": "ws://ocpp.phihong.com.tw:5004",
-  "MaintainMode": 0,
-  "Logging": {
-    "LogLevel": {
-      "Default": "Information",
-      "Microsoft.AspNetCore": "Warning"
-    }
-  },
-  "NLog": {
-    "targets": {
-      "async": true,
-      "f": {
-        "type": "File",
-        "keepFileOpen": false,
-        "fileName": "/home/logs/server/${shortdate}.log",
-        "layout": "${longdate} ${uppercase:${level}} ${message}"
-      },
-      "ws": {
-        "type": "File",
-        "keepFileOpen": false,
-        "fileName": "/home/logs/server/WS_${shortdate}.log",
-        "layout": "${longdate} ${uppercase:${level}} ${message}"
-      },
-      "Console": {
-        "type": "Console",
-        "layout": "${longdate} ${uppercase:${level}} ${message}"
-      },
-      "auth": {
-        "type": "File",
-        "keepFileOpen": false,
-        "fileName": "/home/logs/server/Auth_${shortdate}.log",
-        "layout": "${longdate} ${callsite} ${uppercase:${level}} ${message}"
-      }
-    },
-    "rules": [
-      {
-        "ruleName": "HttpRecord",
-        "logger": "HeaderRecord*",
-        "minLevel": "Info",
-        "writeTo": "ws"
-      },
-      {
-        "ruleName": "WsRecord",
-        "logger": "EVCB_OCPP.WSServer.Service.WsService*",
-        "minLevel": "Info",
-        "writeTo": "ws"
-      },
-      {
-        "ruleName": "OuterBusinessService",
-        "logger": "EVCB_OCPP.WSServer.Service.OuterBusinessService",
-        "level": "Info",
-        "writeTo": "auth"
-      },
-      {
-        "ruleName": "FileLog",
-        "logger": "EVCB_OCPP.*",
-        "minLevel": "Debug",
-        "writeTo": "f"
-      },
-      {
-        "ruleName": "ConsoleLog",
-        "logger": "EVCB_OCPP.*",
-        "minlevel": "Trace",
-        "writeTo": "console"
-      },
-      {
-        "ruleName": "MsFileLog",
-        "logger": "Microsoft.*",
-        "minLevel": "Warn",
-        "writeTo": "f"
-      },
-      {
-        "ruleName": "MsConsoleLog",
-        "logger": "Microsoft.*",
-        "minlevel": "Warn",
-        "writeTo": "console"
-      },
-      {
-        "ruleName": "DbConsoleLog",
-        "logger": "System.Data.Entity.*",
-        "minlevel": "Info",
-        "writeTo": "console"
-      },
-      {
-        "ruleName": "DbFileLog",
-        "logger": "System.Data.Entity.*",
-        "minlevel": "Info",
-        "writeTo": "f"
-      }
-    ]
-  },
-  "ReverseProxy": {
-    "Routes": {
-      "routeApi": {
-        "ClusterId": "api",
-        "Match": {
-          "Path": "/api/{**remainder}"
-        },
-        "Transforms": [
-          { "PathRemovePrefix": "/api" }
-        ]
-      },
-      "route1": {
-        "ClusterId": "ocpp",
-        "Match": {
-          "Path": "{**catch-all}"
-        }
-      }
-    },
-    "Clusters": {
-      "ocpp": {
-        "Destinations": {
-          "destination1": {
-            "Address": "http://localhost:54089/"
-          }
-        }
-      },
-      "api": {
-        "Destinations": {
-          "destination1": {
-            "Address": "http://localhost:54088/"
-          }
-        }
-      }
-    }
-  },
-  "ConnectionStrings": {
-    "ConnectionLogDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_ConnectionLog;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=True;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;",
-    "MainDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_Main;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=True;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=1024;Connection Lifetime=0;Pooling=true;Min Pool Size=150;",
-    "MeterValueDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_MeterValue;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=True;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;",
-    "WebDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_Web;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=True;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;"
-  }
+{
+  "Kestrel": {
+    "Endpoints": {
+      "Http": {
+        "Url": "http://*:80"
+      }
+    }
+  },
+  "WSPort": 54089,
+  "WSSPort": "",
+  "LocalAuthAPI": "",
+  "apipass": "12345678",
+  "LogProvider": "NLog",
+  "OCPP20_WSUrl": "ws://ocpp.phihong.com.tw:5004",
+  "OCPP20_WSSUrl": "ws://ocpp.phihong.com.tw:5004",
+  "MaintainMode": 0,
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "NLog": {
+    "targets": {
+      "async": true,
+      "f": {
+        "type": "File",
+        "keepFileOpen": false,
+        "fileName": "/home/logs/server/${shortdate}.log",
+        "layout": "${longdate} ${uppercase:${level}} ${message}"
+      },
+      "ws": {
+        "type": "File",
+        "keepFileOpen": false,
+        "fileName": "/home/logs/server/WS_${shortdate}.log",
+        "layout": "${longdate} ${uppercase:${level}} ${message}"
+      },
+      "Console": {
+        "type": "Console",
+        "layout": "${longdate} ${uppercase:${level}} ${message}"
+      },
+      "auth": {
+        "type": "File",
+        "keepFileOpen": false,
+        "fileName": "/home/logs/server/Auth_${shortdate}.log",
+        "layout": "${longdate} ${callsite} ${uppercase:${level}} ${message}"
+      }
+    },
+    "rules": [
+      {
+        "ruleName": "HttpRecord",
+        "logger": "HeaderRecord*",
+        "minLevel": "Info",
+        "writeTo": "ws"
+      },
+      {
+        "ruleName": "WsRecord",
+        "logger": "EVCB_OCPP.WSServer.Service.WsService*",
+        "minLevel": "Info",
+        "writeTo": "ws"
+      },
+      {
+        "ruleName": "OuterBusinessService",
+        "logger": "EVCB_OCPP.WSServer.Service.OuterBusinessService",
+        "level": "Info",
+        "writeTo": "auth"
+      },
+      {
+        "ruleName": "FileLog",
+        "logger": "EVCB_OCPP.*",
+        "minLevel": "Debug",
+        "writeTo": "f"
+      },
+      {
+        "ruleName": "ConsoleLog",
+        "logger": "EVCB_OCPP.*",
+        "minlevel": "Trace",
+        "writeTo": "console"
+      },
+      {
+        "ruleName": "MsFileLog",
+        "logger": "Microsoft.*",
+        "minLevel": "Warn",
+        "writeTo": "f"
+      },
+      {
+        "ruleName": "MsConsoleLog",
+        "logger": "Microsoft.*",
+        "minlevel": "Warn",
+        "writeTo": "console"
+      },
+      {
+        "ruleName": "DbConsoleLog",
+        "logger": "System.Data.Entity.*",
+        "minlevel": "Info",
+        "writeTo": "console"
+      },
+      {
+        "ruleName": "DbFileLog",
+        "logger": "System.Data.Entity.*",
+        "minlevel": "Info",
+        "writeTo": "f"
+      }
+    ]
+  },
+  "ReverseProxy": {
+    "Routes": {
+      "routeApi": {
+        "ClusterId": "api",
+        "Match": {
+          "Path": "/api/{**remainder}"
+        },
+        "Transforms": [
+          { "PathRemovePrefix": "/api" }
+        ]
+      },
+      "route1": {
+        "ClusterId": "ocpp",
+        "Match": {
+          "Path": "{**catch-all}"
+        }
+      }
+    },
+    "Clusters": {
+      "ocpp": {
+        "Destinations": {
+          "destination1": {
+            "Address": "http://localhost:54089/"
+          }
+        }
+      },
+      "api": {
+        "Destinations": {
+          "destination1": {
+            "Address": "http://localhost:54088/"
+          }
+        }
+      }
+    }
+  },
+  "ConnectionStrings": {
+    "ConnectionLogDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_ConnectionLog;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=True;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;",
+    "MainDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_Main;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=True;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=1024;Connection Lifetime=0;Pooling=true;Min Pool Size=150;",
+    "MeterValueDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_MeterValue;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=True;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;",
+    "WebDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_Web;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=True;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;"
+  }
 }

+ 109 - 109
Prerelease_Build.ps1

@@ -1,110 +1,110 @@
-# 設定 ASCII 藝術字的內容
-$asciiArt = @"
-  _____  _____  ______ _____  ______ _      ______           _____ ______ 
- |  __ \|  __ \|  ____|  __ \|  ____| |    |  ____|   /\    / ____|  ____|
- | |__) | |__) | |__  | |__) | |__  | |    | |__     /  \  | (___ | |__   
- |  ___/|  _  /|  __| |  _  /|  __| | |    |  __|   / /\ \  \___ \|  __|  
- | |    | | \ \| |____| | \ \| |____| |____| |____ / ____ \ ____) | |____ 
- |_|    |_|  \_\______|_|  \_\______|______|______/_/    \_\_____/|______|
-"@
-
-# 顯示 ASCII 藝術字
-Write-Host $asciiArt
-
-#第一次建立專案請先設定ACR Name
-$registryname="evdevcontainerregistry"
-$fullregistryname="evdevcontainerregistry.azurecr.io"
-#第一次建立專案請先設定專案名稱
-$imagerepositoryname="server"
-#target tag can be {projectname}_RC or RC on pre-release stage
-$default_dev_prefix = "Docker_test_"
-$target_tag="Docker_RC"
-
-#檢查是否有尚未commit的更新
-$status = git status --porcelain
-if (-not $status) {
-	Write-Output "All changes committed"
-} else {
-    Write-Output "There are uncommitted changes"
-	exit
-}
-
-#檢查remote是否有新commit未處理
-git fetch
-$status = git status --porcelain -b
-if (($status -match "ahead") -or ($status -match "behind") -or ($status -match "gone") )
-{
-	Write-Output "git branch not synced"
-	exit
-}
-else {
-    Write-Output "git branch is synced"
-}
-
-#podman acr登入
-Write-Host "ACR Login....."
-$token = az acr login --name $registryname --expose-token --output tsv --query accessToken
-$user = "00000000-0000-0000-0000-000000000000"
-podman login $fullregistryname -u $user -p $token
-
-$source_tag = Read-Host "Please type the source tag that you want to do RC."
-#預設跟Dev_build一樣
-if (-not $source_tag) {
-	$username = az account show --query user.name
-	$username = $username.TrimStart("""").Split('@')[0]
-	$source_tag=$default_dev_prefix+$username
-}
-$sourcetag_response = Read-Host "Do you want to do the RC tag with $source_tag ?(Y/N)"
-while($sourcetag_response -eq "N")
-{
-	$source_tag = Read-Host "Please type the source tag that you want to do RC."
-	#預設跟Dev_build一樣
-	if (-not $source_tag) {
-		$source_tag=$default_dev_prefix+$username
-	}
-	$sourcetag_response = Read-Host "Do you want to do the RC tag with $source_tag ?(Y/N)"
-}
-
-
-$fulltag=$registryname+".azurecr.io/"+$imagerepositoryname+":"+$targettag
-$source_imagename = $imagerepositoryname+":"+$source_tag
-$target_imagename = $imagerepositoryname+":"+$target_tag
-$source_fulltag = $registryname+".azurecr.io/"+$imagerepositoryname+":"+$source_tag
-$target_fulltag = $registryname+".azurecr.io/"+$imagerepositoryname+":"+$target_tag
-
-$final_response = Read-Host "Final confirmation: Do you want to assign the RC tag name "$target_imagename" to the existing tag with "$source_imagename"?(Y/N)"
-if($final_response -eq "N")
-{
-	write-host "Please restart the process."
-}
-else
-{
-	#取得目前RC的message
-	$rc_message = git tag -l --format='%(contents)' $target_tag
-	if (!$rc_message)
-	{
-		$rc_message = ""
-	}
-	#暫存RC message
-	$filePath = Join-Path -Path (Get-Location) -ChildPath "rc_message.txt"
-	[System.IO.File]::WriteAllLines($filePath, $rc_message)
-	#刪除遠端git的RC tag
-	git push --delete origin $target_tag
-	#刪除本地端git的RC tag
-	git tag --delete $target_tag
-	#標註目前commit為RC
-	git tag -a $target_tag -F "rc_message.txt" --edit
-	#更新遠端tag
-	git push --follow-tags
-	#刪除暫存檔
-	Remove-Item "rc_message.txt"
-	
-	#解除image鎖定
-	az acr repository update --name $registryname --image $target_imagename --delete-enabled true --write-enabled true
-	podman pull $source_fulltag
-	podman image tag $source_fulltag $target_fulltag
-	podman push $target_fulltag
-
-	#鎖定image
-	az acr repository update --name $registryname --image $target_imagename --delete-enabled false --write-enabled false
+# 設定 ASCII 藝術字的內容
+$asciiArt = @"
+  _____  _____  ______ _____  ______ _      ______           _____ ______ 
+ |  __ \|  __ \|  ____|  __ \|  ____| |    |  ____|   /\    / ____|  ____|
+ | |__) | |__) | |__  | |__) | |__  | |    | |__     /  \  | (___ | |__   
+ |  ___/|  _  /|  __| |  _  /|  __| | |    |  __|   / /\ \  \___ \|  __|  
+ | |    | | \ \| |____| | \ \| |____| |____| |____ / ____ \ ____) | |____ 
+ |_|    |_|  \_\______|_|  \_\______|______|______/_/    \_\_____/|______|
+"@
+
+# 顯示 ASCII 藝術字
+Write-Host $asciiArt
+
+#第一次建立專案請先設定ACR Name
+$registryname="evdevcontainerregistry"
+$fullregistryname="evdevcontainerregistry.azurecr.io"
+#第一次建立專案請先設定專案名稱
+$imagerepositoryname="server"
+#target tag can be {projectname}_RC or RC on pre-release stage
+$default_dev_prefix = "Docker_test_"
+$target_tag="Docker_RC"
+
+#檢查是否有尚未commit的更新
+$status = git status --porcelain
+if (-not $status) {
+	Write-Output "All changes committed"
+} else {
+    Write-Output "There are uncommitted changes"
+	exit
+}
+
+#檢查remote是否有新commit未處理
+git fetch
+$status = git status --porcelain -b
+if (($status -match "ahead") -or ($status -match "behind") -or ($status -match "gone") )
+{
+	Write-Output "git branch not synced"
+	exit
+}
+else {
+    Write-Output "git branch is synced"
+}
+
+#podman acr登入
+Write-Host "ACR Login....."
+$token = az acr login --name $registryname --expose-token --output tsv --query accessToken
+$user = "00000000-0000-0000-0000-000000000000"
+podman login $fullregistryname -u $user -p $token
+
+$source_tag = Read-Host "Please type the source tag that you want to do RC."
+#預設跟Dev_build一樣
+if (-not $source_tag) {
+	$username = az account show --query user.name
+	$username = $username.TrimStart("""").Split('@')[0]
+	$source_tag=$default_dev_prefix+$username
+}
+$sourcetag_response = Read-Host "Do you want to do the RC tag with $source_tag ?(Y/N)"
+while($sourcetag_response -eq "N")
+{
+	$source_tag = Read-Host "Please type the source tag that you want to do RC."
+	#預設跟Dev_build一樣
+	if (-not $source_tag) {
+		$source_tag=$default_dev_prefix+$username
+	}
+	$sourcetag_response = Read-Host "Do you want to do the RC tag with $source_tag ?(Y/N)"
+}
+
+
+$fulltag=$registryname+".azurecr.io/"+$imagerepositoryname+":"+$targettag
+$source_imagename = $imagerepositoryname+":"+$source_tag
+$target_imagename = $imagerepositoryname+":"+$target_tag
+$source_fulltag = $registryname+".azurecr.io/"+$imagerepositoryname+":"+$source_tag
+$target_fulltag = $registryname+".azurecr.io/"+$imagerepositoryname+":"+$target_tag
+
+$final_response = Read-Host "Final confirmation: Do you want to assign the RC tag name "$target_imagename" to the existing tag with "$source_imagename"?(Y/N)"
+if($final_response -eq "N")
+{
+	write-host "Please restart the process."
+}
+else
+{
+	#取得目前RC的message
+	$rc_message = git tag -l --format='%(contents)' $target_tag
+	if (!$rc_message)
+	{
+		$rc_message = ""
+	}
+	#暫存RC message
+	$filePath = Join-Path -Path (Get-Location) -ChildPath "rc_message.txt"
+	[System.IO.File]::WriteAllLines($filePath, $rc_message)
+	#刪除遠端git的RC tag
+	git push --delete origin $target_tag
+	#刪除本地端git的RC tag
+	git tag --delete $target_tag
+	#標註目前commit為RC
+	git tag -a $target_tag -F "rc_message.txt" --edit
+	#更新遠端tag
+	git push --follow-tags
+	#刪除暫存檔
+	Remove-Item "rc_message.txt"
+	
+	#解除image鎖定
+	az acr repository update --name $registryname --image $target_imagename --delete-enabled true --write-enabled true
+	podman pull $source_fulltag
+	podman image tag $source_fulltag $target_fulltag
+	podman push $target_fulltag
+
+	#鎖定image
+	az acr repository update --name $registryname --image $target_imagename --delete-enabled false --write-enabled false
 }

+ 9 - 9
Prod_Build_podman.bat

@@ -1,10 +1,10 @@
-for /f %%i in ('git rev-parse --short HEAD') do set ssha=%%i
-FOR /f %%i IN ( version.txt ) DO set version=%%i
-git push
-git tag -a %version%
-git push --follow-tags
-podman build ./ -t evdevcontainerregistry.azurecr.io/server:%version% --label "git-commit=%ssha%"
-
-FOR /f %%i IN ('az acr login --name evdevcontainerregistry --expose-token --output tsv --query accessToken') do (SET token=%%i)
-podman login evdevcontainerregistry.azurecr.io -u "00000000-0000-0000-0000-000000000000" -p %token%
+for /f %%i in ('git rev-parse --short HEAD') do set ssha=%%i
+FOR /f %%i IN ( version.txt ) DO set version=%%i
+git push
+git tag -a %version%
+git push --follow-tags
+podman build ./ -t evdevcontainerregistry.azurecr.io/server:%version% --label "git-commit=%ssha%"
+
+FOR /f %%i IN ('az acr login --name evdevcontainerregistry --expose-token --output tsv --query accessToken') do (SET token=%%i)
+podman login evdevcontainerregistry.azurecr.io -u "00000000-0000-0000-0000-000000000000" -p %token%
 podman push evdevcontainerregistry.azurecr.io/server:%version%

+ 101 - 101
Release_Build.ps1

@@ -1,101 +1,101 @@
-
-# 設定 ASCII 藝術字的內容
-$asciiArt = @"
-
-  _____  ______ _      ______           _____ ______ 
- |  __ \|  ____| |    |  ____|   /\    / ____|  ____|
- | |__) | |__  | |    | |__     /  \  | (___ | |__   
- |  _  /|  __| | |    |  __|   / /\ \  \___ \|  __|  
- | | \ \| |____| |____| |____ / ____ \ ____) | |____ 
- |_|  \_\______|______|______/_/    \_\_____/|______|
-                                                     
-                                                     
-                                                                     
-"@
-
-# 顯示 ASCII 藝術字
-Write-Host $asciiArt
-#第一次建立專案請先設定ACR Name
-$registryname="evdevcontainerregistry"
-$fullregistryname="evdevcontainerregistry.azurecr.io"
-#第一次建立專案請先設定專案名稱
-$imagerepositoryname="server"
-#來源TAG
-$source_tag="Docker_RC"
-$targetTagPrefix="Docker_v"
-
-#檢查remote是否有新commit未處理
-git fetch
-$status = git status --porcelain -b
-if (($status -match "ahead") -or ($status -match "behind") -or ($status -match "gone") )
-{
-	Write-Output "git branch not synced"
-	exit
-}
-else {
-    Write-Output "git branch is synced"
-}
-
-#podman acr登入
-Write-Host "ACR Login....."
-$token = az acr login --name $registryname --expose-token --output tsv --query accessToken
-$user = "00000000-0000-0000-0000-000000000000"
-podman login $fullregistryname -u $user -p $token
-
-$sourcetag_response = Read-Host "Do you want to do release tag with $source_tag ?(Y/N)"
-while($sourcetag_response -ne "Y")
-{
-	$source_tag = Read-Host "Please type the source tag that you want to do newtag."
-	$sourcetag_response = Read-Host "Do you want to do release tag with $source_tag ?(Y/N)"
-}
-	
-$target_tag = Read-Host "Please type the release tag version (make sure that the same as tagname from azure devops)."
-$target_tag = $targetTagPrefix + $target_tag
-$targettag_response = Read-Host "Please confirm the release tag $target_tag (Y/N)."
-
-while($targettag_response -ne "Y")
-{
-	$target_tag = Read-Host "Please type the release tag version (make sure that the same as tagname from azure devops)."
-	$target_tag = $targetTagPrefix + $target_tag
-	$targettag_response = Read-Host "Please confirm the release tag $target_tag (Y/N)."
-}
-
-
-
-$fulltag=$registryname+".azurecr.io/"+$imagerepositoryname+":"+$targettag
-$source_imagename = $imagerepositoryname+":"+$source_tag
-$target_imagename = $imagerepositoryname+":"+$target_tag
-$source_fulltag = $registryname+".azurecr.io/"+$imagerepositoryname+":"+$source_tag
-$target_fulltag = $registryname+".azurecr.io/"+$imagerepositoryname+":"+$target_tag
-
-$final_response = Read-Host "Final confirmation: Do you want to assign the new tag name "$target_imagename" to the existing tag with "$source_imagename"?(Y/N)"
-if($final_response -ne "Y")
-{
-	write-host "Please restart the process."
-}
-else
-{
-	#取得目前RC的message
-	$rc_message = git tag -l --format='%(contents)' $source_tag
-	if ($status -match "unknown revision or path not in the working tree")
-	{
-		Write-Host "source tag missing"
-		exit
-	}
-	#暫存RC message
-	$filePath = Join-Path -Path (Get-Location) -ChildPath "rc_message.txt"
-	[System.IO.File]::WriteAllLines($filePath, $rc_message)
-	#標註RC為新的tag
-	git tag -a $target_tag -F "rc_message.txt" $source_tag
-	#更新遠端tag
-	git push --follow-tags
-	#刪除暫存檔
-	Remove-Item "rc_message.txt"
-	
-	podman pull $source_fulltag
-	podman image tag $source_fulltag $target_fulltag
-	podman push $target_fulltag
-
-	#鎖定image
-	az acr repository update --name $registryname --image $target_imagename --delete-enabled false --write-enabled false
-}
+
+# 設定 ASCII 藝術字的內容
+$asciiArt = @"
+
+  _____  ______ _      ______           _____ ______ 
+ |  __ \|  ____| |    |  ____|   /\    / ____|  ____|
+ | |__) | |__  | |    | |__     /  \  | (___ | |__   
+ |  _  /|  __| | |    |  __|   / /\ \  \___ \|  __|  
+ | | \ \| |____| |____| |____ / ____ \ ____) | |____ 
+ |_|  \_\______|______|______/_/    \_\_____/|______|
+                                                     
+                                                     
+                                                                     
+"@
+
+# 顯示 ASCII 藝術字
+Write-Host $asciiArt
+#第一次建立專案請先設定ACR Name
+$registryname="evdevcontainerregistry"
+$fullregistryname="evdevcontainerregistry.azurecr.io"
+#第一次建立專案請先設定專案名稱
+$imagerepositoryname="server"
+#來源TAG
+$source_tag="Docker_RC"
+$targetTagPrefix="Docker_v"
+
+#檢查remote是否有新commit未處理
+git fetch
+$status = git status --porcelain -b
+if (($status -match "ahead") -or ($status -match "behind") -or ($status -match "gone") )
+{
+	Write-Output "git branch not synced"
+	exit
+}
+else {
+    Write-Output "git branch is synced"
+}
+
+#podman acr登入
+Write-Host "ACR Login....."
+$token = az acr login --name $registryname --expose-token --output tsv --query accessToken
+$user = "00000000-0000-0000-0000-000000000000"
+podman login $fullregistryname -u $user -p $token
+
+$sourcetag_response = Read-Host "Do you want to do release tag with $source_tag ?(Y/N)"
+while($sourcetag_response -ne "Y")
+{
+	$source_tag = Read-Host "Please type the source tag that you want to do newtag."
+	$sourcetag_response = Read-Host "Do you want to do release tag with $source_tag ?(Y/N)"
+}
+	
+$target_tag = Read-Host "Please type the release tag version (make sure that the same as tagname from azure devops)."
+$target_tag = $targetTagPrefix + $target_tag
+$targettag_response = Read-Host "Please confirm the release tag $target_tag (Y/N)."
+
+while($targettag_response -ne "Y")
+{
+	$target_tag = Read-Host "Please type the release tag version (make sure that the same as tagname from azure devops)."
+	$target_tag = $targetTagPrefix + $target_tag
+	$targettag_response = Read-Host "Please confirm the release tag $target_tag (Y/N)."
+}
+
+
+
+$fulltag=$registryname+".azurecr.io/"+$imagerepositoryname+":"+$targettag
+$source_imagename = $imagerepositoryname+":"+$source_tag
+$target_imagename = $imagerepositoryname+":"+$target_tag
+$source_fulltag = $registryname+".azurecr.io/"+$imagerepositoryname+":"+$source_tag
+$target_fulltag = $registryname+".azurecr.io/"+$imagerepositoryname+":"+$target_tag
+
+$final_response = Read-Host "Final confirmation: Do you want to assign the new tag name "$target_imagename" to the existing tag with "$source_imagename"?(Y/N)"
+if($final_response -ne "Y")
+{
+	write-host "Please restart the process."
+}
+else
+{
+	#取得目前RC的message
+	$rc_message = git tag -l --format='%(contents)' $source_tag
+	if ($status -match "unknown revision or path not in the working tree")
+	{
+		Write-Host "source tag missing"
+		exit
+	}
+	#暫存RC message
+	$filePath = Join-Path -Path (Get-Location) -ChildPath "rc_message.txt"
+	[System.IO.File]::WriteAllLines($filePath, $rc_message)
+	#標註RC為新的tag
+	git tag -a $target_tag -F "rc_message.txt" $source_tag
+	#更新遠端tag
+	git push --follow-tags
+	#刪除暫存檔
+	Remove-Item "rc_message.txt"
+	
+	podman pull $source_fulltag
+	podman image tag $source_fulltag $target_fulltag
+	podman push $target_fulltag
+
+	#鎖定image
+	az acr repository update --name $registryname --image $target_imagename --delete-enabled false --write-enabled false
+}

+ 86 - 86
SocketEngine/SuperSocket.SocketEngine.csproj

@@ -1,87 +1,87 @@
-<Project Sdk="Microsoft.NET.Sdk">
-  <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
-    <OutputType>Library</OutputType>
-    <RootNamespace>SuperSocket.SocketEngine</RootNamespace>
-    <AssemblyName>SuperSocket.SocketEngine</AssemblyName>
-    <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>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
-    <DocumentationFile>bin\Debug\SuperSocket.SocketEngine.XML</DocumentationFile>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
-    <DocumentationFile>bin\Release\SuperSocket.SocketEngine.XML</DocumentationFile>
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-  </PropertyGroup>
-  <PropertyGroup>
-    <SignAssembly>true</SignAssembly>
-  </PropertyGroup>
-  <PropertyGroup>
-    <AssemblyOriginatorKeyFile>.\supersocket.snk</AssemblyOriginatorKeyFile>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Update="System.Core">
-      <RequiredTargetFramework>3.5</RequiredTargetFramework>
-    </Reference>
-  </ItemGroup>
-  <ItemGroup>
-    <BootstrapperPackage Include=".NETFramework,Version=v4.0">
-      <Visible>False</Visible>
-      <ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>
-      <Install>true</Install>
-    </BootstrapperPackage>
-    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
-      <Visible>False</Visible>
-      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
-      <Install>false</Install>
-    </BootstrapperPackage>
-    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
-      <Visible>False</Visible>
-      <ProductName>.NET Framework 3.5 SP1</ProductName>
-      <Install>false</Install>
-    </BootstrapperPackage>
-    <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
-      <Visible>False</Visible>
-      <ProductName>Windows Installer 3.1</ProductName>
-      <Install>true</Install>
-    </BootstrapperPackage>
-  </ItemGroup>
-  <ItemGroup>
-    <WCFMetadata Include="Connected Services\" />
-  </ItemGroup>
-  <ItemGroup>
-    <PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.0" />
-    <PackageReference Include="MongoDB.Driver" Version="2.18.0" />
-    <PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
-      <PrivateAssets>all</PrivateAssets>
-    </PackageReference>
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Remove="Configuration\Server.Net45.cs" />
-    <Compile Remove="Configuration\SocketServiceConfig.Net45.cs" />
-    <Compile Remove="DefaultBootstrap.Net45.cs" />
-    <Compile Remove="Extensions.Net35.cs" />
-    <Compile Remove="SocketSession.Net45.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\SocketBase\SuperSocket.SocketBase.csproj" />
-  </ItemGroup>
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <OutputType>Library</OutputType>
+    <RootNamespace>SuperSocket.SocketEngine</RootNamespace>
+    <AssemblyName>SuperSocket.SocketEngine</AssemblyName>
+    <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>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+    <DocumentationFile>bin\Debug\SuperSocket.SocketEngine.XML</DocumentationFile>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+    <DocumentationFile>bin\Release\SuperSocket.SocketEngine.XML</DocumentationFile>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>.\supersocket.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Update="System.Core">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.0">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+      <Visible>False</Visible>
+      <ProductName>Windows Installer 3.1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <WCFMetadata Include="Connected Services\" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.0" />
+    <PackageReference Include="MongoDB.Driver" Version="2.18.0" />
+    <PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
+      <PrivateAssets>all</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Remove="Configuration\Server.Net45.cs" />
+    <Compile Remove="Configuration\SocketServiceConfig.Net45.cs" />
+    <Compile Remove="DefaultBootstrap.Net45.cs" />
+    <Compile Remove="Extensions.Net35.cs" />
+    <Compile Remove="SocketSession.Net45.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\SocketBase\SuperSocket.SocketBase.csproj" />
+  </ItemGroup>
 </Project>

+ 48 - 48
SuperWebSocket/SuperWebSocket.csproj

@@ -1,49 +1,49 @@
-<Project Sdk="Microsoft.NET.Sdk">
-  <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
-    <OutputType>Library</OutputType>
-    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-    <DocumentationFile>bin\Debug\SuperWebSocket.XML</DocumentationFile>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-    <DocumentationFile>bin\Release\SuperWebSocket.XML</DocumentationFile>
-  </PropertyGroup>
-  <PropertyGroup>
-    <SignAssembly>true</SignAssembly>
-  </PropertyGroup>
-  <PropertyGroup>
-    <AssemblyOriginatorKeyFile>superwebsocket.snk</AssemblyOriginatorKeyFile>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="log4net">
-      <HintPath>DLL\log4net.dll</HintPath>
-    </Reference>
-  </ItemGroup>
-  <ItemGroup>
-    <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
-  </ItemGroup>
-  <ItemGroup>
-    <Folder Include="DLL\" />
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\SocketBase\SuperSocket.SocketBase.csproj" />
-    <ProjectReference Include="..\SocketCommon\SuperSocket.Common.csproj" />
-    <ProjectReference Include="..\SocketEngine\SuperSocket.SocketEngine.csproj" />
-  </ItemGroup>
-  <ItemGroup>
-    <PackageReference Include="DnsClient" Version="1.7.0" />
-    <PackageReference Include="MongoDB.Bson" Version="2.18.0" />
-    <PackageReference Include="MongoDB.Driver" Version="2.18.0" />
-    <PackageReference Include="MongoDB.Driver.Core" Version="2.18.0" />
-    <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
-    <PackageReference Include="NLog" Version="5.1.1" />
-    <PackageReference Include="System.Buffers" Version="4.5.1" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Remove="GlobalAssemblyInfo.cs" />
-  </ItemGroup>
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <OutputType>Library</OutputType>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <DocumentationFile>bin\Debug\SuperWebSocket.XML</DocumentationFile>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <DocumentationFile>bin\Release\SuperWebSocket.XML</DocumentationFile>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>superwebsocket.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="log4net">
+      <HintPath>DLL\log4net.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="DLL\" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\SocketBase\SuperSocket.SocketBase.csproj" />
+    <ProjectReference Include="..\SocketCommon\SuperSocket.Common.csproj" />
+    <ProjectReference Include="..\SocketEngine\SuperSocket.SocketEngine.csproj" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="DnsClient" Version="1.7.0" />
+    <PackageReference Include="MongoDB.Bson" Version="2.18.0" />
+    <PackageReference Include="MongoDB.Driver" Version="2.18.0" />
+    <PackageReference Include="MongoDB.Driver.Core" Version="2.18.0" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
+    <PackageReference Include="NLog" Version="5.1.1" />
+    <PackageReference Include="System.Buffers" Version="4.5.1" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Remove="GlobalAssemblyInfo.cs" />
+  </ItemGroup>
 </Project>

+ 4 - 4
build.bat

@@ -1,5 +1,5 @@
-for /f %%i in ('git rev-parse --short HEAD') do set ssha=%%i
-docker build ./ -t 172.1.2.214:5000/server:test --label "git-commit=%ssha%"
-::docker push 172.1.2.214:5000/server:test
-docker tag 172.1.2.214:5000/server:test evdevcontainerregistry.azurecr.io/server:%ssha%
+for /f %%i in ('git rev-parse --short HEAD') do set ssha=%%i
+docker build ./ -t 172.1.2.214:5000/server:test --label "git-commit=%ssha%"
+::docker push 172.1.2.214:5000/server:test
+docker tag 172.1.2.214:5000/server:test evdevcontainerregistry.azurecr.io/server:%ssha%
 docker push evdevcontainerregistry.azurecr.io/server:%ssha%