151 Commity 5ae9f9b4eb ... c758e3587b

Autor SHA1 Správa Dátum
  shayne_lo c758e3587b fix query 4 mesiacov pred
  shayne_lo ba7b7ea95d update web db [StationMachine] Protocol version when EVSE connected 4 mesiacov pred
  shayne_lo 8f6f4ea6b5 update connection log for ipv6 record 4 mesiacov pred
  shayne_lo dbad17b181 fix mis spelling 5 mesiacov pred
  shayne_lo 50c0a0efbe add heat beat interval check to HealthCheckTriggerJob 5 mesiacov pred
  shayne_lo 84ea5eafac code clean 5 mesiacov pred
  shayne_lo 5b5e437336 update limited boot cnt 5 mesiacov pred
  shayne_lo 0ca6e12ce7 test pass 1 5 mesiacov pred
  shayne_lo 5c19ce3457 add get diagnostic restrict 5 mesiacov pred
  shayne_lo e667c00de9 add dotnet react build 5 mesiacov pred
  shayne_lo 94931c5cee remove set machine connection type in initEVSE 5 mesiacov pred
  shayne_lo 9c064b328a fix billing with instant stop report missing info 5 mesiacov pred
  shayne_lo fd1832eaf8 add sn check for docker env 6 mesiacov pred
  shayne_lo 0868e9ac81 handle vendorId null 6 mesiacov pred
  shayne_lo 43ee5458ea try fix ErrorInfo not suplied 6 mesiacov pred
  shayne_lo 866a2c2677 set completed_session not print nu llparameter 6 mesiacov pred
  shayne_lo 116c893777 add handle if CallPartnerApiOnSchedule = 1 and InstantStopTxReport = 1 6 mesiacov pred
  shayne_lo 97e50d1802 add isbilling stop transaction report time 6 mesiacov pred
  shayne_lo 93ef5bffc6 add InstantStopTxReport record 6 mesiacov pred
  shayne_lo 59dc023cc7 Isolate instant stop report from billing 6 mesiacov pred
  shayne_lo 0ddd9b6162 utlize BillingDone to mark transaction can be reported 7 mesiacov pred
  shayne_lo 573b59160f isolate period energy request 7 mesiacov pred
  shayne_lo bf3d5dcb3b fix IsBilling not turn off 7 mesiacov pred
  Robert a7fa809c90 update git ftech for tag update 7 mesiacov pred
  Robert f41983c07b remove try get message from db if not received 7 mesiacov pred
  Robert 0e5bf7ba79 1.fix Dictionary tryInsert error 7 mesiacov pred
  Jessica Tseng ece7eee5af 1. Modify customId when retrieving the configuration with the key "CentralChargeBoxId"/"ChargeBoxId" 7 mesiacov pred
  Jessica Tseng 929450d6a7 ****Update two DLLs**** 7 mesiacov pred
  Jessica Tseng 8e981d224f ****Update two DLLs**** 7 mesiacov pred
  Robert 1232011d87 remove ws and auth from normal log 8 mesiacov pred
  Robert 97be2ccbdd fix new line 9 mesiacov pred
  Robert 313ac107db fix release script 9 mesiacov pred
  Robert c254d0a45e 1. add new build scripts 9 mesiacov pred
  Robert 2914d55c2d dont respond transaction related request before is checkedin 9 mesiacov pred
  Robert 598eed2a4e fix prod podman build bat 9 mesiacov pred
  Robert d51a72dc13 fix GetEvseStation 9 mesiacov pred
  Robert c02d2c4c3e add podman build bat 9 mesiacov pred
  Robert fbe5001c23 increase StationMachine param ChargeBoxId length limit to 50 9 mesiacov pred
  Robert f7034af926 fix vendor id empty handling 9 mesiacov pred
  Robert d292001923 optimize update MachineError FinishedTime 9 mesiacov pred
  Robert 37ba2df96f fix vendorId empty not handle correctly 9 mesiacov pred
  Robert 92b370a3e0 fix fill error status finish time 9 mesiacov pred
  Robert 43beee6b53 1. handle vendor id report empty 9 mesiacov pred
  Robert 4d118385bb 1. add filter for adding machine error to db 9 mesiacov pred
  Robert d20e3c7ac7 1. add back GetConfiguration value null handling 10 mesiacov pred
  Robert 8a22e6eeab get config for every connection 10 mesiacov pred
  Robert b12b046230 make sure rawScheme is in lower case 10 mesiacov pred
  Robert f345cb7c57 fix issue - station config not wait 10 mesiacov pred
  Robert 0db06ced49 update version 10 mesiacov pred
  Robert 761e4aece2 parallal update evse station config 10 mesiacov pred
  Robert a6825b38ab change OccurenceConstraintViolation description 10 mesiacov pred
  Robert 8d5c00f752 skip 3mins problem 10 mesiacov pred
  Robert cae7085dd3 simplfy header record 10 mesiacov pred
  Robert 6f811110be dont send if transactionId null 10 mesiacov pred
  Robert 5667d8047b add get scheme from X-Forwarded-Proto 10 mesiacov pred
  Robert b6599eab0e increase ValidateHandshake log level 10 mesiacov pred
  Robert 9233a36fe3 update Docker_v1.1.25 10 mesiacov pred
  Robert 72a66f16fb set default db to test db 10 mesiacov pred
  Robert 392f8b344c update version Docker_v1.1.24 10 mesiacov pred
  Robert 778c5d4706 fix url default setting 10 mesiacov pred
  Robert 696e8a6bb1 Merge branch 'Docker' into Docker_VID 10 mesiacov pred
  Robert 3435758060 remove hard coded port 10 mesiacov pred
  Jessica Tseng 26917898e8 1.Update MachineError schema from Domain Dll commitId:e90c45d5 10 mesiacov pred
  Robert a3b5167b70 fix new config not saved 10 mesiacov pred
  Robert 0f15991956 test pass by four settings 10 mesiacov pred
  Robert 7e01976cce re enable StationConfigPollingJob schedule 10 mesiacov pred
  Robert 747742b77a code clean 10 mesiacov pred
  Robert 58663ef12a stress test pass 10 mesiacov pred
  Robert 8ca2932cd2 temp commit 11 mesiacov pred
  Robert 2aaec4c169 temp commit 11 mesiacov pred
  Robert 6f7106eeee temp 11 mesiacov pred
  Robert 78db9fbcd2 temp commit 11 mesiacov pred
  Robert d3bcb6d23e try optimize perfornance 11 mesiacov pred
  Robert b331f6d6f4 update auth param version 11 mesiacov pred
  Robert e9c17f227a temp commit 11 mesiacov pred
  Robert 03bcddfd69 temp commit 11 mesiacov pred
  Robert 730993d4e7 temp commit 11 mesiacov pred
  Robert af26e83dd2 remove public Station access, prevent unexpect change by other process 11 mesiacov pred
  Robert aefbfbbb24 Merge branch 'Docker' into Docker_msg 11 mesiacov pred
  Robert 4a5b568e95 test server pass 11 mesiacov pred
  Robert f5c320f2ab set PnC connectorId = -1 if preparing connector not found 1 rok pred
  Robert 198d90a0e3 fix KeepAliveInterval cause crash 1 rok pred
  Robert c999503090 change ping pon inteval to max 1 rok pred
  Robert d88dbeec54 master Commit 92ea5c42: 1 rok pred
  Robert eff9bdd0dc fix MachineConfigurations NULL Data 1 rok pred
  Robert f9b6050e71 fix logic might not send new station config 1 rok pred
  Robert 31303c1bef fix dependency logic 1 rok pred
  Robert 8cdf6557f8 Merge branch 'Docker' into Docker_msg 1 rok pred
  Robert d2b7e835c8 update CDFA url 1 rok pred
  Robert d38a9ad7e4 update version 1 rok pred
  Robert 103e527656 get original ip from X-Forwarded-For 1 rok pred
  Robert 803601bd32 test pass 1 rok pred
  Robert ac4ffe1d98 update version 1 rok pred
  Robert 6a1d861ec3 fix auth fail when 0 1 rok pred
  Robert 9caaab534a stash 1 rok pred
  Robert 6786ac566d stash 1 rok pred
  Robert 0bcc2a8213 Merge branch 'Docker' into Docker_msg 1 rok pred
  Robert fc42b9a563 add connection log HourIndex 1 rok pred
  Robert c4a0f6a277 update version 1 rok pred
  Robert 6b6d27933a Add ReConfirmMessage catch exception 1 rok pred
  Robert c6112528f1 test pass 1 rok pred
  Robert d1ed77a08d clean code 1 rok pred
  Robert a2fc78db27 private test passed 1 rok pred
  Robert 3bb912dde9 add log fr AcceptWebSocketAsync 1 rok pred
  Robert 4f485ddeac fix direct connect scheme check error 1 rok pred
  Robert fd08be54a9 update to version Docker_v1.1.9 1 rok pred
  Robert bebc2ac74f update version to Docker_v1.1.8 1 rok pred
  Jessica Tseng d140590045 Follow master branch with commit 11d74e0a 1 rok pred
  Jessica Tseng 4fef2e8db0 Add Authorize log 1 rok pred
  Robert b1d71d1c59 client diconnect log cleanup 1 rok pred
  Robert 0e9a427747 sync EBUS change 1 rok pred
  Robert 29d881d8ea Merge branch 'Docker' of https://dev.azure.com/ZerovaSD/OCPP%20Backend/_git/EVCB_OCPP.OCPPServer.16J into Docker 1 rok pred
  Jessica Tseng 4e72944e5a Fix the issue where the backend responds with "ocpp2.0.1" when the charger claims to support two protocols. 1 rok pred
  Jessica Tseng 1fe9d723ff Ignore 98f0e1a391e466abbb6290c6eb0368608d489af7 1 rok pred
  Jessica Tseng 37d1b8bc6e 1. Follow Domain.dll with commit =dd3d295a12faa338d7c870470eaa85ad5c39ad25 1 rok pred
  Robert db69dba8ef fix reference 1 rok pred
  Robert 98f0e1a391 update version 1 rok pred
  Robert f913e51d61 fix main db error 1 rok pred
  Robert 82fb402265 update doman dll reference 1 rok pred
  Jessica Tseng 5324ed6c36 add main commit 1 rok pred
  Jessica Tseng 6951a8af06 Remove Superwebsocket dependency 1 rok pred
  Robert abcfe62abd update vesion 1 rok pred
  Robert 5d154fa43a 修正Server呼叫客戶充電完成紀錄API可能crash問題 1 rok pred
  Robert 719d09c337 update version 1 rok pred
  Robert f119248b15 fix DiagnosticsStatusNotification 1 rok pred
  Robert 10c5866dfd update version 1 rok pred
  Robert e953c14804 fix not EndOfMessage not copy previous msg 1 rok pred
  Robert 4ab1c97082 chage base to asp.net original 1 rok pred
  Robert 0cc970fd41 update version 1 rok pred
  Robert 75138c36af fix dappfix error 1 rok pred
  Robert 124190df9c faster HeartBeatCheckTrigger 1 rok pred
  Robert 92dd361714 fix dapper select in 1 rok pred
  Robert ebc21206ef change console log to logger 1 rok pred
  Robert fb0f135236 simplify code 1 rok pred
  Robert ebe8915aa3 fix Iccid 1 rok pred
  Robert 4fd39d53f0 1. fix IsCheckIn 1 rok pred
  Robert 643b2503fe fix worn label 1 rok pred
  Robert 49b980ab74 optimize GroupHandler 1 rok pred
  Robert 77df1c4659 add main commit 798df193053386b10fdc3adc038b4e0953fa7875 1 rok pred
  Robert ac296eebed add main commit e37c009b84cff595a796b7799ad87ab09ffd93c0 1 rok pred
  Robert b8db5b723c add main commit 64e71ca0b667470aa06cc00293031cef7800ffd1 1 rok pred
  Robert 4cb624f982 1. fix reconnect EVSE ghosted 1 rok pred
  Robert 76090ad0fe add main commit 44477 1 rok pred
  Robert f5a8cdcfbf add main db commit 4b3798b9 1 rok pred
  Robert 131750b37c add main commit abfcf96e 1 rok pred
  Robert 51a78b0dff add main commit 327fc 1 rok pred
  Robert 1a3c1784ac add main commit 81611 1 rok pred
  Robert a598ae262a add main commit bba355 1 rok pred
  Robert 90aaf88faa change ExecuteFirmwareManagementRequest async 1 rok pred
  Robert 94119e656b add main db commit 9bfa63004 1 rok pred
  Robert df8046e0e3 add back mian db commit 1 rok pred
98 zmenil súbory, kde vykonal 13289 pridanie a 10516 odobranie
  1. 63 63
      .gitattributes
  2. 66 0
      DEV_Build.ps1
  3. 2 0
      Dev_Build.bat
  4. 65 0
      Dev_Build_React.ps1
  5. 6 0
      Dev_Build_podman.bat
  6. 48 43
      Dockerfile
  7. 30 0
      Dockerfile_React
  8. 0 35
      Dockerfile_dev
  9. 31 55
      EVCB_OCPP.Server.sln
  10. BIN
      EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Domain.dll
  11. BIN
      EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll
  12. 0 13
      EVCB_OCPP.WSServer/Dockerfile
  13. 25 25
      EVCB_OCPP.WSServer/Dto/ErrorDetails.cs
  14. 14 0
      EVCB_OCPP.WSServer/Dto/StationMachine.cs
  15. 15 0
      EVCB_OCPP.WSServer/Dto/StationMachineConfig.cs
  16. 18 17
      EVCB_OCPP.WSServer/Dto/TransactionEnergy.cs
  17. 15 0
      EVCB_OCPP.WSServer/Dto/TransactionSoCDto.cs
  18. 76 67
      EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
  19. 1 1
      EVCB_OCPP.WSServer/GlobalConfig.cs
  20. 71 176
      EVCB_OCPP.WSServer/Helper/AddPortalDbContext.cs
  21. 23 4
      EVCB_OCPP.WSServer/Helper/DbExtention.cs
  22. 147 0
      EVCB_OCPP.WSServer/Helper/GroupHandler.cs
  23. 152 0
      EVCB_OCPP.WSServer/Helper/GroupHandlerIO.cs
  24. 0 119
      EVCB_OCPP.WSServer/Helper/GroupSingleHandler.cs
  25. 246 245
      EVCB_OCPP.WSServer/Helper/MeterValueGroupSingleHandler.cs
  26. 161 139
      EVCB_OCPP.WSServer/HostedProtalServer.cs
  27. 67 68
      EVCB_OCPP.WSServer/Jobs/DenyModelCheckJob.cs
  28. 80 42
      EVCB_OCPP.WSServer/Jobs/HealthCheckTriggerJob.cs
  29. 71 70
      EVCB_OCPP.WSServer/Jobs/HeartBeatCheckJob.cs
  30. 238 186
      EVCB_OCPP.WSServer/Jobs/ServerMessageJob.cs
  31. 97 190
      EVCB_OCPP.WSServer/Jobs/ServerSetFeeJob.cs
  32. 138 138
      EVCB_OCPP.WSServer/Jobs/ServerUpdateJob.cs
  33. 85 82
      EVCB_OCPP.WSServer/Jobs/SmartChargingJob.cs
  34. 27 0
      EVCB_OCPP.WSServer/Jobs/StationConfigPollingJob.cs
  35. 158 156
      EVCB_OCPP.WSServer/Message/BasicMessageHandler.cs
  36. 1813 1682
      EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs
  37. 251 223
      EVCB_OCPP.WSServer/Message/FirmwareManagementProfileHandler.cs
  38. 108 107
      EVCB_OCPP.WSServer/Message/LocalAuthListManagementProfileHandler.cs
  39. 349 348
      EVCB_OCPP.WSServer/Message/OCPP16MessageHandler.cs
  40. 298 298
      EVCB_OCPP.WSServer/Message/OCPP20MessageHandler.cs
  41. 90 88
      EVCB_OCPP.WSServer/Message/RemoteTriggerHandler.cs
  42. 129 127
      EVCB_OCPP.WSServer/Message/ReservationProfileHandler.cs
  43. 75 74
      EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs
  44. 190 188
      EVCB_OCPP.WSServer/Message/SmartChargingProfileHandler.cs
  45. 96 87
      EVCB_OCPP.WSServer/Program.cs
  46. 13 0
      EVCB_OCPP.WSServer/Properties/PublishProfiles/FolderProfile1.pubxml
  47. 2 1
      EVCB_OCPP.WSServer/Properties/launchSettings.json
  48. 1301 1478
      EVCB_OCPP.WSServer/ProtalServer.cs
  49. 3 2
      EVCB_OCPP.WSServer/Service/BlockingTreePrintService.cs
  50. 99 94
      EVCB_OCPP.WSServer/Service/BusinessService/BusinessServiceFactory.cs
  51. 9 0
      EVCB_OCPP.WSServer/Service/BusinessService/IBusinessServiceFactory.cs
  52. 99 93
      EVCB_OCPP.WSServer/Service/BusinessService/LocalBusinessService.cs
  53. 16 0
      EVCB_OCPP.WSServer/Service/BusinessService/Model.cs
  54. 386 281
      EVCB_OCPP.WSServer/Service/BusinessService/OuterBusinessService.cs
  55. 305 0
      EVCB_OCPP.WSServer/Service/ConfirmWaitingMessageSerevice.cs
  56. 0 369
      EVCB_OCPP.WSServer/Service/ConnectionLogdbService.cs
  57. 265 0
      EVCB_OCPP.WSServer/Service/DbService/ConnectionLogdbService.cs
  58. 178 0
      EVCB_OCPP.WSServer/Service/DbService/ConnectorStatusDbService.cs
  59. 1656 0
      EVCB_OCPP.WSServer/Service/DbService/MainDbService.cs
  60. 418 376
      EVCB_OCPP.WSServer/Service/DbService/MeterValueDbService.cs
  61. 301 0
      EVCB_OCPP.WSServer/Service/DbService/WebDbService.cs
  62. 125 0
      EVCB_OCPP.WSServer/Service/EnvCheckService.cs
  63. 31 31
      EVCB_OCPP.WSServer/Service/GoogleGetTimePrintService.cs
  64. 68 0
      EVCB_OCPP.WSServer/Service/HeaderRecordService.cs
  65. 0 9
      EVCB_OCPP.WSServer/Service/IBusinessServiceFactory.cs
  66. 372 367
      EVCB_OCPP.WSServer/Service/LoadingBalanceService.cs
  67. 0 910
      EVCB_OCPP.WSServer/Service/MainDbService.cs
  68. 166 0
      EVCB_OCPP.WSServer/Service/MapApiServce.cs
  69. 166 164
      EVCB_OCPP.WSServer/Service/MeterValueInsertHandler.cs
  70. 84 0
      EVCB_OCPP.WSServer/Service/ServerMessageService.cs
  71. 254 0
      EVCB_OCPP.WSServer/Service/StationConfigService.cs
  72. 0 40
      EVCB_OCPP.WSServer/Service/WebDbService.cs
  73. 271 0
      EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs
  74. 168 0
      EVCB_OCPP.WSServer/Service/WsService/WebsocketService.cs
  75. 159 0
      EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs
  76. 179 0
      EVCB_OCPP.WSServer/Service/WsService/WsSession.cs
  77. 0 19
      EVCB_OCPP.WSServer/SuperSocket.Command/ProcessCallCmd.cs
  78. 0 19
      EVCB_OCPP.WSServer/SuperSocket.Command/ProcessCallErrorCmd.cs
  79. 0 19
      EVCB_OCPP.WSServer/SuperSocket.Command/ProcessCallResultCmd.cs
  80. 0 128
      EVCB_OCPP.WSServer/SuperSocket.Protocol/ClientData.cs
  81. 0 8
      EVCB_OCPP.WSServer/SuperSocket.Protocol/OCPPLog.cs
  82. 0 5
      EVCB_OCPP.WSServer/SuperSocket.Protocol/OCPPLogFactory.cs
  83. 0 35
      EVCB_OCPP.WSServer/SuperSocket.Protocol/OCPPSubCommandConverter.cs
  84. 0 322
      EVCB_OCPP.WSServer/SuperSocket.Protocol/OCPPSubProtocol.cs
  85. 0 251
      EVCB_OCPP.WSServer/SuperSocket/OCPPWSServer.cs
  86. 0 39
      EVCB_OCPP.WSServer/SuperSocket/OCPPWSServerFactory.cs
  87. 151 161
      EVCB_OCPP.WSServer/appsettings.json
  88. 39 0
      LocalTest_Build.ps1
  89. 110 0
      Prerelease_Build.ps1
  90. 7 0
      Prod_Build.bat
  91. 10 0
      Prod_Build_podman.bat
  92. 101 0
      Release_Build.ps1
  93. 86 86
      SocketEngine/SuperSocket.SocketEngine.csproj
  94. 48 48
      SuperWebSocket/SuperWebSocket.csproj
  95. 5 5
      build.bat
  96. 1 0
      entrypoint.sh
  97. 1 0
      schedule
  98. 1 0
      version.txt

+ 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 - 0
DEV_Build.ps1

@@ -0,0 +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."
+}
+
+
+
+

+ 2 - 0
Dev_Build.bat

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

+ 65 - 0
Dev_Build_React.ps1

@@ -0,0 +1,65 @@
+# 設定 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
+
+rm -r ./app
+dotnet publish .\EVCB_OCPP.WSServer\EVCB_OCPP.WSServer.csproj -c Release -o ./app/publish /p:UseAppHost=false
+& 'C:\Program Files (x86)\Eziriz\.NET Reactor\dotNET_Reactor.Console.exe' -file ./app/publish/EVCB_OCPP.WSServer.dll -targetfile ./app/publish/EVCB_OCPP.WSServer.dll
+podman build ./ -f Dockerfile_React -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."
+}

+ 6 - 0
Dev_Build_podman.bat

@@ -0,0 +1,6 @@
+for /f %%i in ('git rev-parse --short HEAD') do set ssha=%%i
+podman build ./ -t evdevcontainerregistry.azurecr.io/server:test --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:test

+ 48 - 43
Dockerfile

@@ -1,44 +1,49 @@
-#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
-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 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 .
-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/"]
+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"]

+ 30 - 0
Dockerfile_React

@@ -0,0 +1,30 @@
+#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 
+
+FROM base AS final
+WORKDIR /app
+COPY ./app/publish/ /app/
+COPY entrypoint.sh /app/entrypoint.sh
+COPY ssha /app/ssha
+RUN chmod +x /app/entrypoint.sh
+CMD ["/app/entrypoint.sh"]

+ 0 - 35
Dockerfile_dev

@@ -1,35 +0,0 @@
-#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
-
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS final
-EXPOSE 80
-EXPOSE 443
-EXPOSE 54088
-EXPOSE 2222 
-
-#RUN sed -i 's/TLSv1.2/TLSv1/g' /etc/ssl/openssl.cnf
-#RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
-
-RUN apt-get update \
-    && apt-get install -y --no-install-recommends dialog \
-    && apt-get install -y --no-install-recommends openssh-server \
-	&& mkdir -p /run/sshd \
-    && echo "root:Docker!" | chpasswd 
-	
-COPY sshd_config /etc/ssh/sshd_config
-
-# Install dotnet debug tools
-RUN dotnet tool install --tool-path /tools dotnet-trace \
- && dotnet tool install --tool-path /tools dotnet-counters \
- && dotnet tool install --tool-path /tools dotnet-dump \
- && dotnet tool install --tool-path /tools dotnet-gcdump
- 
-#RUN apt update
-#RUN apt install -y linux-perf
-#RUN echo 0 > /proc/sys/kernel/kptr_restrict
-WORKDIR /src
-COPY . .
-#RUN export DOTNET_PerfMapEnabled=1
-RUN dotnet restore "EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj"
-RUN dotnet build ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
-RUN chmod +x /src/entrypoint.sh
-CMD ["/src/entrypoint.sh"]

+ 31 - 55
EVCB_OCPP.Server.sln

@@ -1,55 +1,31 @@
-
-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
+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
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {79198208-2DBA-46E9-B361-18617E64EAAE}
+	EndGlobalSection
+EndGlobal

BIN
EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Domain.dll


BIN
EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll


+ 0 - 13
EVCB_OCPP.WSServer/Dockerfile

@@ -1,13 +0,0 @@
-#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
-
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS final
-EXPOSE 80
-EXPOSE 443
-EXPOSE 54088
-RUN sed -i 's/TLSv1.2/TLSv1/g' /etc/ssl/openssl.cnf
-RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
-WORKDIR /src
-COPY . .
-RUN export DOTNET_PerfMapEnabled=1
-RUN dotnet build ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
-CMD ["dotnet", " run --project ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj"]

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

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

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Dto
+{
+    internal class StationMachine
+    {
+        public int StationId { get; set; }
+        public string ChargeBoxId { get; set; }
+    }
+}

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

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

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

@@ -1,17 +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 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; }
+    }
+}

+ 15 - 0
EVCB_OCPP.WSServer/Dto/TransactionSoCDto.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Dto
+{
+    internal class TransactionSoCDto
+    {
+        public int TransactionId { set; get; }
+        public int MinSoC { set; get; }
+        public int MaxSoC { set; get; }
+    }
+}

+ 76 - 67
EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj

@@ -1,68 +1,77 @@
-<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="Dapper" Version="2.0.143" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10" />
-    <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.Data.DataSetExtensions" Version="4.5.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>
-  <ItemGroup>
-    <ProjectReference Include="..\SuperWebSocket\SuperWebSocket.csproj" />
-  </ItemGroup>
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UserSecretsId>88f57ec2-60b9-4291-bba3-3c0d312fe6dc</UserSecretsId>
+    <DockerfileContext>$(SolutionDir.TrimEnd('\'))</DockerfileContext>
+    <DockerfileFile>$(SolutionDir)\Dockerfile</DockerfileFile>
+  </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.12.0" />
+    <PackageReference Include="Dapper" Version="2.0.143" />
+    <PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
+    <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.20.0" />
+    <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="112.1.0" />
+    <PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
+    <PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
+    <PackageReference Include="System.Data.SqlClient" Version="4.8.6" />
+    <PackageReference Include="System.Formats.Asn1" Version="8.0.1" />
+    <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.Text.Json" Version="8.0.5" />
+    <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
+    <PackageReference Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />
+    <PackageReference Include="Yarp.ReverseProxy" Version="2.0.1" />
+  </ItemGroup>
 </Project>

+ 1 - 1
EVCB_OCPP.WSServer/GlobalConfig.cs

@@ -168,7 +168,7 @@ namespace EVCB_OCPP.WSServer
         public static DateTime DefaultNullTime = new DateTime(1991, 1, 1);
 
 
-
+        public const string BootData_EVSEConfig_Key = "BootData_EVSEConfig_Key";
 
 
     }

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

@@ -1,177 +1,72 @@
-using EVCB_OCPP.Domain;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using MongoDB.Driver.Core.Configuration;
-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 AddMainDbContext(this IServiceCollection services, IConfiguration configuration)
-    { 
-        const string DbUserIdKey = "MainDbUserIdKey";
-        const string DbPassKey = "MainDbPass";
-        const string DbConnectionStringKey = "MainDBContext";
-
-        var conneciotnString = GetConnectionString(configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
-
-        services.AddSingleton(
-            (serviceProvider) =>
-            new SqlConnectionFactory<MainDBContext>(serviceProvider.GetRequiredService<ILogger<SqlConnectionFactory>>())
-            {
-                ConnectionString = conneciotnString
-            });
-        AddPortalDbContextInternal<MainDBContext>(services, configuration, conneciotnString, logToConsole: false);
-        return services;
-    }
-
-    public static IServiceCollection AddMeterValueDbContext(this IServiceCollection services, IConfiguration configuration)
-    {
-        const string DbUserIdKey = "MeterValueDbUserId";
-        const string DbPassKey = "MeterValueDbPass";
-        const string DbConnectionStringKey = "MeterValueDBContext";
-
-        var conneciotnString = GetConnectionString(configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
-        services.AddSingleton(
-            (serviceProvider) =>
-            new SqlConnectionFactory<MeterValueDBContext>(serviceProvider.GetRequiredService<ILogger<SqlConnectionFactory>>())
-            {
-                ConnectionString = conneciotnString
-            });
-        AddPortalDbContextInternal<MeterValueDBContext>(services, configuration, conneciotnString, logToConsole: false);
-        return services;
-    }
-
-    public static IServiceCollection AddConnectionLogDbContext(this IServiceCollection services, IConfiguration configuration)
-    {
-        const string DbUserIdKey = "ConnectionLogDbUserId";
-        const string DbPassKey = "ConnectionLogDbPass";
-        const string DbConnectionStringKey = "ConnectionLogDBContext";
-
-        var conneciotnString = GetConnectionString(configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
-        services.AddSingleton(
-            (serviceProvider) =>
-            new SqlConnectionFactory<ConnectionLogDBContext>(serviceProvider.GetRequiredService<ILogger<SqlConnectionFactory>>())
-            {
-                ConnectionString = conneciotnString
-            });
-        AddPortalDbContextInternal<ConnectionLogDBContext>(services, configuration, conneciotnString);
-        return services;
-    }
-
-    public static IServiceCollection AddWebDBConetext(this IServiceCollection services, IConfiguration configuration)
-    {
-        const string DbUserIdKey = "WebDbUserId";
-        const string DbPassKey = "WebDbPass";
-        const string DbConnectionStringKey = "WebDBContext";
-
-        var conneciotnString = GetConnectionString(configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
-        services.AddSingleton(
-            (serviceProvider) =>
-            new SqlConnectionFactory<WebDBConetext>(serviceProvider.GetRequiredService<ILogger<SqlConnectionFactory>>())
-            {
-                ConnectionString = 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 = GetConnectionString(configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
-        services.AddSingleton(
-            (serviceProvider) =>
-            new SqlConnectionFactory<OnlineLogDBContext>(serviceProvider.GetRequiredService<ILogger<SqlConnectionFactory>>())
-            {
-                ConnectionString = 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>());
-        });
-    }
-
-    private static string GetConnectionString(IConfiguration configuration, string UserIdKey, string DbPassKey, string ConnectionStringKey ) 
-    {
-        string mainDbUserId = string.IsNullOrEmpty(configuration[UserIdKey]) ? string.Empty : $"user id={configuration[UserIdKey]};";
-        string mainDbUserPass = string.IsNullOrEmpty(configuration[DbPassKey]) ? string.Empty : $"password={configuration[DbPassKey]};";
-        return $"{configuration.GetConnectionString(ConnectionStringKey)}{mainDbUserId}{mainDbUserPass}";
-    }
-}
-
-public class SqlConnectionFactory<T> where T: DbContext
-{
-    private readonly ILogger<SqlConnectionFactory> logger;
-
-    public string ConnectionString { get; init; }
-    public SqlConnectionFactory(ILogger<SqlConnectionFactory> logger)
-    {
-        this.logger = logger;
-    }
-
-    public SqlConnection Create()
-    {
-        var sqlConnection = new SqlConnection(ConnectionString);
-        sqlConnection.Open();
-        return sqlConnection;
-    }
-
-    public async Task<SqlConnection> CreateAsync()
-    {
-        var timer = Stopwatch.StartNew();
-        long t0, t1;
-
-        var sqlConnection = new SqlConnection(ConnectionString);
-        t0 = timer.ElapsedMilliseconds;
-
-        await sqlConnection.OpenAsync();
-        t1 = timer.ElapsedMilliseconds;
-        timer.Stop();
-
-        if (t1 > 500)
-        {
-            logger.LogWarning($"{typeof(T)} SqlConnection Open slow {t0}/{t1}");
-        }
-
-        return sqlConnection;
-    }
-}
-
-/// <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 { }

+ 23 - 4
EVCB_OCPP.WSServer/Helper/DbExtention.cs

@@ -39,8 +39,8 @@ namespace EVCB_OCPP.WSServer.Helper
 
         public static void AddInsertMeterValueRecordSqlParameters(this List<SqlParameter> sqlParameters,
             string chargeBoxId, byte connectorId, decimal value, DateTime createdOn
-            ,int contextId, int formatId, int measurandId, int phaseId
-            ,int locationId,int unitId,int transactionId)
+            , int contextId, int formatId, int measurandId, int phaseId
+            , int locationId, int unitId, int transactionId)
         {
             List<SqlParameter> parameter = new List<SqlParameter>
             {
@@ -56,7 +56,26 @@ namespace EVCB_OCPP.WSServer.Helper
                 new SqlParameter("UnitId", SqlDbType.Int) { Value = unitId },
                 new SqlParameter("TransactionId", SqlDbType.Int) { Value = transactionId },
             };
-            sqlParameters.AddRange(parameter);
-        }
+            sqlParameters.AddRange(parameter);
+        }
+
+
+        public static void AddInsertConnectorStatusRecordSqlParameters(this List<SqlParameter> sqlParameters,
+           string chargeBoxId, byte connectorId, int status, DateTime createdOn
+           , string errorInfo, string vendorId, string vendorErrorCode, int chargePointErrorCodeId)
+        {
+            List<SqlParameter> parameter = new List<SqlParameter>
+            {
+                new SqlParameter("ChargeBoxId", SqlDbType.NVarChar, 50){ Value = chargeBoxId },
+                new SqlParameter("ConnectorId", SqlDbType.TinyInt) { Value = connectorId },
+                new SqlParameter("Status", SqlDbType.Int){ Value = status },
+                new SqlParameter("CreatedOn", SqlDbType.DateTime) { Value = createdOn },
+                new SqlParameter("ChargePointErrorCodeId", SqlDbType.Int) { Value = chargePointErrorCodeId },
+                new SqlParameter("ErrorInfo",SqlDbType.NVarChar, 50){ Value = errorInfo is null ? DBNull.Value : errorInfo },
+                new SqlParameter("VendorId", SqlDbType.NVarChar, 255){ Value = vendorId is null ? DBNull.Value : vendorId },
+                new SqlParameter("VendorErrorCode", SqlDbType.NVarChar, 100) {Value =vendorErrorCode is null ? DBNull.Value : vendorErrorCode},
+            };
+            sqlParameters.AddRange(parameter);
+        }
     }
 }

+ 147 - 0
EVCB_OCPP.WSServer/Helper/GroupHandler.cs

@@ -0,0 +1,147 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.IdentityModel.Tokens;
+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<T>
+{
+    public GroupHandler(
+        Func<BundleHandlerData<T>, 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<T>, Task> handleFunc;
+    private readonly ILogger logger;
+    private readonly int maxRetry;
+    private readonly ConcurrentQueue<WaitParam<T>> waitList = new();
+
+    private SemaphoreSlim workersLock;// = new SemaphoreSlim(1);
+
+    public async Task HandleAsync(T param)
+    {
+        var waitData = new WaitParam<T>() { 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;
+        }
+    }
+
+    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<T>>();
+
+        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<T>(datas);
+            try
+            {
+                await handleFunc(bundleHandledata);
+            }
+            catch (Exception e)
+            {
+                lastException = e;
+            }
+
+            var completedRequests = requests.Where(x => bundleHandledata.CompletedDatas.Contains(x.Data)).ToList();
+            foreach (var request in completedRequests)
+            {
+                request.Waiter.Release();
+            }
+
+            datas = datas.Except(bundleHandledata.CompletedDatas).ToList();
+
+            if (datas.IsNullOrEmpty())
+            {
+                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<T>
+{
+    public List<T> Datas { get; set; }
+    public List<T> CompletedDatas { get; set; }
+
+    public BundleHandlerData(List<T> datas)
+    {
+        Datas = datas;
+        CompletedDatas = new();
+    }
+
+    public void AddCompletedData(T competedData)
+    {
+        CompletedDatas.Add(competedData);
+    }
+}
+
+internal class WaitParam<T>
+{
+    public T Data { get; init; }
+    public SemaphoreSlim Waiter { get; init; }
+    public Exception Exception { get; set; }
+}

+ 152 - 0
EVCB_OCPP.WSServer/Helper/GroupHandlerIO.cs

@@ -0,0 +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, CancellationToken token = default)
+    {
+        var waitData = new WaitParam<TI,TO>() { Data = param, Waiter = new SemaphoreSlim(0), Exception = null };
+        waitList.Enqueue(waitData);
+        TryStartHandler();
+        await waitData.Waiter.WaitAsync(token);
+        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; }
+}

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

@@ -1,119 +0,0 @@
-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 GroupSingleHandler<T>
-{
-    public GroupSingleHandler(Func<IEnumerable<T>, Task> handleFunc, ILogger logger, int workerCnt = 1)
-    {
-        this.handleFunc = handleFunc;
-        this.logger = logger;
-
-        WorkerCnt = workerCnt;
-        //singleWorkLock = new(_WorkerCnt);
-    }
-
-    private int _WorkerCnt = 1;
-    public int WorkerCnt
-    {
-        get => _WorkerCnt;
-        set
-        {
-            if (IsStarted)
-            {
-                throw new Exception($"{nameof(WorkerCnt)} must not be changed afted {nameof(HandleAsync)} is called");
-            }
-
-            _WorkerCnt = value;
-            singleWorkLock = new(_WorkerCnt);
-        }
-    }
-
-    private readonly Func<IEnumerable<T>, Task> handleFunc;
-    private readonly ILogger logger;
-    private readonly ConcurrentQueue<(T param, SemaphoreSlim waitLock)> waitList = new();
-    private SemaphoreSlim singleWorkLock;// = new SemaphoreSlim(1);
-    private bool IsStarted = false;
-    private Task singleHandleTask;
-
-    public Task HandleAsync(T param)
-    {
-        IsStarted = true;
-
-        SemaphoreSlim reqLock = new(0);
-        waitList.Enqueue((param, reqLock));
-        TryStartHandler();
-        return reqLock.WaitAsync();
-    }
-
-    private void TryStartHandler()
-    {
-        if (!singleWorkLock.Wait(0))
-        {
-            return;
-        }
-
-        if (waitList.Count == 0)
-        {
-            singleWorkLock.Release();
-            return;
-        }
-
-        singleHandleTask = StartHandleTask();
-    }
-
-    private async Task StartHandleTask()
-    {
-        var timer = Stopwatch.StartNew();
-        long t0 = 0, t1 = 0, t2 = 0;
-
-        var handleList = new List<(T param, SemaphoreSlim waitLock)>();
-
-        while (waitList.TryDequeue(out var handle))
-        {
-            handleList.Add(handle);
-        }
-        t0 = timer.ElapsedMilliseconds;
-
-        int cnt = 0;
-        do
-        {
-            cnt++;
-            try
-            {
-                var task = handleFunc(handleList.Select(x => x.param));
-                await task;
-                t1 = timer.ElapsedMilliseconds;
-                break;
-            }
-            catch (Exception e)
-            {
-                logger.LogError(e, "Trying Cnt {0}", cnt);
-                logger.LogError(e.Message);
-            }
-        }
-        while (true);
-
-        foreach (var handled in handleList)
-        {
-            handled.waitLock.Release();
-        }
-        singleWorkLock.Release();
-
-        timer.Stop();
-        t2= timer.ElapsedMilliseconds;
-        if (t2 >1000)
-        {
-            logger.LogWarning("StartHandleTask {0}/{1}/{2}", t0, t1, t2);
-        }
-
-        TryStartHandler();
-    }
-}

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

@@ -1,245 +1,246 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.WSServer.Service;
-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,
-        SqlConnectionFactory<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 SqlConnectionFactory<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}";
+}

+ 161 - 139
EVCB_OCPP.WSServer/HostedProtalServer.cs

@@ -1,139 +1,161 @@
-using EVCB_OCPP.Domain;
-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.SuperSocket;
-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.AddSingleton<MeterValueDbService>();
-            services.AddSingleton<WebDbService>();
-            services.AddSingleton<IMainDbService, MainDbService>();
-            services.AddSingleton<IConnectionLogdbService, ConnectionLogdbService>();
-
-            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<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.BusinessService;
+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.AddPortalServerDatabase(configuration);
+            services.AddBusinessServiceFactory();
+
+          
+            services.AddHeaderRecordService();
+			services.AddSingleton<ConnectorStatusDbService>();
+			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<EnvCheckService>();
+            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)
+                .AddOnlineRecordDBContext(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 - 68
EVCB_OCPP.WSServer/Jobs/DenyModelCheckJob.cs

@@ -1,68 +1,67 @@
-using Dapper;
-using DnsClient.Internal;
-using EVCB_OCPP.WSServer.Service;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using OCPPServer.Protocol;
-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();
-            logger.LogDebug("Current DenyList:[{0}]", string.Join(",", GlobalConfig.DenyModelNames));
-
-            if (string.IsNullOrEmpty(GlobalConfig.DenyModelNames[0]))
-            {
-                return;
-            }
-
-            Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
-            foreach (var denyName in GlobalConfig.DenyModelNames)
-            {
-                var removeClients = _copyClientDic.Where(x => x.Key.StartsWith(denyName)).Select(x => x.Value).ToList();
-                foreach (var session in removeClients)
-                {
-                    //Console.WriteLine(string.Format("Server forced to shut down ChargeBox ({0}: Reason: DenyModelName-{1}", session.ChargeBoxId, denyName));
-                    logger.LogInformation(string.Format("Server forced to shut down ChargeBox ({0}: Reason: DenyModelName-{1}", session.ChargeBoxId, denyName));
-                    protalServer.RemoveClient(session);
-                }
-            }
-        }
-        catch (Exception ex)
-        {
-            logger.LogError("DenyModelCheckTrigger  Ex:{0}", ex.ToString());
-        }
-    }
-}
+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());
+        }
+    }
+}

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

@@ -1,42 +1,80 @@
-using EVCB_OCPP.Domain;
-using Microsoft.Extensions.Logging;
-using OCPPServer.Protocol;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-public class HealthCheckTriggerJob : IJob
-{
-    public HealthCheckTriggerJob(
-        ProtalServer protalServer,
-        ILogger<HealthCheckTriggerJob> logger)
-    {
-        this.protalServer = protalServer;
-        this.logger = logger;
-    }
-
-    private readonly ProtalServer protalServer;
-    private readonly ILogger<HealthCheckTriggerJob> logger;
-
-    public Task Execute(IJobExecutionContext context)
-    {
-        //logger.LogDebug("{0} Started", nameof(HealthCheckTriggerJob));
-
-        Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
-
-        var removeClients = _copyClientDic.Where(x => x.Value.LastActiveTime < DateTime.UtcNow.AddSeconds(-300)).Select(x => x.Value).ToList();
-
-        foreach (var session in removeClients)
-        {
-            logger.LogDebug("Server forced to shut down ChargeBox ({0}: LastActiveTime{1})", session.ChargeBoxId, session.LastActiveTime);
-            protalServer.RemoveClient(session);
-        }
-        return Task.CompletedTask;
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.WSServer.Service.DbService;
+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,
+        IMainDbService mainDbService,
+        ILogger<HealthCheckTriggerJob> logger)
+    {
+        this.protalServer = protalServer;
+        this.mainDbService = mainDbService;
+        this.logger = logger;
+    }
+
+    private readonly ProtalServer protalServer;
+    private readonly IMainDbService mainDbService;
+    private readonly ILogger<HealthCheckTriggerJob> logger;
+
+    public async Task Execute(IJobExecutionContext context)
+    {
+        //logger.LogDebug("{0} Started", nameof(HealthCheckTriggerJob));
+
+        Dictionary<string, WsClientData> _copyClientDic = protalServer.GetClientDic();
+
+        Dictionary<string, int> chargeBoxHeartBeatIngerval = await GetChargeBoxIdleSeconds(_copyClientDic?.Keys?.ToList());
+
+        var removeClients = _copyClientDic
+            .Where(x => x.Value.LastActiveTime < DateTime.UtcNow.AddSeconds(-1 * chargeBoxHeartBeatIngerval[x.Key]))
+            .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 ;
+    }
+
+    private async Task<Dictionary<string, int>> GetChargeBoxIdleSeconds(List<string> chageboxList)
+    {
+        if (chageboxList is null)
+        {
+            return new Dictionary<string, int>();
+        }
+
+        var getHeartbeatIntervalTasks = chageboxList.Select(async x => (x, await mainDbService.GetMachineHeartbeatInterval(x))).ToList();
+        await Task.WhenAll(getHeartbeatIntervalTasks);
+        var chargeBoxHeartbeatIntervalIntervalPair = getHeartbeatIntervalTasks.Select(x => x.Result).ToDictionary(x => x.x, x=> x.Item2 );
+
+        Dictionary<string, int> toReturn = new Dictionary<string, int>();
+        foreach (var chageboxId in chageboxList)
+        {
+            //var heartBeatIntervalString = await mainDbService.GetMachineHeartbeatInterval(chageboxId);
+            var heartBeatIntervalString = chargeBoxHeartbeatIntervalIntervalPair.Keys.Contains(chageboxId) ? chargeBoxHeartbeatIntervalIntervalPair[chageboxId] : "0";
+            int idelSeconds = 300;
+            if (int.TryParse(heartBeatIntervalString, out var parsedInterval)
+                && parsedInterval * 2 > idelSeconds)
+            {
+                idelSeconds = parsedInterval * 2;
+            }
+
+            toReturn.Add(chageboxId, idelSeconds);
+        }
+        return toReturn;
+    }
+}

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

@@ -1,70 +1,71 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-using EVCB_OCPP.WSServer.Service;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-using OCPPServer.Protocol;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-public class HeartBeatCheckJob : IJob
-{
-    public HeartBeatCheckJob(
-        ProtalServer protalServer,
-        IDbContextFactory<MainDBContext> maindbContextFactory,
-        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, ClientData> _copyClientDic = protalServer.ClientDic;
-
-            var cdt = DateTime.UtcNow;
-            var clients = _copyClientDic.Where(x => x.Value.LastActiveTime > cdt.AddSeconds(-120)).Select(x => x.Value).ToList();
-
-            watch.Start();
-
-            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);
-
-            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()));
+        }
+    }
+}

+ 238 - 186
EVCB_OCPP.WSServer/Jobs/ServerMessageJob.cs

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

@@ -1,190 +1,97 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages.Core;
-using EVCB_OCPP.WSServer.Dto;
-using EVCB_OCPP.WSServer.Helper;
-using EVCB_OCPP.WSServer.Message;
-using EVCB_OCPP.WSServer.Service;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using OCPPServer.Protocol;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-public class ServerSetFeeJob : IJob
-{
-    private readonly ProtalServer protalServer;
-    private readonly SqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
-    private readonly IMainDbService mainDbService;
-    private readonly ILogger<ServerSetFeeJob> logger;
-    //private readonly string webConnectionString;
-
-    public ServerSetFeeJob(
-        ProtalServer protalServer,
-        //IConfiguration configuration,
-        SqlConnectionFactory<WebDBConetext> sqlConnectionFactory,
-        IMainDbService mainDbService,
-        ILogger<ServerSetFeeJob> logger)
-    {
-        this.protalServer = protalServer;
-        this.webDbConnectionFactory = sqlConnectionFactory;
-        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, ClientData> _copyClientDic = protalServer.ClientDic;
-        //using var db = maindbContextFactory.CreateDbContextAsync();
-        foreach (var item in _copyClientDic)
-        {
-            try
-            {
-                ClientData session = item.Value;
-                if (!session.IsCheckIn)
-                {
-                    continue;
-                }
-
-                string displayPriceText = await SetDefaultFee(session);
-                if (string.IsNullOrEmpty(displayPriceText) || displayPriceText == session.DisplayPrice)
-                {
-                    continue;
-                }
-
-                protalServer.UpdateClientDisplayPrice(item.Key, displayPriceText);
-
-                await mainDbService.AddServerMessage(
-                        ChargeBoxId: session.ChargeBoxId,
-                        OutAction: Actions.ChangeConfiguration.ToString(),
-                        OutRequest: new ChangeConfigurationRequest()
-                        {
-                            key = "DefaultPrice",
-                            value = displayPriceText
-                        });
-
-                if (session.CustomerId == new Guid("10C7F5BD-C89A-4E2A-8611-B617E0B41A73"))
-                {
-                    await mainDbService.AddServerMessage(
-                            ChargeBoxId: session.ChargeBoxId,
-                            OutAction: Actions.ChangeConfiguration.ToString(),
-                            OutRequest: new ChangeConfigurationRequest()
-                            {
-                                key = "ConnectionTimeOut",
-                                value = "120"
-                            });
-
-                    await mainDbService.AddServerMessage(
-                            ChargeBoxId: session.ChargeBoxId,
-                            OutAction: Actions.ChangeConfiguration.ToString(),
-                            OutRequest:  new ChangeConfigurationRequest()
-                            {
-                                key = "MeterValueSampleInterval",
-                                value = "3"
-                            });
-                }
-            }
-            catch (Exception ex)
-            {
-                logger.LogError("ServerSetFeeTrigger ChargeBoxId:{0}  Ex:{1}", item.Key, ex.ToString());
-            }
-        }
-    }
-
-    async private Task<string> SetDefaultFee(ClientData client)
-    {
-        string displayPriceText = string.Empty;
-        string charingPriceText = string.Empty;
-
-        if (string.IsNullOrEmpty(client.ChargeBoxId)) return displayPriceText;
-
-        try
-        {
-            using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
-            {
-                var parameters = new DynamicParameters();
-                parameters.Add("@MachineId", client.MachineId, DbType.String, ParameterDirection.Input, 36);
-                string displayPricestrSql = "";
-                string strSql = "";
-
-                if (client.IsAC)
-                {
-                    displayPricestrSql = """
-                    SELECT [AC_BillingMethod] as BillingMethod,[AC_FeeName] as FeeName,[AC_Fee] as ChargingFeebyHour ,[AC_ParkingFee] as ParkingFee, [Currency] 
-                    FROM[StationMachine] left join[dbo].[Station]
-                    on[StationMachine].StationId = Station.[Id] 
-                    where StationMachine.MachineId=@MachineId and Station.IsBilling=1;
-                    """;
-
-                    strSql = """
-                    SELECT CAST( [StartTime] as varchar(5)) StartTime,CAST( [EndTime] as varchar(5)) EndTime,[Fee] 
-                    FROM[StationMachine] left join [dbo].[StationFee]
-                    on[StationMachine].StationId = StationFee.StationId  
-                    where StationMachine.MachineId =@MachineId and StationFee.IsAC=1; 
-                    """;
-                }
-                else
-                {
-                    displayPricestrSql = """
-                    SELECT [DC_BillingMethod] as BillingMethod,[DC_FeeName] as FeeName,[DC_Fee] as ChargingFeebyHour ,[DC_ParkingFee] as ParkingFee, [Currency] 
-                    FROM[StationMachine] left join[dbo].[Station] 
-                    on[StationMachine].StationId = Station.[Id]
-                    where StationMachine.MachineId=@MachineId and Station.IsBilling=1; 
-                    """;
-
-                    strSql = """
-                    SELECT CAST( [StartTime] as varchar(5)) StartTime,CAST( [EndTime] as varchar(5)) EndTime,[Fee]
-                    FROM[StationMachine] left join [dbo].[StationFee]
-                    on[StationMachine].StationId = StationFee.StationId
-                    where StationMachine.MachineId =@MachineId and StationFee.IsAC=0;
-                    """;
-
-                }
-                var result = await conn.QueryAsync<StationFee>(displayPricestrSql, parameters);
-                if (result.Count() == 0)
-                {
-                    return string.Empty;
-                }
-                var stationPrice = result.First();
-
-                if (stationPrice.BillingMethod == 1)
-                {
-                    var chargingPriceResult = await conn.QueryAsync<ChargingPrice>(strSql, parameters);
-                    client.ChargingPrices = chargingPriceResult.ToList();
-                    if (string.IsNullOrEmpty(client.ChargingPrices[0].StartTime))
-                    {
-                        client.ChargingPrices = new List<ChargingPrice>();
-                    }
-                }
-
-                displayPriceText = stationPrice.FeeName;
-                client.BillingMethod = stationPrice.BillingMethod;
-                client.Currency = stationPrice.Currency;
-                client.ChargingFeebyHour = stationPrice.ChargingFeebyHour;
-                client.ParkingFee = stationPrice.ParkingFee;
-                client.IsBilling = true;
-            }
-        }
-        catch (Exception ex)
-        {
-            logger.LogError("SetDefaultFee", ex.ToString());
-        }
-
-        return displayPriceText;
-    }
-}
+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 Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-using OCPPServer.Protocol;
-using Quartz;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-public class ServerUpdateJob : IJob
-{
-    private readonly ProtalServer protalServer;
-    private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
-    private readonly ILogger<ServerUpdateJob> logger;
-
-    public ServerUpdateJob(
-        ProtalServer protalServer,
-        IDbContextFactory<MainDBContext> maindbContextFactory,
-        ILogger<ServerUpdateJob> logger)
-    {
-        this.protalServer = protalServer;
-        this.maindbContextFactory = maindbContextFactory;
-        this.logger = logger;
-    }
-
-    public async Task Execute(IJobExecutionContext context)
-    {
-        //logger.LogDebug("{0} Started", nameof(ServerUpdateJob));
-        BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-        Dictionary<string, ClientData> _copyClientDic = protalServer.ClientDic;
-        var checkUpdateDt = DateTime.UtcNow;
-        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.FW_VersionReport && x.Online == true)
-            //    .Select(x => new { x.Id, x.ChargeBoxId, x.FW_AssignedMachineVersionId }).ToList();
-
-            needUpdateChargers = await db.Machine.Where(x => x.FW_AssignedVersion.HasValue == true &&
-              x.FW_AssignedVersion != x.FW_VersionReport && x.Online == true)
-              .Select(x => x.ChargeBoxId).AsNoTracking().ToListAsync();
-        }
-
-        foreach (var chargeBoxId in needUpdateChargers)
-        {
-            try
-            {
-                ClientData 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.FW_VersionReport && x.ChargeBoxId == session.ChargeBoxId)
-                        //        .Select(x => new { x.Id, x.FW_AssignedMachineVersionId }).FirstOrDefault();
-
-                        //    if (machine != null)
-                        //    {
-                        //        var mv = db.MachineVersion.Include(c => c.PublishVersion)
-                        //         .Include(c => c.PublishVersion.PublishVersionFiles)
-                        //         .Include(c => c.PublishVersion.PublishVersionFiles.Select(z => z.UploadFile))
-                        //         .Where(c => c.Id == machine.FW_AssignedMachineVersionId.Value).First();
-
-                        //        string downloadUrl = mv.PublishVersion.PublishVersionFiles.FirstOrDefault().UploadFile.FileUrl;
-
-                        //        var _updateFWrequest = new UpdateFirmwareRequest()
-                        //        {
-                        //            location = new Uri(downloadUrl),
-                        //            retries = 3,
-                        //            retrieveDate = DateTime.UtcNow,
-                        //            retryInterval = 10
-                        //        };
-                        //        db.MachineOperateRecord.Add(new MachineOperateRecord()
-                        //        {
-                        //            CreatedOn = DateTime.UtcNow,
-                        //            ChargeBoxId = session.ChargeBoxId,
-                        //            SerialNo = requestId,
-                        //            RequestContent = JsonConvert.SerializeObject(_updateFWrequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
-                        //            EVSE_Status = 0,
-                        //            EVSE_Value = "Fw Version:" + machine.FW_AssignedMachineVersionId,
-                        //            Status = 0,
-                        //            RequestType = 0,
-
-                        //        });
-
-                        //        db.ServerMessage.Add(new ServerMessage()
-                        //        {
-                        //            ChargeBoxId = session.ChargeBoxId,
-                        //            CreatedBy = "Server",
-                        //            CreatedOn = DateTime.UtcNow,
-                        //            OutAction = _updateFWrequest.Action.ToString(),
-                        //            OutRequest = JsonConvert.SerializeObject(_updateFWrequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }),
-                        //            SerialNo = requestId,
-                        //            InMessage = string.Empty
-
-                        //        });
-
-                        //        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 - 82
EVCB_OCPP.WSServer/Jobs/SmartChargingJob.cs

@@ -1,82 +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;
-
-namespace EVCB_OCPP.WSServer.Jobs;
-
-[DisallowConcurrentExecution]
-public class SmartChargingJob : IJob
-{
-    public SmartChargingJob(
-        ProtalServer protalServer,
-        SqlConnectionFactory<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 SqlConnectionFactory<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[StandardOCPP_Web].[dbo].[Station]" +
-             "where LBMode = 1; ";
-            var result = await conn.QueryAsync<StationInfoDto>(strSql);
-            stations = result.ToList();
-        }
-        foreach (var station in stations)
-        {
-            var compareStation = _StationInfo.Where(x => x.Id == station.Id).FirstOrDefault();
-
-            if (compareStation != null && (station.Id != compareStation.Id || station.Availability == compareStation.Availability))
-            {
-                continue;
-            }
-            var _powerDic = await protalServer.LoadingBalanceService.GetSettingPower(station.Id);
-            if (_powerDic == null)
-            {
-                continue;
-            }
-            foreach (var kv in _powerDic)
-            {
-                try
-                {
-
-                    if (kv.Value.HasValue)
-                    {
-                        protalServer.ProfileHandler.SetChargingProfile(kv.Key, kv.Value.Value, Packet.Messages.SubTypes.ChargingRateUnitType.W);
-                    }
-                }
-                catch (Exception ex)
-                {
-                    logger.LogError("Set Profile Exception: {0}", ex.ToString());
-                }
-
-            }
-        }
-        _StationInfo = stations;
-    }
-}
+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 - 0
EVCB_OCPP.WSServer/Jobs/StationConfigPollingJob.cs

@@ -0,0 +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();
+        }
+    }
+}

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

@@ -1,156 +1,158 @@
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Serialization;
-using NLog;
-using OCPPServer.Protocol;
-
-
-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(ClientData 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);
+        }
+    }
+}

+ 1813 - 1682
EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs

@@ -1,1682 +1,1813 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-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 OCPPServer.Protocol;
-using System.Data;
-using System.Diagnostics;
-using System.Globalization;
-using SuperSocket.SocketBase;
-
-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 string webConnectionString;// = ConfigurationManager.ConnectionStrings[].ConnectionString;
-    private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
-    private readonly SqlConnectionFactory<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,
-        SqlConnectionFactory<WebDBConetext> webDbConnectionFactory,
-        //IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory,
-        MeterValueDbService meterValueDbService,
-        IBusinessServiceFactory businessServiceFactory,
-        IMainDbService mainDbService,
-        ILogger<ProfileHandler> logger,
-        BlockingTreePrintService blockingTreePrintService,
-        GoogleGetTimePrintService googleGetTimePrintService,
-        OuterHttpClient httpClient)
-    {
-        //webConnectionString = configuration.GetConnectionString("WebDBContext");
-
-        this.logger = logger;
-        this.blockingTreePrintService = blockingTreePrintService;
-        this.googleGetTimePrintService = googleGetTimePrintService;
-        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, ClientData 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);
-                        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.FW_CurrentVersion = 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);
-
-                        var configValue = await mainDbService.GetMachineHeartbeatInterval(session.ChargeBoxId);
-
-                        if (configValue != null)
-                        {
-                            int.TryParse(configValue, out heartbeat_interval);
-                        }
-
-                        session.IsPending = false;
-
-                        var confirm = new BootNotificationConfirmation()
-                        {
-                            currentTime = DateTime.UtcNow,
-                            interval = session.IsPending ? 5 : heartbeat_interval,
-                            status = session.IsPending ? RegistrationStatus.Pending : RegistrationStatus.Accepted
-                        };
-
-                        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
-                            });
-                        }
-
-                        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;
-
-                        if (_request.status == Packet.Messages.SubTypes.ChargePointStatus.Faulted)
-                        {
-                            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)
-                                {
-                                    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)
-                        {
-                            try
-                            {
-                                if (session.IsBilling)
-                                {
-                                    await mainDbService.AddServerMessage(
-                                            ChargeBoxId: session.ChargeBoxId,
-                                            OutAction: Actions.DataTransfer.ToString(),
-                                            OutRequest:
-                                                new DataTransferRequest()
-                                                {
-                                                    messageId = "ID_TxEnergy",
-                                                    vendorId = "Phihong Technology",
-                                                    data = JsonConvert.SerializeObject(new { txId = _request.transactionId, ConnectorId = _request.connectorId })
-                                                }
-                                            );
-                                }
-                            }
-                            catch (Exception ex)
-                            {
-
-                                Console.WriteLine(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 };
-                        if (_request.idTag != "Backend")
-                        {
-                            var authorization_result = await businessService.Authorize(session.ChargeBoxId, _request.idTag);
-                            _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)
-                                {
-
-                                    if (session.UserPrices.ContainsKey(_request.idTag))
-                                    {
-                                        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;
-
-                                    }
-                                    else
-                                    {
-                                        session.UserPrices.Add(_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:
-                    {
-                        long getDateTimeTime, getServiceTime, getTagInfoTime, dbOpTime = 0, meterValueTime = 0;
-                        var stopTrasactionTimer = Stopwatch.StartNew();
-
-                        StopTransactionRequest _request = request as StopTransactionRequest;
-
-                        int _ConnectorId = 0;
-
-                        var utcNow = DateTime.UtcNow;
-                        getDateTimeTime = stopTrasactionTimer.ElapsedMilliseconds;
-                        var businessService = await businessServiceFactory.CreateBusinessService(session.CustomerId.ToString());
-                        getServiceTime = stopTrasactionTimer.ElapsedMilliseconds;
-
-
-                        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)).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 };
-                        }
-                        try
-                        {
-                            //遠傳太久以前的停止充電 直接拒絕 避免電樁持續重送~~~~~~~
-                            if (_request.timestamp < new DateTime(2021, 11, 1))
-                            {
-                                var confirm = new StopTransactionConfirmation()
-                                {
-                                    idTagInfo = new IdTagInfo()
-                                    {
-                                        status = AuthorizationStatus.Invalid
-                                    }
-
-                                };
-
-                                result.Message = confirm;
-                                result.Success = true;
-                                return result;
-                            }
-
-                            TransactionRecord transaction;
-                            transaction = await mainDbService.GetTransactionForStopTransaction(_request.transactionId, session.ChargeBoxId);
-
-                            if (transaction is null)
-                            {
-                                result.Exception = new Exception("Can't find transactionId " + _request.transactionId);
-                            }
-                            else
-                            {
-                                _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[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 mainDbService.AddServerMessage(
-                                        ChargeBoxId: session.ChargeBoxId,
-                                        OutAction: Actions.DataTransfer.ToString(),
-                                        OutRequest:
-                                            new DataTransferRequest()
-                                            {
-                                                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);
-                            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)
-                                {
-
-                                    if (session.UserPrices.ContainsKey(_request.idTag))
-                                    {
-                                        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;
-
-                                    }
-                                    else
-                                    {
-                                        session.UserPrices.Add(_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;
-
-                                    }
-
-                                    if (session.UserDisplayPrices.ContainsKey(_request.idTag))
-                                    {
-                                        session.UserDisplayPrices[_request.idTag] = price.DisplayMessage;
-                                    }
-                                    else
-                                    {
-                                        session.UserDisplayPrices.Add(_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:
-                    {
-                        Console.WriteLine(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, ClientData 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.EVSE_Status = (int)_confirm.status;
-                                operation.EVSE_Value = 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 }).FirstOrDefaultAsync();
-                                    decimal chargedEnergy = 0m;
-                                    if (feedto == null || string.IsNullOrEmpty(feedto.Fee)) return result;
-                                    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;
-
-                                            //小數點無條件捨去到第4位
-                                            var periodEnergy = (decimal)((int)Decimal.Multiply(item.Value, 10000) / (double)10000);
-                                            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)
-                                        {  //小數點無條件捨去到第4位
-                                            var periodEnergy = (decimal)((int)Decimal.Multiply(item.Value, 10000) / (double)10000);
-                                            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)
-                                        {
-                                            Console.WriteLine("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);
-
-                                            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 mainDbService.AddServerMessage(
-                                            ChargeBoxId: session.ChargeBoxId,
-                                            OutAction: Actions.DataTransfer.ToString(),
-                                            OutRequest:
-                                                new DataTransferRequest()
-                                                {
-                                                    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("http://ocpp.phihong.com.tw/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);
-
-                                            Console.WriteLine(JsonConvert.SerializeObject(mail_response));
-
-                                        }
-                                        #endregion
-
-                                    }
-                                    else
-                                    {
-                                        await mainDbService.AddServerMessage(
-                                            ChargeBoxId: session.ChargeBoxId,
-                                            OutAction: Actions.DataTransfer.ToString(),
-                                            OutRequest:
-                                             new DataTransferRequest()
-                                             {
-                                                 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.EVSE_Status = (int)_confirm.status;
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;
-                                operation.EVSE_Value = _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 MachineConfiguration()
-                                    {
-                                        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;
-
-                            using (var db = await maindbContextFactory.CreateDbContextAsync())
-                            {
-                                var configure = await db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId).ToListAsync();
-
-                                if (_confirm.configurationKey != null)
-                                {
-                                    foreach (var item in _confirm.configurationKey)
-                                    {
-                                        string oldValue = string.Empty;
-                                        if (item.key == null)
-                                        {
-                                            Console.WriteLine("*********************");
-                                        }
-                                        var foundConfig = configure.Find(x => x.ConfigureName == item.key);
-
-
-                                        if (foundConfig != null)
-                                        {
-                                            if (foundConfig.ConfigureName == null)
-                                            {
-                                                Console.WriteLine("*********************");
-                                            }
-
-                                            if (foundConfig.ConfigureName == "SecurityProfile")
-                                            {
-                                                oldValue = foundConfig.ConfigureSetting;
-                                            }
-
-                                            foundConfig.ReadOnly = item.IsReadOnly;
-                                            foundConfig.ConfigureSetting = string.IsNullOrEmpty(item.value) ? string.Empty : item.value;
-                                        }
-                                        else
-                                        {
-                                            await db.MachineConfigurations.AddAsync(new MachineConfiguration()
-                                            {
-                                                ChargeBoxId = session.ChargeBoxId,
-                                                ConfigureName = item.key,
-                                                ReadOnly = item.IsReadOnly,
-                                                ConfigureSetting = string.IsNullOrEmpty(item.value) ? string.Empty : item.value,
-                                                Exists = true
-                                            });
-                                        }
-
-
-                                    }
-                                }
-                                if (_confirm.unknownKey != null)
-                                {
-
-                                    foreach (var item in _confirm.unknownKey)
-                                    {
-                                        var foundConfig = configure.Find(x => x.ConfigureName == item);
-                                        if (foundConfig != null)
-                                        {
-                                            foundConfig.ReadOnly = true;
-                                            foundConfig.ConfigureSetting = string.Empty;
-                                            foundConfig.Exists = false;
-                                        }
-                                        else
-                                        {
-                                            await db.MachineConfigurations.AddAsync(new MachineConfiguration()
-                                            {
-                                                ChargeBoxId = session.ChargeBoxId,
-                                                ConfigureName = item
-                                            });
-                                        }
-                                    }
-                                }
-
-                                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.EVSE_Status = 1;
-                                    operation.EVSE_Value = 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.EVSE_Status = (int)_confirm.status;
-                                operation.EVSE_Value = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        Console.WriteLine(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, ClientData 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.EVSE_Status = (int)255;//錯誤
-                            operation.EVSE_Value = errorMsg;
-                            await db.SaveChangesAsync();
-                        }
-
-                    }
-
-                }
-                break;
-
-            default:
-                {
-                    Console.WriteLine(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;
-    }
-}
+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;
+using EVCB_OCPP.WSServer.Service.BusinessService;
+
+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 ServerMessageService messageService;
+
+
+	private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
+	private readonly ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
+	private readonly MeterValueDbService meterValueDbService;
+	private readonly ConnectorStatusDbService connectorStatusDbService;
+
+	//private readonly IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory;
+	private readonly IBusinessServiceFactory businessServiceFactory;
+	private readonly IMainDbService mainDbService;
+    private readonly WebDbService webDbService;
+    private OuterHttpClient httpClient;
+
+	public ProfileHandler(
+		IConfiguration configuration,
+		IDbContextFactory<MainDBContext> maindbContextFactory,
+		ISqlConnectionFactory<WebDBConetext> webDbConnectionFactory,
+		MeterValueDbService meterValueDbService,
+		ConnectorStatusDbService connectorStatusDbService,
+		IBusinessServiceFactory businessServiceFactory,
+		IMainDbService mainDbService,
+        WebDbService webDbService,
+        ILogger<ProfileHandler> logger,
+		ServerMessageService messageService,
+		OuterHttpClient httpClient)
+	{
+		
+
+		this.logger = logger;	
+		this.messageService = messageService;
+		this.maindbContextFactory = maindbContextFactory;
+		this.webDbConnectionFactory = webDbConnectionFactory;
+		this.meterValueDbService = meterValueDbService;
+		this.connectorStatusDbService = connectorStatusDbService;
+		this.mainDbService = mainDbService;
+        this.webDbService = webDbService;
+        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;
+
+						await connectorStatusDbService.InsertAsync(session.ChargeBoxId,(byte) _request.connectorId, (int)_request.status, _request.timestamp.HasValue? _request.timestamp.Value.ToUniversalTime(): DateTime.UtcNow,
+							_request.info, _request.vendorId, _request.vendorErrorCode, (int)_request.errorCode);
+
+						_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.ToUniversalTime() : 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.ToUniversalTime() : 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.ToUniversalTime() : 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.ToUniversalTime() : 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);
+                            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.HasValue ? _request.timestamp.Value.ToUniversalTime() : 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);
+						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);
+						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 webDbService.GetStationIsPeriodEnergyRequired(session.ChargeBoxId))
+								{
+									await messageService.SendDataTransferRequest(
+										session.ChargeBoxId,
+										messageId: "ID_TxEnergy",
+										vendorId: "Phihong Technology",
+										data: JsonConvert.SerializeObject(new { txId = _request.transactionId, ConnectorId = transaction.ConnectorId })
+										);
+								}
+								else
+								{
+									await mainDbService.SetTransactionBillingDone(_request.transactionId, 0, string.Empty);
+
+                                    IBusinessService customer = await businessServiceFactory.CreateBusinessService(session.CustomerId);
+                                    var feedto = await mainDbService.GetTransaction(_request.transactionId);
+
+                                    NotifyTransactionCompletedResult response = await customer.NotifyTransactionCompleted(feedto, null);
+                                    if (response != null && response.Success)
+                                    {
+                                        await mainDbService.ReportStopTx(_request.transactionId, response);
+                                    }
+                                }
+
+								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);
+						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;
+
+                        await mainDbService.SetMachineOperateRecordFinished(requestId, session.ChargeBoxId, _confirm.status, _confirm.data);
+
+                        if (_request.messageId == "ID_FirmwareVersion")
+                        {
+                            await mainDbService.RecordBoardVersions(session.MachineId, _confirm.data);
+                        }
+
+
+                        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 roundedPeriodEnergy = PeriodEnergyRounding(txEnergy.PeriodEnergy);
+                                //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();
+                                var feedto = await mainDbService.GetTransaction(txEnergy.TxId);
+                                decimal chargedEnergy = 0m;
+
+                                if (feedto == null)
+                                    return result;
+
+                                if (string.IsNullOrEmpty(feedto.Fee))
+                                {
+                                    if (feedto.StopTime != GlobalConfig.DefaultNullTime)
+                                    {
+                                        await mainDbService.InsertOrUpdateTransactionPeriodEnergy(txEnergy.TxId, roundedPeriodEnergy);
+                                        await mainDbService.SetTransactionBillingDone(txEnergy.TxId, 0, string.Empty);
+
+                                        IBusinessService customer = await businessServiceFactory.CreateBusinessService(session.CustomerId);
+                                        NotifyTransactionCompletedResult response = await customer.NotifyTransactionCompleted(feedto, roundedPeriodEnergy);
+										if (response != null && response.Success)
+										{
+											await mainDbService.ReportStopTx(feedto.Id, response);
+                                        }
+                                    }
+                                    return result;
+                                }
+
+                                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 customerInfo = await mainDbService.GetCustomer(session.CustomerId);
+
+                                    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));
+
+										await mainDbService.SetPncNotifiyResult(txEnergy.TxId, response.Status == System.Net.HttpStatusCode.OK, txEnergy.EVCCID);
+                                    }
+                                }
+
+                                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 roundedPeriodEnergy)
+                                    {
+                                        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);
+										var periodEnergy = 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 periodEnergy in roundedPeriodEnergy)
+                                    {
+                                        // 小數點第5位4捨五入
+                                        //var periodEnergy = PeriodEnergyRounding(item.Value);
+                                        chargedEnergy += periodEnergy.Value;
+                                    }
+
+                                    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);
+                                    IBusinessService customer = await businessServiceFactory.CreateBusinessService(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();
+                                    var tx = await mainDbService.GetTransaction(txEnergy.TxId);
+                                    if (tx == null)
+                                    {
+                                        logger.LogWarning("Tx is empty");
+                                        return result;
+                                    }
+
+                                    if (tx.BillingDone) return result;
+
+                                    if (feedto.StopTime != DefaultSetting.DefaultNullTime)
+                                    {
+                                        await mainDbService.InsertOrUpdateTransactionPeriodEnergy(txEnergy.TxId, roundedPeriodEnergy);
+                                    }
+
+                                    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;
+
+                                    await mainDbService.SetTransactionBillingDone(txEnergy.TxId, cost: chargingCost + parkingCost, receipt);
+
+                                    tx = await mainDbService.GetTransaction(txEnergy.TxId);
+
+                                    var response = await customer.NotifyTransactionCompleted(tx, roundedPeriodEnergy);
+
+                                    if (response != null)
+                                    {
+                                        couponPoint = response.CouponPoint == null ? couponPoint : response.CouponPoint.Value;
+                                        farewellMessage = response.FarewellMessage == null ? farewellMessage : response.FarewellMessage;
+
+                                        if (response.Success)
+                                        {
+                                            await mainDbService.ReportStopTx(feedto.Id, response);
+                                        }
+                                    }
+
+                                    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
+                                        );
+                                }
+                            }
+
+                        }
+
+                        using (var db = await maindbContextFactory.CreateDbContextAsync())
+						{
+
+							#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;							
+                            List<MachineConfigurations> configure = await mainDbService.GetMachineConfiguration(session.ChargeBoxId);
+							string customId = string.Empty;
+                            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 (item.key == "CentralChargeBoxId" || item.key == "ChargeBoxId")
+									{
+										customId = 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();
+
+							}
+							if (!string.IsNullOrEmpty(customId))
+							{
+								await mainDbService.UpdateCustomId(customId, session.ChargeBoxId);
+							}
+
+						}
+						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;
+	}
+
+    /// <summary>
+    /// PeriodEnergy處理4捨5入
+    /// </summary>
+    /// <param name="periodEnergy">raw data of periodEnergy</param>
+    /// <returns></returns>
+    private Dictionary<string, decimal> PeriodEnergyRounding(Dictionary<string, decimal> periodEnergy)
+    {
+        var toReturn = new Dictionary<string, decimal>();
+        foreach (var period in periodEnergy)
+        {
+            toReturn.Add(period.Key, PeriodEnergyRounding(period.Value));
+        }
+        return toReturn;
+    }
+
+
+    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;
+    }
+}

+ 251 - 223
EVCB_OCPP.WSServer/Message/FirmwareManagementProfileHandler.cs

@@ -1,223 +1,251 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.FirmwareManagement;
-using Newtonsoft.Json;
-using OCPPServer.Protocol;
-using System;
-using System.Linq;
-using Microsoft.Extensions.Logging;
-using System.Threading.Tasks;
-using Microsoft.EntityFrameworkCore;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    internal partial class ProfileHandler
-    {
-        internal async Task<MessageResult> ExecuteFirmwareManagementRequest(Actions action, ClientData 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.FW_AssignedVersion.HasValue == true && x.FW_AssignedVersion.HasValue
-                                       && x.FW_AssignedVersion != x.FW_VersionReport && x.ChargeBoxId == session.ChargeBoxId)
-                                        .Select(x => new { x.Id, x.FW_AssignedVersion }).AsNoTracking().FirstOrDefaultAsync();
-
-                                    if (machine != null)
-                                    {
-                                        var mv = db.MachineVersionFile.Include(c => c.UploadFile)
-                                         .Where(c => c.Id == machine.FW_AssignedVersion.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 }),
-                                            EVSE_Status = 0,
-                                            EVSE_Value = "Fw Version:" + machine.FW_AssignedVersion,
-                                            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 = db.Machine.Where(x => x.Id == machine.Id).FirstOrDefault();
-                                        clearMachine.FW_AssignedVersion = null;
-                                        await db.SaveChangesAsync();
-                                    }
-
-                                }
-                            }
-                            else
-                            {
-                                using (var db = await maindbContextFactory.CreateDbContextAsync())
-                                {
-                                    var item = await db.MachineOperateRecord.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.RequestType == 0)
-                                        .OrderByDescending(x => x.CreatedOn).FirstOrDefaultAsync();
-                                    if (item != null)
-                                    {
-                                        item.EVSE_Status = (int)_request.status;
-                                        item.FinishedOn = DateTime.UtcNow;
-
-                                        //if (!string.IsNullOrEmpty(item.EVSE_Value) && _request.status == Packet.Messages.SubTypes.FirmwareStatus.Installed)
-                                        //{
-                                        //    int version = 0;
-                                        //    int.TryParse(item.EVSE_Value.Split(':').Last(), out version);
-                                        //    var machine = await db.Machine.Where(x => x.ChargeBoxId == session.ChargeBoxId).FirstOrDefaultAsync();
-                                        //    machine.FW_VersionReport = version;
-                                        //}
-                                    }
-
-                                    await db.SaveChangesAsync();
-                                }
-                            }
-
-                            var confirm = new FirmwareStatusNotificationConfirmation() { };
-
-                            result.Message = confirm;
-                            result.Success = true;
-                        }
-                        break;
-                    case Actions.DiagnosticsStatusNotification:
-                        {
-                            DiagnosticsStatusNotificationRequest _request = request as DiagnosticsStatusNotificationRequest;
-
-                            var confirm = new DiagnosticsStatusNotificationConfirmation() { };
-
-                            result.Message = confirm;
-                            result.Success = true;
-
-                        }
-                        break;
-
-                    default:
-                        {
-                            Console.WriteLine(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, ClientData 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.EVSE_Status = (int)1;//OK
-                                operation.EVSE_Value = string.IsNullOrEmpty(evse_rep) ? operation.EVSE_Value : evse_rep;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        Console.WriteLine(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, ClientData 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.EVSE_Status = (int)255;//錯誤
-                                operation.EVSE_Value = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-                default:
-                    {
-                        Console.WriteLine(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.Id).FirstOrDefault();
+                                    if (item != null)
+                                    {
+                                        item.EvseStatus = (int)_request.status;
+                                        item.FinishedOn = DateTime.UtcNow;
+                                    }
+
+                                    await db.SaveChangesAsync();
+                                }
+
+                            }
+                            else
+                            {
+                                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.Id).FirstOrDefault();
+
+                                    if (item != null &&
+                                        item.EvseStatus != (int)Packet.Messages.SubTypes.DiagnosticsStatus.Uploaded)
+                                    {
+                                        item.EvseStatus = (int)Packet.Messages.SubTypes.DiagnosticsStatus.UploadFailed;
+                                        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 - 107
EVCB_OCPP.WSServer/Message/LocalAuthListManagementProfileHandler.cs

@@ -1,107 +1,108 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.LocalAuthListManagement;
-using Microsoft.EntityFrameworkCore;
-using OCPPServer.Protocol;
-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, ClientData 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.EVSE_Status = 1;//OK
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;//OK     
-                                operation.EVSE_Value = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        Console.WriteLine(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, ClientData 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.EVSE_Status = (int)255;//錯誤
-                                operation.EVSE_Value = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-                default:
-                    {
-                        Console.WriteLine(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 - 348
EVCB_OCPP.WSServer/Message/OCPP16MessageHandler.cs

@@ -1,348 +1,349 @@
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.Basic;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-using OCPPServer.Protocol;
-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(ClientData 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_OCPP20.Packet.Features;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-using OCPPServer.Protocol;
-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(ClientData 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 - 88
EVCB_OCPP.WSServer/Message/RemoteTriggerHandler.cs

@@ -1,88 +1,90 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.RemoteTrigger;
-using Microsoft.EntityFrameworkCore;
-using OCPPServer.Protocol;
-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, ClientData 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.EVSE_Status = (int)_confirm.status;//OK
-                                operation.EVSE_Value = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        Console.WriteLine(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, ClientData 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.EVSE_Status = (int)255;//錯誤
-                                operation.EVSE_Value = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-                default:
-                    {
-                        Console.WriteLine(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 - 127
EVCB_OCPP.WSServer/Message/ReservationProfileHandler.cs

@@ -1,127 +1,129 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.Reservation;
-using Microsoft.EntityFrameworkCore;
-using OCPPServer.Protocol;
-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, ClientData 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.EVSE_Status = (int)_confirm.status;//OK
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;//OK
-                                operation.EVSE_Value = _confirm.status.ToString();
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        Console.WriteLine(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, ClientData 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.EVSE_Status = (int)255;//錯誤
-                                operation.EVSE_Value = 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.EVSE_Status = (int)255;//錯誤
-                                operation.EVSE_Value = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-
-                default:
-                    {
-                        Console.WriteLine(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 - 74
EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs

@@ -1,74 +1,75 @@
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using OCPPServer.Protocol;
-using System;
-using Microsoft.Extensions.Logging;
-
-namespace EVCB_OCPP.WSServer.Message
-{
-    internal partial class ProfileHandler
-    {
-        internal MessageResult ExecuteSecurityRequest(Actions action, ClientData session, IRequest request)
-        {
-            MessageResult result = new MessageResult() { Success = false };
-
-            try
-            {
-                switch (action)
-                {
-
-
-                    default:
-                        {
-                            Console.WriteLine(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, ClientData session, IConfirmation confirm, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = false };
-
-            switch (action)
-            {
-
-                default:
-                    {
-                        Console.WriteLine(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
-                    }
-                    break;
-            }
-            return result;
-        }
-
-
-        internal MessageResult ReceivedSecurityError(Actions action, string errorMsg, ClientData session, string requestId)
-        {
-            MessageResult result = new MessageResult() { Success = true };
-
-            switch (action)
-            {
-
-                default:
-                    {
-                        Console.WriteLine(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 - 188
EVCB_OCPP.WSServer/Message/SmartChargingProfileHandler.cs

@@ -1,188 +1,190 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-using EVCB_OCPP.Packet.Features;
-using EVCB_OCPP.Packet.Messages;
-using EVCB_OCPP.Packet.Messages.SmartCharging;
-using EVCB_OCPP.Packet.Messages.SubTypes;
-using Microsoft.EntityFrameworkCore;
-using Newtonsoft.Json;
-using OCPPServer.Protocol;
-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, ClientData 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.EVSE_Status = (int)_confirm.status;//OK
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;//OK
-                                operation.EVSE_Value = _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.EVSE_Status = (int)_confirm.status;//OK
-                                operation.EVSE_Value = JsonConvert.SerializeObject(_confirm.chargingSchedule, Formatting.None);
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-                default:
-                    {
-                        Console.WriteLine(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, ClientData 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.EVSE_Status = (int)255;//錯誤
-                                operation.EVSE_Value = errorMsg;
-                                await db.SaveChangesAsync();
-                            }
-
-                        }
-                    }
-                    break;
-
-                default:
-                    {
-                        Console.WriteLine(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 - 87
EVCB_OCPP.WSServer/Program.cs

@@ -1,87 +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;
-
-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));
-
-            IHost host = Host.CreateDefaultBuilder(args)
-                //.UseEnvironment("Development")
-                //.ConfigureLogging((context, builder) => { 
-                //    builder.ClearProviders();
-                //    builder.AddAzureWebAppDiagnostics();
-                //    NLog.LogManager.Configuration = new NLogLoggingConfiguration(context.Configuration.GetSection("NLog"));
-                //})
-                //.UseNLog()
-                .AddLogServcie()
-                .ConfigureServices((hostContext, services) =>
-                {
-                    //services.AddSingleton<MeterValueGroupSingleHandler>();
-                    services.AddSingleton<IHostLifetime, DummyHostLifeTime>();
-
-                    services.AddProtalServer(hostContext.Configuration);
-
-                    services.AddTransient<BlockingTreePrintService>();
-                    services.AddTransient<GoogleGetTimePrintService>();
-                })
-                .Build();
-
-            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;
+        }
+    }
+}

+ 13 - 0
EVCB_OCPP.WSServer/Properties/PublishProfiles/FolderProfile1.pubxml

@@ -0,0 +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>
+</Project>

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

@@ -4,7 +4,8 @@
       "commandName": "Project"
     },
     "Docker": {
-      "commandName": "Docker"
+      "commandName": "Docker",
+      "DockerfileRunArguments": "-d -p 2022:80"
     }
   }
 }

+ 1301 - 1478
EVCB_OCPP.WSServer/ProtalServer.cs

@@ -1,1479 +1,1302 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-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.Packet.Messages.RemoteTrigger;
-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 Newtonsoft.Json.Linq;
-
-using OCPPServer.Protocol;
-using OCPPServer.SubProtocol;
-using SuperSocket.SocketBase;
-using SuperSocket.SocketBase.Config;
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Diagnostics;
-using System.Linq;
-using System.Security.Authentication;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using NLog;
-using Microsoft.Extensions.Configuration;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using ProtoBuf.Serializers;
-using System.Net;
-using Microsoft.AspNetCore.Builder;
-using NLog.Extensions.Logging;
-using Microsoft.Data.SqlClient;
-using System.Collections.ObjectModel;
-using System.Collections.Concurrent;
-using EVCB_OCPP.WSServer.SuperSocket;
-using Microsoft.Extensions.Logging;
-
-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
-            , SqlConnectionFactory<WebDBConetext> webDbConnectionFactory
-            , SqlConnectionFactory<MainDBContext> mainDbConnectionFactory
-            , IHostEnvironment environment
-            , IOCPPWSServerFactory ocppWSServerFactory
-            , IConnectionLogdbService connectionLogdbService
-            , WebDbService webDbService
-            , IServiceProvider serviceProvider,
-            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.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, ClientData> clientDic = new ConcurrentDictionary<string, ClientData>();
-        //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 SqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
-
-        //private readonly IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory;
-        private readonly IOCPPWSServerFactory ocppWSServerFactory;
-        private readonly IConnectionLogdbService connectionLogdbService;
-        private readonly WebDbService webDbService;
-        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;
-        #endregion
-
-        internal Dictionary<string, ClientData> ClientDic
-        {
-            get
-            {
-                Dictionary<string, ClientData> toReturn = null;
-                toReturn = new Dictionary<string, ClientData>(clientDic);
-                return toReturn;
-            }
-        }
-
-        internal List<NeedConfirmMessage> ResendMessage
-        {
-            get
-            {
-                List<NeedConfirmMessage> sendMessages = new List<NeedConfirmMessage>();
-                lock (_lockConfirmPacketList)
-                {
-                    sendMessages = needConfirmPacketList.Where(x => x.SentTimes > 1 && x.CreatedBy == "Server").ToList();
-
-                }
-
-                return sendMessages;
-            }
-        }
-
-        internal IReadOnlyList<Profile> Profiles => profiles.AsReadOnly();
-        internal LoadingBalanceService LoadingBalanceService => _loadingBalanceService;
-        internal ProfileHandler ProfileHandler => profileHandler;
-
-        public async Task StartAsync(CancellationToken cancellationToken)
-        {
-            GlobalConfig.DenyModelNames = await webDbService.GetDenyModelNames();
-            Start();
-            return;
-        }
-
-        public Task StopAsync(CancellationToken cancellationToken)
-        {
-            return Task.CompletedTask;
-        }
-
-        internal void UpdateClientDisplayPrice(string key,string price)
-        {
-            clientDic[key].DisplayPrice = price;
-        }
-
-        internal void SendMsg(ClientData 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;
-            }
-            
-            OpenNetwork();
-
-
-            RunHttpConsoleService();
-            return;
-            if (!isInDocker)
-            {
-                Task consoleReadTask = new Task(RunConsoleInteractive);
-                consoleReadTask.Start();
-                //RunConsoleInteractive();
-                return;
-            }
-        }
-
-        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, ClientData> _copyClientDic = null;
-                            _copyClientDic = new Dictionary<string, ClientData>(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, ClientData> _copyClientDic = null;
-                            _copyClientDic = new Dictionary<string, ClientData>(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;
-                }
-            }
-        }
-
-        private void RunHttpConsoleService()
-        {
-            var appBuilder = WebApplication.CreateBuilder();
-            appBuilder.Services.AddSingleton<IHostLifetime, DummyHostLifeTime>();
-            var app = appBuilder.Build();
-
-            var helpFunc = () => {
-                return string.Join("\r\n", new[] { 
-                    "Command help!!",
-                    "lcn : List Customer Name",
-                    "gc : GC Collect",
-                    "lc : List Clients",
-                    "silent : silent",
-                    "show : show log"
-                });
-            };
-            app.MapGet("/", helpFunc);
-            app.MapGet("/help", helpFunc);
-
-            app.MapPost("/stop", () => {
-                Stop();
-                return "Command stop";
-            });
-
-            app.MapPost("/gc", () => {
-                GC.Collect();
-                return "Command GC";
-            });
-
-            app.MapPost("/lc", () => {
-                List<string> toReturn = new List<string>() { "Command List Clients" };
-                Dictionary<string, ClientData> _copyClientDic = null;
-                _copyClientDic = new Dictionary<string, ClientData>(clientDic);
-                var list = _copyClientDic.Select(c => c.Value).ToList();
-                int i = 1;
-                foreach (var c in list)
-                {
-                    toReturn.Add(i + ":" + c.ChargeBoxId + " " + c.SessionID);
-                    i++;
-                }
-                return string.Join("\r\n", toReturn);
-            });
-
-            app.MapPost("/lcn", () => {
-                List<string> toReturn = new List<string> { "Command List Customer Name" };
-                Dictionary<string, ClientData> _copyClientDic = null;
-                _copyClientDic = new Dictionary<string, ClientData>(clientDic);
-                var lcn = clientDic.Select(c => c.Value.CustomerName).Distinct().ToList();
-                int iLcn = 1;
-                foreach (var c in lcn)
-                {
-                    toReturn.Add(iLcn + ":" + c + ":" + clientDic.Where(z => z.Value.CustomerName == c).Count().ToString());
-                    iLcn++;
-                }
-                return string.Join("\r\n", toReturn);
-            });
-
-            app.MapPost("/silent", () => {
-                foreach (var rule in LogManager.Configuration.LoggingRules)
-                {
-                    if (rule.RuleName != "ConsoleLog")
-                    {
-                        continue;
-                    }
-
-                    var isTargetRule = rule.Targets.Any(x => x.Name.ToLower() == "console");
-
-                    if (isTargetRule)
-                    {
-                        rule.SetLoggingLevels(NLog.LogLevel.Warn, NLog.LogLevel.Off);
-                    }
-                }
-                return "Command silent";
-            });
-
-            app.MapPost("/show", () => {
-                foreach (var rule in LogManager.Configuration.LoggingRules)
-                {
-                    if (rule.RuleName != "ConsoleLog")
-                    {
-                        continue;
-                    }
-
-                    var isTargetRule = rule.Targets.Any(x => x.Name.ToLower() == "console");
-
-                    if (isTargetRule)
-                    {
-                        rule.SetLoggingLevels(NLog.LogLevel.Trace, NLog.LogLevel.Off);
-                    }
-                }
-                return "Command show";
-            });
-
-            app.MapGet("/threads", () => {
-                ThreadPool.GetMaxThreads(out var maxWorkerThread,out var maxCompletionPortThreads);
-                ThreadPool.GetAvailableThreads(out var avaliableWorkerThread, out var avaliableCompletionPortThreads);
-                return $"WorkerThread:{avaliableWorkerThread}/{maxWorkerThread} CompletionPortThreads{avaliableCompletionPortThreads}/{maxCompletionPortThreads}";
-            });
-
-            app.MapPost("/threads", (int min, int max) => {
-                ThreadPool.GetMaxThreads(out var maxWorkerThread, out var maxCompletionPortThreads);
-                ThreadPool.GetAvailableThreads(out var avaliableWorkerThread, out var avaliableCompletionPortThreads); 
-                ThreadPool.SetMinThreads(min, 0);
-                ThreadPool.SetMaxThreads(max, maxCompletionPortThreads);
-                return $"WorkerThread:{avaliableWorkerThread}/{maxWorkerThread} CompletionPortThreads{avaliableCompletionPortThreads}/{maxCompletionPortThreads}";
-            });
-
-            app.Urls.Add("http://*:54088");
-
-            _ = app.RunAsync();
-
-            var builder = WebApplication.CreateBuilder();
-            //builder.Configuration.AddJsonFile("./EVCB_OCPP.WSServer/appsettings.json");
-            //var sec = builder.Configuration.GetSection("ReverseProxy");
-            //Console.WriteLine($"Printing.............");
-            //foreach (var pair in sec.AsEnumerable())
-            //{
-            //    Console.WriteLine($"{pair.Key}:{pair.Value} \n");
-            //}
-
-            builder.Services.AddReverseProxy()
-                .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
-            builder.Services.AddSingleton<IHostLifetime, DummyHostLifeTime>();
-            var yarpApp = builder.Build();
-            yarpApp.Urls.Add("http://*:80");
-            yarpApp.MapReverseProxy();
-            _ = yarpApp.RunAsync();
-        }
-
-        internal void Stop()
-        {
-            _cts?.Cancel();
-        }
-
-        private async void CheckEVSEConfigure(string chargeBoxId)
-        {
-            if (string.IsNullOrEmpty(chargeBoxId)) return;
-            await mainDbService.AddServerMessage(
-                ChargeBoxId: chargeBoxId,
-                OutAction: Actions.GetConfiguration.ToString(),
-                OutRequest: new GetConfigurationRequest() { key = new List<string>() }
-                );
-        }
-
-        private void OpenNetwork()
-        {
-
-            //載入OCPP Protocol
-            OCPPWSServer appServer = ocppWSServerFactory.Create(new List<OCPPSubProtocol>() { new OCPPSubProtocol(), new OCPPSubProtocol(" ocpp1.6"), new OCPPSubProtocol("ocpp2.0") });
-            //var appServer = new OCPPWSServer(new List<OCPPSubProtocol>() { new OCPPSubProtocol(), new OCPPSubProtocol(" ocpp1.6"), new OCPPSubProtocol("ocpp2.0") });
-
-            List<IListenerConfig> llistener = new List<IListenerConfig>();
-
-            if (GlobalConfig.GetWS_Port() != 0)
-            {
-                llistener.Add(new ListenerConfig { Ip = System.Net.IPAddress.Any.ToString(), Port = GlobalConfig.GetWS_Port(), Backlog = 100, Security = "None" });
-            }
-
-            foreach (var securityport in GlobalConfig.GetWSS_Ports())
-            {
-                llistener.Add(new ListenerConfig { Ip = System.Net.IPAddress.Any.ToString(), Port = securityport, Backlog = 100, Security = SslProtocols.Tls12.ToString() });
-            }
-
-            //var config = ConfigurationManager.GetSection("superSocket") as IConfigurationSource;\
-            //var certificate = configuration.GetSection("superSocket").GetSection("Servers:0").GetSection("Certificate").Get<CertificateConfig>();
-            var certificate = configuration.GetSection("SuperSocketServerCertificate").Get<CertificateConfig>();
-            ICertificateConfig Certificate = certificate;
-            IEnumerable<IListenerConfig> listeners = llistener;
-
-            //設定server config
-            var serverConfig = new ServerConfig
-            {
-                SendingQueueSize = 10,
-                //Port = Convert.ToInt32(2012),
-                //Ip = "172.17.40.13",
-                MaxRequestLength = 204800,
-                //Security = serverSecurity,
-                //Certificate = Certificate,
-                Listeners = listeners,
-                //  LogAllSocketException = true,
-                KeepAliveTime = 10,
-                // LogBasicSessionActivity = true
-                //Security = "None"
-            };
-
-            //Setup with listening port
-            if (!appServer.Setup(serverConfig, logFactory: new NLogLoggerFactory()))
-            {
-                Console.WriteLine("Failed to setup!");
-                return;
-            }
-
-            appServer.NewSessionConnected += AppServer_NewSessionConnected;
-            appServer.SessionClosed += AppServer_SessionClosed;
-
-
-            //Try to start the appServer
-            if (!appServer.Start())
-            {
-                Console.WriteLine("Failed to start!");
-                //Console.ReadKey();
-                return;
-            }
-        }
-
-        private void AppServer_SessionClosed(ClientData session, CloseReason value)
-        {
-            WriteMachineLog(session, string.Format("CloseReason: {0}", value), "Connection", "");
-            RemoveClient(session);
-        }
-
-        private async void AppServer_NewSessionConnected(ClientData 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;
-                }
-                //ClientData _removeClient = null;
-
-                var addedClient = clientDic.GetOrAdd(session.ChargeBoxId, session);
-
-                //clientDic.TryGetValue(session.ChargeBoxId, out _removeClient);
-                if (addedClient != session)
-                {
-                    WriteMachineLog(addedClient, "Duplicate Logins", "Connection", "");
-                    addedClient.Close(CloseReason.ServerShutdown);
-                    RemoveClient(addedClient);
-                }
-
-                //clientDic.add.Add(session.ChargeBoxId, session);
-                session.m_ReceiveData += ReceivedMessageTimeLimited;
-                // logger.LogDebug("------------New " + (session == null ? "Oops" : session.ChargeBoxId));
-                WriteMachineLog(session, "NewSessionConnected", "Connection", "");
-
-                using (var db = await maindbContextFactory.CreateDbContextAsync())
-                {
-                    var machine = await db.Machine.Where(x => x.ChargeBoxId == session.ChargeBoxId).FirstOrDefaultAsync();
-                    if (machine != null)
-                    {
-                        machine.ConnectionType = session.Origin.Contains("https") ? 2 : 1;
-                        await db.SaveChangesAsync();
-                    }
-
-                }
-            }
-            catch (Exception ex)
-            {
-                logger.LogError(string.Format("NewSessionConnected Ex: {0}", ex.ToString()));
-            }
-
-
-        }
-
-        async private void ReceivedMessageTimeLimited(ClientData session, string rawdata)
-        {
-            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(ClientData 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);
-                    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;
-
-                    }
-                }
-
-                await Task.Delay(10);
-            }
-            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()));
-                }
-            }
-        }
-
-        private async Task ProcessRequestMessage(MessageResult analysisResult, ClientData session, Actions action)
-        {
-            Stopwatch outter_stopwatch = Stopwatch.StartNew();
-            //BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-            if (!session.IsCheckIn && action != Actions.BootNotification)
-            {
-                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)
-                                {
-                                    session.IsCheckIn = true;
-                                    if (((BootNotificationConfirmation)replyResult.Message).status == Packet.Messages.SubTypes.RegistrationStatus.Accepted)
-                                    {
-                                        CheckEVSEConfigure(session.ChargeBoxId);
-                                        if (session.CustomerId == new Guid("298918C0-6BB5-421A-88CC-4922F918E85E") || session.CustomerId == new Guid("9E6BFDCC-09FB-4DAB-A428-43FE507600A3"))
-                                        {
-                                            await mainDbService.AddServerMessage(
-                                                ChargeBoxId: session.ChargeBoxId,
-                                                OutAction: Actions.ChangeConfiguration.ToString(),
-                                                OutRequest: new ChangeConfigurationRequest()
-                                                {
-                                                    key = "TimeOffset",
-                                                    value = "+08:00"
-                                                });
-                                        }
-                                    }
-                                    else
-                                    {
-                                        using (var db = await maindbContextFactory.CreateDbContextAsync())
-                                        {
-                                            var machine = await db.Machine.Where(x => x.ChargeBoxId == session.ChargeBoxId).FirstOrDefaultAsync();
-                                            if (machine != null)
-                                            {
-                                                if (machine.ConnectorType.Contains("6") || machine.ConnectorType.Contains("7") || machine.ConnectorType.Contains("8") || machine.ConnectorType.Contains("9"))
-                                                {
-                                                    session.IsAC = false;
-                                                }
-                                                machine.ConnectionType = session.Origin.Contains("https") ? 2 : 1;
-                                                await db.SaveChangesAsync();
-                                            }
-                                        }
-
-                                        await SetDefaultFee(session);
-                                    }
-                                }
-
-                                if (action == Actions.Authorize && replyResult.Message is AuthorizeConfirmation)
-                                {
-                                    var authorizeRequest = (IRequest)analysisResult.Message as AuthorizeRequest;
-                                    if (session.UserDisplayPrices.ContainsKey(authorizeRequest.idTag))
-                                    {
-                                        await mainDbService.AddServerMessage(
-                                            ChargeBoxId: session.ChargeBoxId,
-                                            OutAction: Actions.DataTransfer.ToString(),
-                                            OutRequest: new DataTransferRequest() { 
-                                                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 
-                                }
-                                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, ClientData session, Actions action)
-        {
-
-            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-            if (await ReConfirmMessage(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, ClientData session, Actions action)
-        {
-            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
-            if (await ReConfirmMessage(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 void Send(ClientData 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()));
-            }
-
-
-        }
-
-        async private Task<string> SetDefaultFee(ClientData client)
-        {
-            string displayPriceText = string.Empty;
-            string charingPriceText = string.Empty;
-
-            if (string.IsNullOrEmpty(client.ChargeBoxId)) return displayPriceText;
-
-            try
-            {
-
-                using (SqlConnection conn = await webDbConnectionFactory.CreateAsync())
-                {
-                    var parameters = new DynamicParameters();
-                    parameters.Add("@MachineId", client.MachineId, DbType.String, ParameterDirection.Input, 36);
-                    string displayPricestrSql = "";
-                    string strSql = "";
-
-                    if (client.IsAC)
-                    {
-                        displayPricestrSql = "   SELECT  [AC_BillingMethod] as BillingMethod,[AC_FeeName] as FeeName,[AC_Fee] as ChargingFeebyHour" +
-                    "  ,[AC_ParkingFee] as ParkingFee, [Currency]  FROM[StationMachine]  left join[dbo].[Station]" +
-                    "  on[StationMachine].StationId = Station.[Id]  where StationMachine.MachineId=@MachineId and Station.IsBilling=1; ";
-
-                        strSql = " SELECT CAST( [StartTime] as varchar(5)) StartTime,CAST( [EndTime] as varchar(5)) EndTime,[Fee]  FROM[StationMachine]  left join [dbo].[StationFee]" +
-                       " on[StationMachine].StationId = StationFee.StationId  where StationMachine.MachineId =@MachineId and StationFee.IsAC=1; ";
-                    }
-                    else
-                    {
-                        displayPricestrSql = "   SELECT  [DC_BillingMethod] as BillingMethod,[DC_FeeName] as FeeName,[DC_Fee] as ChargingFeebyHour" +
-                   "  ,[DC_ParkingFee] as ParkingFee, [Currency]  FROM[StationMachine]  left join[dbo].[Station]" +
-                   "  on[StationMachine].StationId = Station.[Id]  where StationMachine.MachineId=@MachineId and Station.IsBilling=1; ";
-
-                        strSql = " SELECT CAST( [StartTime] as varchar(5)) StartTime,CAST( [EndTime] as varchar(5)) EndTime,[Fee]  FROM[StationMachine]  left join [dbo].[StationFee]" +
-                       " on[StationMachine].StationId = StationFee.StationId  where StationMachine.MachineId =@MachineId and StationFee.IsAC=0; ";
-
-                    }
-                    //var result = await conn.QueryAsync<StationFee>(displayPricestrSql, parameters);
-                    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 void AddConfirmMessage(string chargePointSerialNumber, int table_id, string requestId, string action, string msg_id, string createdBy, string sendMessage)
-        {
-            NeedConfirmMessage _needConfirmMsg = new NeedConfirmMessage();
-            _needConfirmMsg.Id = table_id;
-            _needConfirmMsg.SentAction = action;
-            _needConfirmMsg.SentOn = DateTime.UtcNow;
-            _needConfirmMsg.SentTimes = 4;
-            _needConfirmMsg.ChargePointSerialNumber = chargePointSerialNumber;
-            _needConfirmMsg.RequestId = requestId;
-            _needConfirmMsg.SentUniqueId = msg_id;
-            _needConfirmMsg.CreatedBy = createdBy;
-            _needConfirmMsg.SentMessage = sendMessage;
-
-            if (needConfirmActions.Contains(action))
-            {
-
-                lock (_lockConfirmPacketList)
-                {
-                    needConfirmPacketList.Add(_needConfirmMsg);
-                }
-            }
-        }
-
-        internal void RemoveConfirmMessage()
-        {
-            var before10Mins = DateTime.UtcNow.AddMinutes(-10);
-            lock (_lockConfirmPacketList)
-            {
-                var removeList = needConfirmPacketList.Where(x => x.SentTimes == 0 || x.SentOn < before10Mins).ToList();
-                foreach (var item in removeList)
-                {
-                    needConfirmPacketList.Remove(item);
-                }
-            }
-        }
-
-        private async Task<bool> ReConfirmMessage(MessageResult analysisResult)
-        {
-            bool confirmed = false;
-            if (needConfirmActions.Contains(analysisResult.Action))
-            {
-
-                NeedConfirmMessage foundRequest = null;
-                lock (_lockConfirmPacketList)
-                {
-                    foundRequest = needConfirmPacketList.Where(x => x.SentUniqueId == analysisResult.UUID).FirstOrDefault();
-                }
-
-                if (foundRequest != null && foundRequest.Id > 0)
-                {
-                    foundRequest.SentTimes = 0;
-                    foundRequest.SentInterval = 0;
-                    analysisResult.RequestId = foundRequest.RequestId;
-
-                    using (var db = await maindbContextFactory.CreateDbContextAsync())
-                    {
-                        var sc = await db.ServerMessage.Where(x => x.Id == foundRequest.Id).FirstOrDefaultAsync();
-                        sc.InMessage = JsonConvert.SerializeObject(analysisResult.Message, Formatting.None);
-                        sc.ReceivedOn = DateTime.UtcNow;
-                        await db.SaveChangesAsync();
-                        //  Console.WriteLine(string.Format("Now:{0} ServerMessage Id:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), foundRequest.Id));
-
-                    }
-                    confirmed = true;
-
-
-
-                }
-                else if (analysisResult.Action == Actions.TriggerMessage.ToString())
-                {
-                    confirmed = true;
-                }
-                else
-                {
-                    logger.LogError(string.Format("Received no record Action:{0} MessageId:{1} ", analysisResult.Action, analysisResult.UUID));
-                }
-            }
-
-            return confirmed;
-        }
-
-
-
-
-        internal void RemoveClient(ClientData session)
-        {
-            if (session == null)
-            {
-                return;
-            }
-
-            if (!string.IsNullOrEmpty(session.MachineId))
-                logger.LogTrace("RemoveClient[" + session.ChargeBoxId + "]");
-
-            if (session.Connected)
-            {
-                session.Close(CloseReason.ServerShutdown);
-            }
-            RemoveClientDic(session);
-            try
-            {
-                session.m_ReceiveData -= ReceivedMessageTimeLimited;
-                // 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(ClientData 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(ClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false)
-        {
-            try
-            {
-
-                if (clientData == null || string.IsNullOrEmpty(data)) return;
-
-                if (clientData.ChargeBoxId == null)
-                {
-                    logger.LogCritical(clientData.Path + "]********************session ChargeBoxId null sessionId=" + clientData.SessionID);
-                }
-
-                connectionLogdbService.WriteMachineLog(clientData, 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
+            , EnvCheckService envCheckService)
+        {
+            _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"]);
+
+            var maxBootCntConfig = configuration["MaxBootCnt"];
+            if (!string.IsNullOrEmpty(maxBootCntConfig) &&
+                int.TryParse(maxBootCntConfig, out var maxBootCntConfigInt))
+            {
+                maxBootCnt = maxBootCntConfigInt;
+            }
+            var bootReservCntConfig = configuration["BootReservCnt"];
+            if (!string.IsNullOrEmpty(bootReservCntConfig) &&
+                int.TryParse(bootReservCntConfig, out var bootReservCntConfigInt))
+            {
+                bootReservCnt = bootReservCntConfigInt;
+            }
+            bootSemaphore = new SemaphoreSlim(maxBootCnt, maxBootCnt);
+
+            // = configuration.GetConnectionString("WebDBContext");
+            this.profileHandler = serviceProvider.GetService<ProfileHandler>();// new ProfileHandler(configuration, serviceProvider);
+            _loadingBalanceService = new LoadingBalanceService(mainDbConnectionFactory, webDbConnectionFactory);
+
+            envCheckService.CheckVariable();
+
+            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 readonly int maxBootCnt = 10;
+        private readonly int bootReservCnt = 5;
+        private readonly SemaphoreSlim bootSemaphore = new SemaphoreSlim(20, 20);
+        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;
+        #endregion
+
+        internal Dictionary<string, WsClientData> GetClientDic()
+        {
+            Dictionary<string, WsClientData> toReturn = null;
+            toReturn = new Dictionary<string, WsClientData>(clientDic);
+            return toReturn;
+        }
+
+        internal int GetBootLockCnt()
+        {
+            return bootSemaphore.CurrentCount;
+        }
+
+        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>>();
+        internal readonly List<Func<WsClientData, CancellationToken, Task>> LateInitActions = 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);
+                await webDbService.UpdateProtocalVersion(session.ChargeBoxId, session.DisconnetCancellationToken);
+            }
+            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
+                                        )
+                                    {
+                                        //session.BootStatus = BootStatus.Pending;
+                                        session.BootStatus = BootStatus.Initializing;
+                                        session.AddTask(StartInitializeEVSE(session));
+                                    }
+
+                                    if (bootNotificationConfirmation.status == Packet.Messages.SubTypes.RegistrationStatus.Accepted
+                                        )
+                                    {
+                                        session.IsCheckIn = true;
+                                        //session.AddTask(StartAllInitializeEVSE(session));
+                                        session.AddTask(StartLateInitializeEVSE(session));
+                                        //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 StartAllInitializeEVSE(WsClientData session)
+        {
+            await bootSemaphore.WaitAsync();
+            try
+            {
+                await InitializeEVSE(session);
+                await LateInitializeEVSE(session);
+            }
+            catch (Exception e)
+            {
+                logger.LogCritical("StartAllInitializeEVSE:{errormsg}", e.Message);
+                logger.LogCritical("StartAllInitializeEVSE:{errorStackTrace}", e.StackTrace);
+            }
+            finally
+            {
+                bootSemaphore.Release();
+            }
+        }
+
+        private async Task StartInitializeEVSE(WsClientData session)
+        {
+            await bootSemaphore.WaitAsync();
+            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;
+            }
+
+            string requestId = string.Empty;
+
+            var displayPriceText = await webDbService.SetDefaultFee(session);
+            UpdateClientDisplayPrice(session.ChargeBoxId, displayPriceText);
+
+            Func<string , CancellationToken , Task<string>> sendTask;
+            sendTask = async (string serialNo, CancellationToken token) => await messageService.SendGetEVSEConfigureRequest(session.ChargeBoxId, serialNo: serialNo, token: token);
+            var response = await confirmWaitingMessageSerevice.SendAndWaitUntilResultAsync(sendTask, token: session.DisconnetCancellationToken);
+            if (response is GetConfigurationConfirmation getConfigurationConfirmation)
+            {
+                session.Data[GlobalConfig.BootData_EVSEConfig_Key] = getConfigurationConfirmation.configurationKey;
+            }
+
+            if (!string.IsNullOrEmpty(displayPriceText))
+            {
+                sendTask = async (string serialNo, CancellationToken token) => await messageService.SendChangeConfigurationRequest(
+                    session.ChargeBoxId, key: "DefaultPrice", value: displayPriceText, serialNo: serialNo);
+                await confirmWaitingMessageSerevice.SendAndWaitResultAsync(sendTask, token: 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);
+            }
+
+            session.Data.Remove(GlobalConfig.BootData_EVSEConfig_Key);//= getConfigurationConfirmation.configurationKey;
+            //await StationConfigService?.CheckAndUpdateEvseConfig(session, session.DisconnetCancellationToken);
+        }
+
+        private async Task StartLateInitializeEVSE(WsClientData session)
+        {
+            await WaitCanStartLateInitEVSE(session.DisconnetCancellationToken);
+
+            try
+            {
+                //await Task.Delay(TimeSpan.FromMinutes(5));
+                await LateInitializeEVSE(session);
+            }
+            catch (Exception e)
+            {
+                logger.LogCritical("StartLateInitializeEVSE:{errormsg}", e.Message);
+                logger.LogCritical("StartLateInitializeEVSE:{errorStackTrace}", e.StackTrace);
+            }
+            finally
+            {
+                //session.BootStatus = BootStatus.Pending;
+                bootSemaphore.Release();
+            }
+        }
+
+        private async Task WaitCanStartLateInitEVSE(CancellationToken token)
+        {
+            bool passed = false;
+            do
+            {
+                while (bootSemaphore.CurrentCount < bootReservCnt)
+                {
+                    await Task.Delay(TimeSpan.FromMinutes(2), cancellationToken: token);
+                }
+                passed = bootSemaphore.Wait(0);
+            } while (!passed);
+        }
+
+        private async Task LateInitializeEVSE(WsClientData session)
+        {
+            Func<string, CancellationToken , Task<string>> sendTask; 
+
+            sendTask  = async (string serialNo, CancellationToken token) => await messageService.SendDataTransferRequest(
+                session.ChargeBoxId,
+                messageId: "ID_FirmwareVersion",
+                vendorId: "Phihong Technology",
+                data: string.Empty,
+                serialNo: serialNo,
+                token: token);
+            await confirmWaitingMessageSerevice.SendAndWaitResultAsync(sendTask, token: session.DisconnetCancellationToken);
+
+            sendTask = async (string serialNo, CancellationToken token) => await messageService.SendTriggerMessageRequest(
+                session.ChargeBoxId,
+                messageTrigger: MessageTrigger.DiagnosticsStatusNotification,
+                serialNo: serialNo,
+                token: token);
+            await confirmWaitingMessageSerevice.SendAndWaitResultAsync(sendTask, token: session.DisconnetCancellationToken);
+
+            for (var index = 0; index < LateInitActions.Count; index++)
+            {
+                var lateInitFunction = LateInitActions[index];
+                await lateInitFunction(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);
+            }
+
+
+        }
+    }
 }

+ 3 - 2
EVCB_OCPP.WSServer/Service/BlockingTreePrintService.cs

@@ -1,5 +1,6 @@
 using Dapper;
 using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
 using EVCB_OCPP.WSServer.Helper;
 using Microsoft.Data.SqlClient;
 using Microsoft.Extensions.Logging;
@@ -15,7 +16,7 @@ namespace EVCB_OCPP.WSServer.Service
     public class BlockingTreePrintService
     {
         public BlockingTreePrintService(ILogger<BlockingTreePrintService> logger,
-            SqlConnectionFactory<MainDBContext> connectionFactory)
+            ISqlConnectionFactory<MainDBContext> connectionFactory)
         {
             this.logger = logger;
             this.connectionFactory = connectionFactory;
@@ -97,7 +98,7 @@ namespace EVCB_OCPP.WSServer.Service
             ORDER BY LEVEL ASC
             """;
         private readonly ILogger<BlockingTreePrintService> logger;
-        private readonly SqlConnectionFactory<MainDBContext> connectionFactory;
+        private readonly ISqlConnectionFactory<MainDBContext> connectionFactory;
     }
 
     public class TreeStruct

+ 99 - 94
EVCB_OCPP.WSServer/Service/BusinessServiceFactory.cs → EVCB_OCPP.WSServer/Service/BusinessService/BusinessServiceFactory.cs

@@ -1,94 +1,99 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-using EVCB_OCPP.Packet.Messages.SubTypes;
-using EVCB_OCPP.WSServer.Dto;
-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);
-
-    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.Domain.Models.MainDb;
+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.BusinessService;
+
+
+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);
+
+    ValueTask<NotifyTransactionCompletedResult> NotifyTransactionCompleted(TransactionRecord tx, Dictionary<string, decimal> roundedPeriodEnergy = null);
+
+}
+
+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(Guid 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,
+                InstantStopTxReport = _customerDb.InstantStopTxReport,
+            };
+        }
+        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.ToString();
+        return toReturn;
+
+        //return isCallOut ? new OuterBusinessService(customerId) : 
+    }
+
+}

+ 9 - 0
EVCB_OCPP.WSServer/Service/BusinessService/IBusinessServiceFactory.cs

@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service.BusinessService
+{
+    public interface IBusinessServiceFactory
+    {
+        Task<IBusinessService> CreateBusinessService(Guid customerId);
+    }
+}

+ 99 - 93
EVCB_OCPP.WSServer/Service/LocalBusinessService.cs → EVCB_OCPP.WSServer/Service/BusinessService/LocalBusinessService.cs

@@ -1,93 +1,99 @@
-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)
-        {
-            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.Domain.Models.MainDb;
+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.BusinessService
+{
+    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);
+        }
+
+        public ValueTask<NotifyTransactionCompletedResult> NotifyTransactionCompleted(TransactionRecord tx, Dictionary<string, decimal> roundedPeriodEnergy = null)
+        {
+            return ValueTask.FromResult<NotifyTransactionCompletedResult>(null);
+        }
+    }
+}

+ 16 - 0
EVCB_OCPP.WSServer/Service/BusinessService/Model.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service.BusinessService
+{
+    public class NotifyTransactionCompletedResult
+    {
+        public decimal? CouponPoint { set; get; } = null;
+        public string FarewellMessage { set; get; } = null;
+        public string ErrorMsg { get; set; } = null;
+        public bool Success => string.IsNullOrEmpty(ErrorMsg);
+    }
+}

+ 386 - 281
EVCB_OCPP.WSServer/Service/OuterBusinessService.cs → EVCB_OCPP.WSServer/Service/BusinessService/OuterBusinessService.cs

@@ -1,281 +1,386 @@
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-using EVCB_OCPP.Packet.Messages.SubTypes;
-using EVCB_OCPP.WSServer.Dto;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using NLog;
-using SuperSocket.SocketBase;
-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)
-        {
-            //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
-            {
-                string requestParams = string.Format("charging_auth?ChargeBoxId={0}&IdTag={1}", chargeBoxId, idTag);
-
-               // if (CustomerId.ToLower() == "9e6bfdcc-09fb-4dab-a428-43fe507600a3")
-                {
-                    logger.LogInformation(chargeBoxId + " Charging Monitor======================================>");
-                    logger.LogInformation(signMaterial.APIUrl + requestParams);
-                }
-                var response = await httpClient.Post(signMaterial.APIUrl + requestParams, new Dictionary<string, string>()
-                            {
-                                { "PartnerId",signMaterial.Id}
-
-                            }, null, signMaterial.SaltKey).ConfigureAwait(false);
-               // if (CustomerId.ToLower() == "9e6bfdcc-09fb-4dab-a428-43fe507600a3")
-                {
-                    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;
-        }
-
-        public Task NotifyConnectorUnplugged(string data)
-        {
-            throw new NotImplementedException();
-        }
-    }
-}
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.MainDb;
+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.BusinessService
+{
+    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; }
+
+        internal bool InstantStopTxReport { 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<int>(), 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();
+        }
+
+        public async ValueTask<NotifyTransactionCompletedResult> NotifyTransactionCompleted(TransactionRecord tx, Dictionary<string, decimal> roundedPeriodEnergy = null)
+        {
+            if (signMaterial == null || !signMaterial.InstantStopTxReport)
+            {
+                return null;
+            }
+
+            var toReturn = new NotifyTransactionCompletedResult();
+
+            //var PeriodEnergy = await mainDbService.GetTransactionPeriodEnergy(tx.Id);
+
+            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,
+                PeriodEnergy = roundedPeriodEnergy,
+                StartSOC = int.TryParse(tx.StartSoc, out int StartSOCint) ? StartSOCint : (int?)null,
+                StopSOC = int.TryParse(tx.StopSoc, out int StopSOCint) ? StopSOCint : (int?)null
+
+            };
+
+            logger.LogDebug("completed_session " + JsonConvert.SerializeObject(request, GlobalConfig.JSONSERIALIZER_FORMAT));
+            var response = await httpClient.Post(
+                signMaterial.APIUrl + "completed_session", 
+                new Dictionary<string, string>()
+                {
+                    { "PartnerId", CustomerId}
+                }, 
+                request,
+                signMaterial.SaltKey);
+
+            logger.LogDebug("completed_session Response" + response.Response);
+
+            if (!response.Success)
+            {
+                toReturn.ErrorMsg = !string.IsNullOrEmpty(response.Response) ? response.Response : response.StatusCode.ToString();
+                return toReturn;
+            }
+
+            if (!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"))
+                {
+                    toReturn.CouponPoint = jo["CouponPoint"].Value<Decimal>();
+                }
+
+                if (jo.ContainsKey("FarewellMessage"))
+                {
+                    toReturn.FarewellMessage = jo["FarewellMessage"].Value<string>();
+                }
+            }
+
+            return toReturn;
+        }
+    }
+}

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

@@ -0,0 +1,305 @@
+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;
+using System.Collections.Concurrent;
+
+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 ConcurrentDictionary<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 Task<object> SendAndWaitUntilResultAsync(Func<string, Task> startSendTaskFunc, int maxWaitSec = 180_000, CancellationToken token = default)
+        {
+            Func<string, CancellationToken, Task> func = (string result, CancellationToken token) => startSendTaskFunc(result);
+            return SendAndWaitUntilResultAsync(func, maxWaitSec: maxWaitSec , token: token);
+        }
+
+        internal async Task<object> SendAndWaitUntilResultAsync(Func<string, CancellationToken, Task> startSendTaskFunc, int maxWaitSec = 180_000, CancellationToken token = default)
+        {
+            object message;
+            do
+            {
+                message = await SendAndWaitResultAsync(startSendTaskFunc, maxWaitSec: maxWaitSec, token: token);
+            }
+            while (message == null && !token.IsCancellationRequested);
+            return message;
+        }
+
+        internal Task<object> SendAndWaitResultAsync(Func<string, Task> startSendTaskFunc, int maxWaitSec = 1_000, CancellationToken token = default)
+        {
+            Func<string, CancellationToken, Task> func = (string result, CancellationToken token) => startSendTaskFunc(result);
+            return SendAndWaitResultAsync(func, maxWaitSec: maxWaitSec, token: token);
+        }
+
+        internal async Task<object> SendAndWaitResultAsync(Func<string, CancellationToken, Task> startSendTaskFunc, int maxWaitSec = 1_000, CancellationToken token = default)
+        {
+            object message;
+            var SerialNo = Guid.NewGuid().ToString();
+            var waitObject = CreateAndAddWaitObject(SerialNo);
+            await startSendTaskFunc(SerialNo, token);
+            message = await WaitResultAsync(SerialNo, waitObject: waitObject, maxWaitSec: maxWaitSec, token: token);
+            return message;
+        }
+
+        internal async Task<object> WaitResultAsync(string serialNo, MessageResultWaitObject waitObject = null, int maxWaitSec = 180_000, CancellationToken token = default)
+        {
+            if (waitObject == null)
+            {
+                waitObject = new MessageResultWaitObject();
+                asyncWaitingTasks.TryAdd(serialNo, waitObject);
+            }
+
+            var task = waitObject.Lock.WaitAsync(token);
+            var completedTask = await Task.WhenAny(task, Task.Delay(maxWaitSec, token));
+            if (completedTask != task)
+            {
+                logger.LogWarning("wait {msg} time out", serialNo);
+            }
+
+            asyncWaitingTasks.Remove(serialNo, out var _);
+            return waitObject.Result?.Message;
+        }
+
+        private MessageResultWaitObject CreateAndAddWaitObject(string serialNo)
+        {
+            var waiObj = new MessageResultWaitObject();
+            asyncWaitingTasks.TryAdd(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);
+    }
+}

+ 0 - 369
EVCB_OCPP.WSServer/Service/ConnectionLogdbService.cs

@@ -1,369 +0,0 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.WSServer.Helper;
-using log4net;
-using Microsoft.Data.SqlClient;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using NLog.Fluent;
-using OCPPServer.Protocol;
-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;
-
-public interface IConnectionLogdbService
-{
-    void WarmUpLog();
-    void WriteMachineLog(ClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false);
-}
-
-public class ConnectionLogdbService : IConnectionLogdbService
-{
-    public const string LimitConfigKey = "ConnectionLogDbLimit";
-
-    public ConnectionLogdbService(
-        IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory,
-        SqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory,
-        ILogger<ConnectionLogdbService> logger,
-        IConfiguration configuration)
-    {
-        this.connectionLogdbContextFactory = connectionLogdbContextFactory;
-        this.sqlConnectionFactory = sqlConnectionFactory;
-        this.logger = logger;
-        //connectionLogdbConnectionString = configuration.GetConnectionString("MeterValueDBContext");
-
-        var opLimit = GetLimit(configuration);
-        this.queueHandler = new(WriteMachineLogEF, opLimit);
-
-        InitInsertConnectonLogHandler();
-    }
-
-    private readonly IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory;
-    private readonly SqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory;
-    private readonly ILogger<ConnectionLogdbService> logger;
-    private readonly QueueHandler<MachineLog> queueHandler;
-    //private readonly string connectionLogdbConnectionString;
-    private readonly Queue<string> _existTables = new();
-    private GroupSingleHandler<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(ClientData 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.RemoteEndPoint == null ? "123" : log.clientData.RemoteEndPoint.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 GroupSingleHandler<MachineLog>(
-            //BulkInsertWithBulkCopy,
-            BundleInsertWithDapper,
-            //loggerFactory.CreateLogger("InsertMeterValueHandler")
-            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 + "]********************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.RemoteEndPoint == null ? "123" : log.clientData.RemoteEndPoint.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(IEnumerable<MachineLog> parms)
-    {
-        var watch = Stopwatch.StartNew();
-        long t0, t1, t2, t3, t4;
-        var workTime = DateTime.UtcNow;
-
-        var parmsList = parms.ToList();
-
-        if (parmsList.Count == 0)
-        {
-            return;
-        }
-        var candidate = parmsList[0];
-        if (!await GetTableExist(workTime))
-        {
-            t0 = watch.ElapsedMilliseconds;
-            await WriteMachineLogEF(candidate);
-            watch.Stop();
-            t1 = watch.ElapsedMilliseconds;
-            if (t1 > 500)
-            {
-                logger.LogWarning("ConnectionLog InsertWithDapper {0}/{1}", t0, t1);
-            }
-            parmsList.Remove(candidate);
-        }
-
-        t0 = watch.ElapsedMilliseconds;
-
-        t1 = watch.ElapsedMilliseconds;
-        using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
-        //using var trans = await sqlConnection.BeginTransactionAsync();
-
-        t2 = 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);
-            """;
-
-        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.RemoteEndPoint == null ? "123" : log.clientData.RemoteEndPoint.ToString(), DbType.String, size: 25);
-            parameters.Add("Session", log.clientData.SessionID == null ? "123" : log.clientData.SessionID, DbType.String, size: 36);
-
-            await sqlConnection.ExecuteAsync(command, parameters
-                //, trans
-                );
-        }
-
-        t3 = watch.ElapsedMilliseconds;
-        //await trans.CommitAsync();
-
-        watch.Stop();
-        t4 = watch.ElapsedMilliseconds;
-        if (t4 > 1000)
-        {
-            logger.LogWarning("MachineLog Bundle Dapper {0}/{1}/{2}/{3}/{4}/{5}", t0, t1, t2, t3, t4, parms.Count());
-        }
-    }
-
-    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.RemoteEndPoint == null ? "123" : param.clientData.RemoteEndPoint.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(ClientData clientData, string data, string messageType, string errorMsg, bool isSent);

+ 265 - 0
EVCB_OCPP.WSServer/Service/DbService/ConnectionLogdbService.cs

@@ -0,0 +1,265 @@
+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 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, 50) { 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: 50);
+            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 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);

+ 178 - 0
EVCB_OCPP.WSServer/Service/DbService/ConnectorStatusDbService.cs

@@ -0,0 +1,178 @@
+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 ConnectorStatusDbService
+{
+	private readonly IDbContextFactory<OnlineRecordDBContext> onlineRecordDbContextFactory;
+	private readonly ISqlConnectionFactory<OnlineRecordDBContext> sqlConnectionFactory;
+	private readonly ILoggerFactory loggerFactory;
+
+	
+	private readonly ILogger logger;
+	private readonly Queue<string> _existTables = new();
+
+
+	public ConnectorStatusDbService(
+		IDbContextFactory<OnlineRecordDBContext> onlineRecordDbContextFactory,
+		ISqlConnectionFactory<OnlineRecordDBContext> sqlConnectionFactory,
+		ILogger<ConnectorStatusDbService> logger,
+		ILoggerFactory loggerFactory,
+		IConfiguration configuration		
+		)
+	{
+		this.onlineRecordDbContextFactory = onlineRecordDbContextFactory;
+		this.sqlConnectionFactory = sqlConnectionFactory;
+		this.loggerFactory = loggerFactory;	
+		this.logger = logger;
+
+	
+
+	}
+
+	public Task InsertAsync(string chargeBoxId, byte connectorId, int status, DateTime createdOn
+		   , string errorInfo, string vendorId, string vendorErrorCode, int chargePointErrorCodeId)
+	{
+		var param = new InsertConnectorStatusParam(chargeBoxId,  connectorId,  status,   errorInfo
+			,  vendorId,  createdOn,  vendorErrorCode,  chargePointErrorCodeId);
+
+		return InsertAsync(param);
+	}
+
+	public Task InsertAsync(InsertConnectorStatusParam param)
+	{	
+		return InsertWithDapper(param);
+	}
+	
+
+	private async Task InsertWithDapper(InsertConnectorStatusParam 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("ConnectorStatusRecord 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("ConnectorStatusRecord Dapper {0}/{1}", t0, t1);
+		}
+	}
+
+
+	private async Task InsertWithNoCheckDapper(string tableName, InsertConnectorStatusParam data, SqlConnection conn = null, DbTransaction trans = null)
+	{
+		string command = $"""
+                INSERT INTO {tableName} ([ChargeBoxId],[ConnectorId]
+                ,[Status],[CreatedOn],[ChargePointErrorCodeId],[ErrorInfo]
+                ,[VendorId],[VendorErrorCode])
+                VALUES (@ChargeBoxId,@ConnectorId, @Status, @CreatedOn,@ChargePointErrorCodeId, @ErrorInfo, @VendorId, @VendorErrorCode);
+                """;
+
+		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("Status", data.status, DbType.Int32);
+		parameters.Add("ErrorInfo", data.errorInfo, DbType.String, size: 50);
+		parameters.Add("VendorId", data.vendorId, DbType.String, size: 255);
+		parameters.Add("CreatedOn", data.createdOn, DbType.DateTime);
+		parameters.Add("VendorErrorCode", data.vendorErrorCode, DbType.String, size: 100);
+		parameters.Add("ChargePointErrorCodeId", data.chargePointErrorCodeId, DbType.Int32);
+		parameters.Add("ChargeBoxId", data.chargeBoxId, DbType.String, size: 50);		
+
+		await connection.ExecuteAsync(command, parameters, trans);
+
+		if (isLocalConnection)
+		{
+			connection.Dispose();
+		}
+	}
+
+	
+
+	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 onlineRecordDbContextFactory.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(InsertConnectorStatusParam param)
+	{
+		using var db = await onlineRecordDbContextFactory.CreateDbContextAsync();
+
+		string sp = "[dbo].[uspInsertConnectorStatusRecord] @ChargeBoxId," +
+"@ConnectorId,@Status,@CreatedOn,@ChargePointErrorCodeId,@ErrorInfo,@VendorId,@VendorErrorCode";
+
+		List<SqlParameter> parameter = new List<SqlParameter>();
+		parameter.AddInsertConnectorStatusRecordSqlParameters(
+			chargeBoxId: param.chargeBoxId
+			, connectorId: param.connectorId
+			, status: param.status
+			, errorInfo: param.errorInfo
+			, vendorId: param.vendorId
+			, createdOn: param.createdOn
+			, vendorErrorCode: param.vendorErrorCode
+			, chargePointErrorCodeId: param.chargePointErrorCodeId	);
+
+		await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
+	}
+
+	private static string GetTableName(DateTime dateTime)
+		=> $"ConnectorStatusRecord{dateTime:yyMMdd}";
+
+
+}
+
+public record InsertConnectorStatusParam(string chargeBoxId, byte connectorId, int status, string  errorInfo
+			, string vendorId, DateTime createdOn, string vendorErrorCode, int chargePointErrorCodeId);

+ 1656 - 0
EVCB_OCPP.WSServer/Service/DbService/MainDbService.cs

@@ -0,0 +1,1656 @@
+using Azure;
+using Azure.Core;
+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.Packet.Messages.SubTypes;
+using EVCB_OCPP.WSServer.Helper;
+using EVCB_OCPP.WSServer.Service.BusinessService;
+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;
+using System.Diagnostics;
+
+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, CancellationToken token = default);
+    Task<string> AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy = "", DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "", CancellationToken token = default);
+    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 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);
+
+    Task InsertOrUpdateTransactionPeriodEnergy(int txId, Dictionary<string, decimal> periodEnergy);
+
+    Task<Dictionary<string, decimal>> GetTransactionPeriodEnergy(int transactionId);
+
+    Task<bool> UpdateCustomId(string customId, string chargeboxId);
+    Task SetTransactionBillingDone(int txId, decimal cost, string receipt);
+
+    Task SetMachineOperateRecordFinished(string requestId, string chargeBoxId, DataTransferStatus status, string data);
+    Task RecordBoardVersions(string machineId, string data);
+    Task<TransactionRecord> GetTransaction(int txId);
+    Task SetPncNotifiyResult(int txid, bool isNotifySuccess, string eVCCID);
+    Task ReportStopTx(int txid, NotifyTransactionCompletedResult reportResults);
+    Task<bool> GetIsEvseDiagnosticsStatusAvalaible(string charger_SN);
+    Task ContinueLastEvseDiagnostic(string chargeboxId);
+}
+
+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 = "", CancellationToken token = default)
+    {
+        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, token: token);
+        return SerialNo;
+    }
+
+    public Task<string> AddServerMessage(ServerMessage message, CancellationToken token = default)
+    {
+        //return AddServerMessageEF(message);
+        return addServerMessageHandler.HandleAsync(message, token);
+        //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<bool> UpdateCustomId(string customId,string chargeboxId)
+	{
+		using var conn = await sqlConnectionFactory.CreateAsync();
+		try
+		{
+			var parameters = new DynamicParameters();
+			parameters.Add("@ChargeBoxId", chargeboxId, size: 50);
+			parameters.Add("@CustomId", customId, size: 25);
+			var resultCnt = await conn.ExecuteAsync("""
+                UPDATE Machine
+                SET CustomId = @CustomId
+                WHERE ChargeBoxId = @ChargeBoxId
+                """, parameters);
+		}
+		catch (Exception e)
+		{
+			logger.LogError(e.Message);
+			logger.LogCritical("UpdateCustomId update fail");
+			return false;
+		}
+		return true;
+	}
+
+	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;
+        }
+    }
+
+    public async Task SetTransactionBillingDone(int txId, decimal cost, string receipt)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        TransactionRecord transaction = await db.TransactionRecord.Where(x => x.Id == txId).FirstOrDefaultAsync();
+        if (transaction is null)
+        {
+            logger.LogTrace("Tx is empty");
+            return;
+        }
+
+        transaction.Cost = cost;
+        transaction.Receipt = receipt;
+        transaction.BillingDone = true;
+
+        db.ChangeTracker.AutoDetectChangesEnabled = false;
+        //db.Configuration.ValidateOnSaveEnabled = false;
+        db.TransactionRecord.Attach(transaction);
+        db.Entry(transaction).Property(x => x.Cost).IsModified = true;
+        db.Entry(transaction).Property(x => x.Receipt).IsModified = true;
+        db.Entry(transaction).Property(x => x.BillingDone).IsModified = true;
+        await db.SaveChangesAsync();
+    }
+
+    public async Task SetMachineOperateRecordFinished(string requestId, string chargeBoxId, DataTransferStatus status, string data)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        var operation = await db.MachineOperateRecord.Where(x => x.SerialNo == requestId &&
+                            x.ChargeBoxId == chargeBoxId && x.Status == 0).FirstOrDefaultAsync();
+        if (operation != null)
+        {
+            operation.FinishedOn = DateTime.UtcNow;
+            operation.Status = 1;//電樁有回覆
+            operation.EvseStatus = (int)status;
+            operation.EvseValue = string.IsNullOrEmpty(data) ? "" : data;
+            await db.SaveChangesAsync();
+        }
+    }
+
+    public async Task RecordBoardVersions(string machineId, string data)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        var machine = new Machine() { Id = machineId };
+        if (machine != null)
+        {
+            db.ChangeTracker.AutoDetectChangesEnabled = false;
+            //db.Configuration.ValidateOnSaveEnabled = false;
+            db.Machine.Attach(machine);
+            machine.BoardVersions = data;
+            db.Entry(machine).Property(x => x.BoardVersions).IsModified = true;
+            await db.SaveChangesAsync();
+        }
+    }
+
+    public async Task<TransactionRecord> GetTransaction(int txId)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        TransactionRecord feedto = await db.TransactionRecord.Where(x => x.Id == txId).FirstOrDefaultAsync();
+        return feedto;
+    }
+
+    public async Task SetPncNotifiyResult(int txid, bool isNotifySuccess, string eVCCID)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+
+        var pnc_info = db.TransactionRecord.Where(x => x.Id == txid).FirstOrDefault();
+
+        pnc_info.NotifyPnC = isNotifySuccess;
+
+        pnc_info.Evccid = 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();
+    }
+
+    public async Task ReportStopTx(int txid, NotifyTransactionCompletedResult reportResults)
+    {
+        using var dbConn = await sqlConnectionFactory.CreateAsync();
+
+        var cmd = """
+            UPDATE [dbo].[TransactionRecord] 
+            SET StopTransactionReportedOn=@StopTransactionReportedOn, ErrorMsg=@ErrorMsg  
+            WHERE Id=@Id
+            """;
+
+        var parameters = new DynamicParameters();
+        parameters.Add("@Id", txid, DbType.Int32, ParameterDirection.Input);
+        parameters.Add("@StopTransactionReportedOn", DateTime.UtcNow, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@ErrorMsg", reportResults.ErrorMsg, DbType.String, ParameterDirection.Input, -1);
+        await dbConn.ExecuteAsync(cmd, parameters);
+    }
+
+    public async Task<bool> GetIsEvseDiagnosticsStatusAvalaible(string chargeboxId)
+    {
+        var cmd = """
+            SELECT TOP (1) [EVSE_Status]
+            FROM [dbo].[MachineOperateRecord]
+            where [Action] = 'GetDiagnostics' and [EVSE_Value] != '' and ChargeBoxId = @ChargeBoxId
+            order by id desc
+            """;
+        var parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxId", chargeboxId, size: 50);
+
+        using var dbConn = await sqlConnectionFactory.CreateAsync();
+        var result = await dbConn.QueryFirstOrDefaultAsync<int?>(cmd, parameters);
+        return result == null ? true : (result.Value == (int)DiagnosticsStatus.Uploaded || result.Value == (int)DiagnosticsStatus.UploadFailed);
+    }
+
+    public async Task ContinueLastEvseDiagnostic(string chargeboxId)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+
+        var updatedRecord = await db.MachineOperateRecord
+            .Where(x => x.ChargeBoxId == chargeboxId && x.Action == "GetDiagnostics" && x.RequestType == 1 && x.EvseValue != "")
+            .OrderByDescending(x => x.Id)
+            .FirstOrDefaultAsync();
+
+        var needUpdateRecord = await db.MachineOperateRecord
+            .Where(x => x.ChargeBoxId == chargeboxId && x.Action == "GetDiagnostics" && x.RequestType == 1 && x.EvseValue == "")
+            .OrderByDescending(x => x.Id)
+            .FirstOrDefaultAsync();
+
+        if (updatedRecord == null)
+        {
+            logger.LogInformation("chargeboxId:{0} updatedRecord null", chargeboxId);
+            return;
+        }
+
+        if (needUpdateRecord == null)
+        {
+            logger.LogInformation("chargeboxId:{0} needUpdateRecord null", chargeboxId);
+            return;
+        }
+
+        needUpdateRecord.EvseStatus = updatedRecord.EvseStatus;
+        needUpdateRecord.EvseValue = updatedRecord.EvseValue;
+        needUpdateRecord.Status = updatedRecord.Status;
+        await db.SaveChangesAsync();
+
+        //var getLastUpdatedDataCmd = """
+        //    SELECT TOP (1) [Id], [EVSE_Value], [EVSE_Status]
+        //    FROM [dbo].[MachineOperateRecord]
+        //    WHERE [Action] = 'GetDiagnostics' and [EVSE_Status] != 0 and ChargeBoxId = @ChargeBoxId
+        //    order by Id desc
+        //    """;
+        //var getLastIdCmd = """
+        //    SELECT TOP (1) [Id]
+        //    FROM [dbo].[MachineOperateRecord]
+        //    WHERE [Action] = 'GetDiagnostics' and ChargeBoxId = @ChargeBoxId
+        //    order by Id desc
+        //    """;
+        //var updateLastCmd = """
+        //    Update [dbo].[MachineOperateRecord]
+        //    SET [EVSE_Value] = @EVSE_Value, [EVSE_Status] = @EVSE_Status
+        //    WHERE [Id] = @Id
+        //    """;
+
+        //using var dbConn = await sqlConnectionFactory.CreateAsync();
+        //var parameters = new DynamicParameters();
+
+        //parameters.Add("@ChargeBoxId", chargeboxId, size: 50);
+        //var getDataResult = await dbConn.QueryFirstOrDefaultAsync<MachineOperateRecord>(getLastUpdatedDataCmd, parameters);
+        //if (getDataResult is null)
+        //{
+        //    logger.LogInformation("chargebox:{0} diagnostic get updated faied", chargeboxId);
+        //}
+
+        //var getNeedUpdateIdResult = await dbConn.QueryFirstOrDefaultAsync<int?>(getLastIdCmd, parameters);
+        //if (getNeedUpdateIdResult is null)
+        //{
+        //    logger.LogInformation("chargebox:{0} diagnostic get update id faied", chargeboxId);
+        //}
+
+        //parameters = new DynamicParameters();
+        //parameters.Add("@EVSE_Value", getDataResult.EvseValue);
+        //parameters.Add("@EVSE_Status", getDataResult.EvseStatus);
+        //var updateResult = await dbConn.ExecuteAsync(updateLastCmd, parameters);
+        //if (updateResult == 0)
+        //{
+        //    logger.LogInformation("chargebox:{0} diagnostic update faied", chargeboxId);
+        //}
+    }
+
+    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<Dictionary<string, decimal>> GetTransactionPeriodEnergy(int transactionId)
+    {
+        var parms = new DynamicParameters();
+        parms.Add("@TransactionId", transactionId);
+
+        var cmd = """
+                SELECT TOP(1) 
+                [H00],[H01],[H02],[H03],[H04],[H05],[H06],[H07],[H08],[H09],[H10]
+                ,[H11],[H12],[H13],[H14],[H15],[H16],[H17],[H18],[H19],[H20]
+                ,[H21],[H22],[H23]
+                FROM [dbo].[TransactionPeriodEnergy]
+                WHERE TransactionId = @TransactionId
+                """;
+
+        using var dbConn = await sqlConnectionFactory.CreateAsync();
+        dynamic queryResult = await dbConn.QueryFirstOrDefaultAsync(cmd, parms);
+        if (queryResult is not IDictionary<string, object> queryResultPairs)
+        {
+            return null;
+        }
+
+        var toReturn = new Dictionary<string, decimal>();
+        for (int hour = 0; hour < 24; hour++)
+        {
+            var key = GetColName(hour);
+            if (queryResultPairs[key] is decimal value)
+            {
+                toReturn.Add(hour.ToString(), value);
+            }
+        }
+
+        return toReturn;
+
+    }
+
+    public async Task InsertOrUpdateTransactionPeriodEnergy(int txId, Dictionary<string, decimal> periodEnergy)
+    {
+        try
+        {
+            var isExists = await GetTransactionPeriodEnergyExists(txId);
+            var param = CreateParameters(txId, periodEnergy);
+
+            if (isExists)
+            {
+                await UpdateTransactionPeriodEnergy(param);
+                return;
+            }
+            await InsertTransactionPeriodEnergy(param);
+        }
+        catch (Exception e)
+        {
+            logger.LogError(e.Message);
+        }
+        return;
+
+        async Task<bool> GetTransactionPeriodEnergyExists(int txId)
+        {
+            var param = new DynamicParameters();
+            param.Add("@TransactionId", txId, DbType.Int32, ParameterDirection.Input);
+
+            var cmd = """
+            SELECT TransactionId
+            FROM TransactionPeriodEnergy
+            WHERE TransactionId = @TransactionId
+            """;
+            using var conn = await sqlConnectionFactory.CreateAsync();
+            return await conn.QueryFirstOrDefaultAsync<int?>(cmd, param) != null;
+        }
+
+        async Task<int> UpdateTransactionPeriodEnergy(DynamicParameters param)
+        {
+            var cmd = """
+            UPDATE TransactionPeriodEnergy
+            SET H00 = @H00, H01 = @H01, H02 = @H02, H03 = @H03, H04 = @H04, H05 = @H05, H06 = @H06, H07 = @H07, H08 = @H08,
+            H09 = @H09, H10 = @H10, H11 = @H11, H12 = @H12, H13 = @H13, H14 = @H14, H15 = @H15, H16 = @H16, H17 = @H17,
+            H18 = @H18, H19 = @H19, H20 = @H20, H21 = @H21, H22 = @H22, H23 = @H23
+            WHERE TransactionId = @TransactionId
+            """;
+            using var conn = await sqlConnectionFactory.CreateAsync();
+            return await conn.ExecuteAsync(cmd, param);
+        }
+
+        async Task InsertTransactionPeriodEnergy(DynamicParameters param)
+        {
+            var cmd = """
+            INSERT INTO TransactionPeriodEnergy
+            (TransactionId, H00, H01, H02, H03, H04, H05, H06, H07, H08, H09, H10, H11, H12, H13, H14, H15, H16, H17, H18, H19, H20, H21, H22, H23)
+            VALUES (@TransactionId, @H00, @H01, @H02, @H03, @H04, @H05, @H06, @H07, @H08, @H09, @H10, @H11, @H12, @H13, @H14, @H15, @H16, @H17, @H18, @H19, @H20, @H21, @H22, @H23)
+            """;
+            using var conn = await sqlConnectionFactory.CreateAsync();
+            await conn.ExecuteAsync(cmd, param);
+        }
+
+        DynamicParameters CreateParameters(int txId, Dictionary<string, decimal> periodEnergy)
+        {
+            var parameters = new DynamicParameters();
+            parameters.Add("@TransactionId", txId, DbType.Int32, ParameterDirection.Input);
+
+            for (int hour = 0; hour < 24; hour++)
+            {
+                var key = hour.ToString();
+                if (!periodEnergy.ContainsKey(key))
+                {
+                    parameters.Add(GetColName(hour), 0, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 4);
+                    continue;
+                }
+
+                parameters.Add(GetColName(hour), periodEnergy[key], DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 4);
+            }
+            return parameters;
+        }
+
+        string GetColName(int hour)
+        {
+            return $"@H{hour.ToString("00")}";
+        }
+
+
+
+    }
+
+	string GetColName(int hour)
+	{
+		return $"H{hour.ToString("00")}";
+	}
+}
+
+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 - 376
EVCB_OCPP.WSServer/Service/MeterValueDbService.cs → EVCB_OCPP.WSServer/Service/DbService/MeterValueDbService.cs

@@ -1,377 +1,419 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-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;
-
-public class MeterValueDbService
-{
-    private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
-    private readonly SqlConnectionFactory<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 GroupSingleHandler<InsertMeterValueParam> insertMeterValueHandler;
-
-    public MeterValueDbService(
-        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
-        SqlConnectionFactory<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);
-    }
-
-    private void InitInsertMeterValueHandler()
-    {
-        if (insertMeterValueHandler is not null)
-        {
-            throw new Exception($"{nameof(InitInsertMeterValueHandler)} should only called once");
-        }
-
-        insertMeterValueHandler = new GroupSingleHandler<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: (byte)param.connectorId
-            , value: param.value
-            , createdOn: param.createdOn
-            , contextId: param.contextId
-            , formatId: param.formatId
-            , measurandId: param.measurandId
-            , phaseId: param.phaseId
-            , locationId: param.locationId
-            , unitId: param.unitId
-            , transactionId: param.transactionId);
-
-        await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
-    }
-
-    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 async Task BundleInsertWithDapper(IEnumerable<InsertMeterValueParam> parms)
-    {
-        var watch = Stopwatch.StartNew();
-        long t0, t1, t2, t3, t4;
-
-        var parmsList = parms.ToList();
-        foreach (var param in parms)
-        {
-            if (!await GetTableExist(param.createdOn))
-            {
-                await InsertWithStoredProcedure(param);
-                parmsList.Remove(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
-                    );
-            }
-        }
-
-        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, parms.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: (byte)param.connectorId
-            , value: param.value
-            , createdOn: param.createdOn
-            , contextId: param.contextId
-            , formatId: param.formatId
-            , measurandId: param.measurandId
-            , phaseId: param.phaseId
-            , locationId: param.locationId
-            , unitId: param.unitId
-            , transactionId: param.transactionId);
-
-        await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
-    }
-
-    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);

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

@@ -0,0 +1,301 @@
+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 Newtonsoft.Json.Linq;
+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>(new CommandDefinition(
+                    commandText: displayPricestrSql,
+                    parameters: parameters,
+                    cancellationToken: client.DisconnetCancellationToken
+                    ));
+                if (result == default)
+                {
+                    client.IsBilling = false;
+                    return string.Empty;
+                }
+                var stationPrice = result;//.First();
+
+                if (stationPrice.BillingMethod == 1)
+                {
+                    //var chargingPriceResult = await conn.QueryAsync<ChargingPrice>(strSql, parameters);
+                    var chargingPriceResult = await conn.QueryAsync<ChargingPrice>(new CommandDefinition(
+                        commandText: strSql,
+                        parameters: parameters,
+                        cancellationToken: client.DisconnetCancellationToken
+                        ));
+                    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>>();
+        }
+    }
+
+    internal async Task<bool> GetStationIsPeriodEnergyRequired(string chargeBoxId, CancellationToken token = default)
+    {
+        var stationId = await GetEvseStation(chargeBoxId, token);
+        if (stationId is null)
+        {
+            return false;
+        }
+
+        string getSql = """
+                SELECT [IsPeriodEnergyRequired]
+                FROM [dbo].[Station]
+                WHERE [Id] = @StationId
+                """; 
+
+        var parameters = new DynamicParameters();
+        parameters.Add("@StationId", stationId, direction: ParameterDirection.Input);
+
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        try
+        {
+            bool? isPeriodEnergyRequired = await conn.QueryFirstOrDefaultAsync<bool?>(new CommandDefinition(getSql, parameters, cancellationToken: token));
+            return isPeriodEnergyRequired is null ? false : isPeriodEnergyRequired.Value;
+        }
+        catch (Exception e)
+        {
+            logger.LogWarning(e.Message);
+            logger.LogWarning(e.StackTrace);
+            return true;
+        }
+    }
+
+    internal async Task UpdateProtocalVersion(string chargeBoxId, CancellationToken token = default)
+    {
+        string setSql = """
+                UPDATE [dbo].[StationMachine]
+                SET [ChargerProtocol] = '1.6'
+                WHERE [ChargeBoxId] = @ChargeBoxId
+                """;
+
+        var parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxId", chargeBoxId, direction: ParameterDirection.Input);
+
+        using SqlConnection conn = await webDbConnectionFactory.CreateAsync();
+        try
+        {
+            int rows = await conn.ExecuteAsync(new CommandDefinition(setSql, parameters, cancellationToken: token));
+        }
+        catch (Exception e)
+        {
+            logger.LogWarning(e.Message);
+            logger.LogWarning(e.StackTrace);
+        }
+    }
+}

+ 125 - 0
EVCB_OCPP.WSServer/Service/EnvCheckService.cs

@@ -0,0 +1,125 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Policy;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.WSServer.Service
+{
+    public class EnvCheckService
+    {
+        public EnvCheckService(
+            IConfiguration configuration,
+            ILogger<EnvCheckService> logger)
+        {
+            this.configuration = configuration;
+            this.logger = logger;
+
+            CheckVariable();
+        }
+
+        private readonly IConfiguration configuration;
+        private readonly ILogger<EnvCheckService> logger;
+
+        private const string certPath = "/run/secrets";
+        private const string certFileName1 = "cert1";
+        private const string certFileName2 = "cert2";
+        private const string certFileName3 = "licence";
+
+        private const string key1 = "MYkGePq3yYw0w4FYKXSl2KP4erMVos+WtkZS+SKnD+E=/BhbnUz7mYSSzr8xfSFp3Q==";
+        private const string key2 = "MIIBCgKCAQEAnsVwgEb9IVBDZrYE42KOMuI5RWAgnk4OsUWMu/UuHLbQyUxv9cNTSQqWnUgEU4J1Dys8cbRT+qsND9eJHFkxnGE1vpXO04ADDCTBBPn1b3J6Wj6lp5Wy/feREg6oiCGJB7nAK1SzmFLKQxKmJX09vbveE02JNvU3KqmFuOthuNqzCCjht8o58+68as3tldskrGy2OjQwBp5rPL0lT9x2tBStm6xiBKPmG87WBjBqPL9LwXI+lmGnfJOABmbddjMp746RCUjzTr/40tXSjL1LZKEA/4vv5qnqIWz+peHfyu8L+XjJjipQ8OKATB6vUWAVrDBH6uHPvpU3G5eNq/o5TQIDAQAB";
+        //private const string key3 = "MIIEpAIBAAKCAQEAnsVwgEb9IVBDZrYE42KOMuI5RWAgnk4OsUWMu/UuHLbQyUxv9cNTSQqWnUgEU4J1Dys8cbRT+qsND9eJHFkxnGE1vpXO04ADDCTBBPn1b3J6Wj6lp5Wy/feREg6oiCGJB7nAK1SzmFLKQxKmJX09vbveE02JNvU3KqmFuOthuNqzCCjht8o58+68as3tldskrGy2OjQwBp5rPL0lT9x2tBStm6xiBKPmG87WBjBqPL9LwXI+lmGnfJOABmbddjMp746RCUjzTr/40tXSjL1LZKEA/4vv5qnqIWz+peHfyu8L+XjJjipQ8OKATB6vUWAVrDBH6uHPvpU3G5eNq/o5TQIDAQABAoIBAAmZ0NrA8C+iheDhItyJKiYjjekHDhHkkHjhxsfa5KXx3CBAEgkffZrOHBt4rmJKYj+/kpEhoW5oB4sssmrXbeuR6UkUEAS0GfsTAeUGJHRPzNxGK4g9wiwfW1NnNYO922ZvMhKstYuBfh+eMhPURfaCNMSgDKsZGvPmemKbQTaHUqaAFkmUCpvnd8DLTatj5dwmqW6ZFX8LA9nYE/6GqCyxjIdLLD0pX5QWqEugstB1fqtL4hX3AQKolL5CTrCKv8UOCufoBC2GwEVeYgu10puF5rw34zXKPNq3g0FpPyRk+hIfnWRfJlyP34DepNhpBSxkfbB3wNEJJvR9dGOP9JUCgYEAzmg960uy3mKXCxAnTf1zrrEIHWIdYZIunQ94wpfFnbQ4YsjhcvVYuqML+P4VQjWp5zytIHog5GTZahG5TgsvlFhqXjKCi1mcJ3VkBgwlTrc42TpzsWFtoOAnsjCMVBwj1eRpLXRehZC9plfZUDlLhqDXa17Pz73B1w2Cj0tcUHcCgYEAxOsw3u4rXu4qC2LZ4kee3o/u/ewlRS1IJjt7sQlwqFW6flFbyqtzbkziXLj5ePVMnYHT0jfUkBUa07BlJlPcGFk0WbrfuQ6YjwP/0nBz68uKgSpHX2viY62kQkHAveXz0CQRYpwebpFJmmgmQPz1QYApP3LZQ9ghmnlqqGWiGVsCgYEAjqVkBXTvTNl94Vtsjm2WwSf/n67q9z97j3fd0T3qiK7AOSTzCeudQn7kC1QthPBpVzGLxGIi0TURPEi7c8AvRapE+IyXw45OaMasNbG3JsthMl8/DVtz1DaVuIPst0QrT+rm9U7y9AOvzYHw4Yx3Mbd+qOmBXOSbTfA8RqOiTMkCgYAnpZ7KU7OhwlvuvPFXcMoYz2vz7fa3Dd3n7LQDuf3XtqL8yc6saIauH4a32npIE0NgtwH54knG1Kj5FBvZcqrusA8tPcXLkZe/u7NnIEMGp410YhnKqYMERLK8sFZpJYJIVuq1Ku+pnVDvaKbDEOskS/SCFNKPqVVir618yDGx3wKBgQDI6aMMQ8o6ALi4HGandjlt70sSJtVq2+TLnclX3r6Er3zKfwMu8M4WRmMVrtp79Si471DoJ2f4xQRTR3IaySZ0x2Fxxt9x/t6UjJPX54x0hjI+WVt0N5BwEzhQ357H0HlzpL/u0mIDcFrjjpsp2MCcggV3TqwXZ6kyaUf9uzo1OA==";
+
+        public void CheckVariable()
+        {
+            var connectionSectionString = JsonConvert.SerializeObject(configuration.GetSection("ConnectionStrings").AsEnumerable());
+            var dbSn = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(connectionSectionString)));
+
+            if (!CheckIsInDocker() ||
+                CheckIsInAzureAppService() ||
+                CheckVariable(dbSn, key1, key2)
+                )
+            {
+                return;
+            }
+
+            Console.WriteLine($"Error:{dbSn}");
+            Environment.Exit(401);
+        }
+
+        private static bool CheckVariable(string dbSn ,string key1, string key2)
+        {
+            if (!File.Exists(Path.Combine(certPath, certFileName1)) ||
+                !File.Exists(Path.Combine(certPath, certFileName2)) )
+            {
+                return false;
+            }
+
+            var cert1 = File.ReadAllText(Path.Combine(certPath, certFileName1));
+            var cert2 = File.ReadAllText(Path.Combine(certPath, certFileName2));
+
+            string keySn = GetSn(cert1, key1);
+            //if (string.IsNullOrEmpty(dbSn) ||
+            //    !keySn.StartsWith(dbSn))
+            //{
+            //    return false;
+            //}
+            return CheckSn(keySn, cert2, key2);
+        }
+
+        private static string GetSn(string cert, string key)
+        {
+            var aesDecrept = Aes.Create();
+            (byte[] aesKey, byte[] aesIv) = GetAesKey(key);
+            aesDecrept.Key = aesKey;
+            aesDecrept.IV = aesIv;
+
+            var toDecryptStream = new MemoryStream(Convert.FromBase64String(cert));
+            CryptoStream deCryptoStream = new CryptoStream(
+                   toDecryptStream,
+                   aesDecrept.CreateDecryptor(),
+                   CryptoStreamMode.Read);
+            StreamReader sr = new StreamReader(deCryptoStream);
+            var deceypedString = sr.ReadToEnd();
+            return deceypedString;
+        }
+
+        private static bool CheckSn(string sn, string cert2, string key)
+        {
+            var encryptor = new System.Security.Cryptography.RSACryptoServiceProvider();
+            encryptor.ImportRSAPublicKey(Convert.FromBase64String(key), out var bytesRead);
+            var isValid = encryptor.VerifyData(Encoding.UTF8.GetBytes(sn), SHA1.Create(), Convert.FromBase64String(cert2));
+            return isValid;
+        }
+
+        private static (byte[] key, byte[] iv) GetAesKey(string key)
+        {
+            var keyString = key.Substring(0, 44);
+            var ivString = key.Substring(44);
+
+            return (Convert.FromBase64String(keyString), Convert.FromBase64String(ivString));
+        }
+
+        private bool CheckEnv()
+        {
+            return !CheckIsInDocker() || CheckIsInAzureAppService();
+        }
+
+        private bool CheckIsInAzureAppService()
+        {
+            return !String.IsNullOrEmpty(configuration["WEBSITE_SITE_NAME"]);
+        }
+
+        private bool CheckIsInDocker()
+        {
+            return !String.IsNullOrEmpty(configuration["RUNNING_IN_CONTAINER"]);
+        }
+    }
+}

+ 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;
-using ZstdSharp.Unsafe;
-
-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 - 0
EVCB_OCPP.WSServer/Service/HeaderRecordService.cs

@@ -0,0 +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============================================================");
+        }
+    }
+}

+ 0 - 9
EVCB_OCPP.WSServer/Service/IBusinessServiceFactory.cs

@@ -1,9 +0,0 @@
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.Service
-{
-    public interface IBusinessServiceFactory
-    {
-        Task<IBusinessService> CreateBusinessService(string customerId);
-    }
-}

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

@@ -1,367 +1,372 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-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 SqlConnectionFactory<MainDBContext> mainDbConnectionFactory;
-        private readonly SqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
-
-        //private readonly string mainConnectionString;
-        //private readonly string webConnectionString;
-
-        public LoadingBalanceService(SqlConnectionFactory<MainDBContext> mainDbConnectionFactory, SqlConnectionFactory<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 onlineResult = await conn.QueryAsync<string>(onlineChargerSql, new { machineIds = machineIds.ToArray() });
-                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, 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; ";
-                result = (await conn.QueryAsync<string>(offlineChargerSql, new { machineIds = machineIds })).ToList();
-                foreach (var charger in result)
-                {
-                    string txSql = "SELECT TOP(1) [Id] from [dbo].[TransactionRecord] where ChargeBoxId=@ChargeBoxId and StopTime = '1991-01-01 00:00:00.000'; ";
-                    var 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;
+        }
+
+
+
+    }
+}
+

+ 0 - 910
EVCB_OCPP.WSServer/Service/MainDbService.cs

@@ -1,910 +0,0 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.Domain.Models.Database;
-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;
-
-public interface IMainDbService
-{
-    Task<string> GetMachineAuthorizationKey(string ChargeBoxId);
-    Task<string> GetMachineConfiguration(string ChargeBoxId, string configName);
-    Task<string> GetMachineHeartbeatInterval(string ChargeBoxId);
-    Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId);
-    Task<string> GetMachineSecurityProfile(string ChargeBoxId);
-    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 AddServerMessage(ServerMessage message);
-    Task AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy = "", DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "");
-    ValueTask AddMachineError(byte ConnectorId, DateTime CreatedOn, int Status, string ChargeBoxId, int ErrorCodeId, string ErrorInfo, int PreStatus, string VendorErrorCode, string VendorId);
-    ValueTask<Customer> GetCustomer(string id);
-    ValueTask<Customer> GetCustomer(Guid id);
-    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);
-}
-
-public class MainDbService : IMainDbService
-{
-    public MainDbService(
-        IDbContextFactory<MainDBContext> contextFactory,
-        SqlConnectionFactory<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");
-        this.startupSemaphore = new(startupLimit);
-
-        var opLimit = GetOpLimit(configuration);
-        this.opSemaphore = new SemaphoreSlim(opLimit);
-
-        InitUpdateConnectorStatusHandler();
-        InitUpdateMachineBasicInfoHandler();
-        InitAddServerMessageHandler();
-    }
-
-    private const string CustomerMemCacheKeyFromat = "Customer_{0}";
-    private const string ChargeBoxConnectorIdMemCacheKeyFromat = "Connector_{0}{1}";
-
-    private readonly IDbContextFactory<MainDBContext> contextFactory;
-    private readonly SqlConnectionFactory<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 GroupSingleHandler<StatusNotificationParam> statusNotificationHandler;
-    private GroupSingleHandler<UpdateMachineBasicInfoParam> updateMachineBasicInfoHandler;
-    private GroupSingleHandler<ServerMessage> addServerMessageHandler;
-
-    public async Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId)
-    {
-        using var semaphoreWrapper = await startupSemaphore.GetToken();
-        using var db = await contextFactory.CreateDbContextAsync();
-
-        var machine = await db.Machine.Where(x => x.ChargeBoxId == ChargeBoxId && x.IsDelete == false).Select(x => new { x.CustomerId, x.Id }).AsNoTracking().FirstOrDefaultAsync();
-        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);
-        var customerName = customer?.Name;
-        return new MachineAndCustomerInfo(machine.Id, machine.CustomerId, customerName);
-    }
-
-    public async Task<string> GetMachineConfiguration(string ChargeBoxId, string configName)
-    {
-        using var semaphoreWrapper = await startupSemaphore.GetToken();
-        using var db = await contextFactory.CreateDbContextAsync();
-        return await db.MachineConfigurations
-            .Where(x => x.ChargeBoxId == ChargeBoxId && x.ConfigureName == configName)
-            .Select(x => x.ConfigureSetting).FirstOrDefaultAsync();
-    }
-
-    public Task<string> GetMachineSecurityProfile(string ChargeBoxId)
-    {
-        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.SecurityProfile);
-    }
-
-    public Task<string> GetMachineAuthorizationKey(string ChargeBoxId)
-    {
-        return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.AuthorizationKey);
-    }
-
-    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 Domain.Models.Database.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 Task 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
-        };
-
-        return AddServerMessage(data);
-    }
-
-    public Task AddServerMessage(ServerMessage message)
-    {
-        //return AddServerMessageEF(message);
-        //return addServerMessageHandler.HandleAsync(message);
-        return AddServerMessageDapper(message);
-    }
-
-    public ValueTask<Customer> GetCustomer(string id)
-        => GetCustomer(new Guid(id));
-
-    public async ValueTask<Customer> GetCustomer(Guid id)
-    {
-        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())
-        {
-            toReturn = await db.Customer.FirstOrDefaultAsync(x => x.Id == id);
-        }
-
-        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);
-    }
-
-    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();
-    }
-
-    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);
-    }
-    private void InitUpdateConnectorStatusHandler()
-    {
-        if (statusNotificationHandler is not null)
-        {
-            throw new Exception($"{nameof(InitUpdateConnectorStatusHandler)} should only called once");
-        }
-
-        statusNotificationHandler = new GroupSingleHandler<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 GroupSingleHandler<ServerMessage>(
-            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 GroupSingleHandler<UpdateMachineBasicInfoParam>(
-            handleFunc: BundelUpdateMachineBasicInfo,
-            logger: loggerFactory.CreateLogger("UpdateMachineBasicInfoHandler"),
-            workerCnt: 10);
-    }
-
-    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.FW_CurrentVersion = machine.FW_CurrentVersion;
-        _machine.Iccid = DateTime.UtcNow.ToString("yy-MM-dd HH:mm");
-        _machine.Imsi = machine.Imsi;
-        _machine.MeterSerialNumber = machine.MeterSerialNumber;
-        _machine.MeterType = machine.MeterType;
-
-        await db.SaveChangesAsync();
-
-        //using var semaphoreWrapper = await startupSemaphore.GetToken();
-    }
-
-    private async Task BundelUpdateMachineBasicInfo(IEnumerable<UpdateMachineBasicInfoParam> pams)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        using var trans = await db.Database.BeginTransactionAsync();
-
-        pams = pams.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.FW_CurrentVersion = pam.machine.FW_CurrentVersion;
-            _machine.Iccid = DateTime.UtcNow.ToString("yy-MM-dd HH:mm");
-            _machine.Imsi = pam.machine.Imsi;
-            _machine.MeterSerialNumber = pam.machine.MeterSerialNumber;
-            _machine.MeterType = pam.machine.MeterType;
-        }
-
-        await db.SaveChangesAsync();
-        await trans.CommitAsync();
-    }
-
-    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 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 Task BundleUpdateConnectorStatusDapper(IEnumerable<StatusNotificationParam> statusNotifications)
-    {
-        using var conn = sqlConnectionFactory.Create();
-
-        foreach (var status in statusNotifications)
-        {
-
-            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);
-
-            conn.Execute("""
-                update ConnectorStatus
-                set
-                CreatedOn = @CreatedOn,
-                Status = @Status,
-                ChargePointErrorCodeId = @ChargePointErrorCodeId,
-                ErrorInfo = @ErrorInfo,
-                VendorId = @VendorId,
-                VendorErrorCode = @VendorErrorCode
-                where Id = @Id
-                """, parameters);
-        }
-        return Task.CompletedTask;
-    }
-
-    private async Task BundleAddServerMessage(IEnumerable<ServerMessage> messages)
-    {
-        using var db = await contextFactory.CreateDbContextAsync();
-        using var trans = await db.Database.BeginTransactionAsync();
-
-        foreach (var message in messages)
-        {
-            await db.ServerMessage.AddAsync(message);
-        }
-
-        await db.SaveChangesAsync();
-        await trans.CommitAsync();
-        //db.ChangeTracker.Clear();
-    }
-
-    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 ConnectorId, MeterStop, MeterStart, 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> 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 record MachineAndCustomerInfo (string MachineId, Guid CustomerId, string CustomerName);
-public record StatusNotificationParam(string Id, ConnectorStatus Status);
-public record UpdateMachineBasicInfoParam(string ChargeBoxId, Machine machine);

+ 166 - 0
EVCB_OCPP.WSServer/Service/MapApiServce.cs

@@ -0,0 +1,166 @@
+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);
+
+        webApplication.MapGet("/blcn", (ProtalServer server) =>
+        {
+            int cnt = server.GetBootLockCnt();
+            return $"boot lock cnt: {cnt}";
+        });
+
+        webApplication.MapGet("/lpcn", (ProtalServer server) =>
+        {
+            Dictionary<string, WsClientData> clientDic = server.GetClientDic();
+            var acceptedCnt = clientDic.Values.Count(x => x.BootStatus == BootStatus.Accepted);
+            var pendingCnt = clientDic.Values.Count(x => x.BootStatus == BootStatus.Pending);
+            var initializingCnt = clientDic.Values.Count(x => x.BootStatus == BootStatus.Initializing);
+            var startupCnt = clientDic.Values.Count(x => x.BootStatus == BootStatus.Startup);
+            return $"IsCheckIn cnt: {startupCnt}/{initializingCnt}/{pendingCnt}/{acceptedCnt}";
+        });
+    }
+
+    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 - 164
EVCB_OCPP.WSServer/Service/MeterValueInsertHandler.cs

@@ -1,164 +1,166 @@
-using Dapper;
-using EVCB_OCPP.Domain;
-using EVCB_OCPP.WSServer.Helper;
-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 SqlConnectionFactory<MeterValueDBContext> sqlConnectionFactory;
-
-    //private readonly string meterValueConnectionString;
-
-    public MeterValueInsertHandler(
-        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory,
-        ILogger<MeterValueInsertHandler> logger,
-        SqlConnectionFactory<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}";
+}

+ 84 - 0
EVCB_OCPP.WSServer/Service/ServerMessageService.cs

@@ -0,0 +1,84 @@
+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;
+using EVCB_OCPP.Packet.Messages.RemoteTrigger;
+using EVCB_OCPP.Packet.Messages.SubTypes;
+
+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 = "", CancellationToken token = default)
+    {
+        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,
+            token: token
+            );
+    }
+
+    internal Task<string> SendChangeConfigurationRequest(string chargeBoxId, string key, string value, string serialNo = "", CancellationToken token = default)
+    {
+        return mainDbService.AddServerMessage(
+            ChargeBoxId: chargeBoxId,
+            OutAction: Actions.ChangeConfiguration.ToString(),
+            OutRequest: new ChangeConfigurationRequest()
+            {
+                key = key,
+                value = value
+            },
+            SerialNo: serialNo,
+            token: token
+            );
+    }
+
+    internal Task<string> SendDataTransferRequest(string chargeBoxId, string messageId, string vendorId, string data, string serialNo = "", CancellationToken token = default)
+    {
+        return mainDbService.AddServerMessage(
+            ChargeBoxId: chargeBoxId,
+            OutAction: Actions.DataTransfer.ToString(),
+            OutRequest: new DataTransferRequest()
+            {
+                messageId = messageId,
+                vendorId = vendorId,
+                data = data
+            },
+            SerialNo: serialNo,
+            token: token
+            );
+    }
+
+    internal Task<string> SendTriggerMessageRequest(string chargeBoxId, MessageTrigger messageTrigger, string serialNo = "", CancellationToken token = default)
+    {
+        return mainDbService.AddServerMessage(
+            ChargeBoxId: chargeBoxId,
+            OutAction: Actions.TriggerMessage.ToString(),
+            OutRequest: new TriggerMessageRequest()
+            {
+                requestedMessage = messageTrigger
+            },
+            SerialNo: serialNo,
+            token: token
+            );
+    }
+}

+ 254 - 0
EVCB_OCPP.WSServer/Service/StationConfigService.cs

@@ -0,0 +1,254 @@
+using EVCB_OCPP.Packet.Messages.Core;
+using EVCB_OCPP.Packet.Messages.SubTypes;
+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.CheckAndUpdateEvseConfigOnConnnected);
+        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 CheckAndUpdateEvseConfigOnConnnected(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);
+
+            var configs = session.Data.ContainsKey(GlobalConfig.BootData_EVSEConfig_Key) ? session.Data[GlobalConfig.BootData_EVSEConfig_Key] as List<KeyValue> : null;
+            await UpdateEvseConfig(chargeBoxId, stationId.Value, evseCurrentKeys: configs, token: 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, List<KeyValue> evseCurrentKeys = null, CancellationToken token = default)
+    {
+        Dictionary<string, string> dbConfigs = null;
+
+        if (!stationConfigRecord.ContainsKey(stationId))
+        {
+            logger.LogInformation("{chargeBoxId} doesnt has station config", chargeBoxId);
+            return;
+        }
+        dbConfigs = stationConfigRecord[stationId];
+
+        if (evseCurrentKeys == null)
+        {
+            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;
+            }
+            evseCurrentKeys = confirmation.configurationKey;
+        }
+
+        Dictionary<string, string> evseCurrentConfigs = new Dictionary<string, string>();
+        evseCurrentConfigs = evseCurrentKeys.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: 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.SendAndWaitResultAsync(sendTask, token: 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;
+    }
+}

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

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

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

@@ -0,0 +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;
+    }
+}

+ 168 - 0
EVCB_OCPP.WSServer/Service/WsService/WebsocketService.cs

@@ -0,0 +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);
+    }
+}

+ 159 - 0
EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs

@@ -0,0 +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;
+    }
+}

+ 179 - 0
EVCB_OCPP.WSServer/Service/WsService/WsSession.cs

@@ -0,0 +1,179 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using System.Net;
+using System.Net.WebSockets;
+using System.Text;
+
+namespace EVCB_OCPP.WSServer.Service.WsService;
+
+public class WsSession
+{
+    public WsSession(ILogger<WsSession> logger)
+    {
+        this.logger = logger;
+    }
+
+    public PathString? Path { get; set; }
+    public string UriScheme { get; set; }
+    public string SessionID { get; set; }
+    public IPEndPoint Endpoint { get; internal set; }
+    public DateTime LastActiveTime { get; set; }
+
+
+    private WebSocket _WebSocket;
+    public WebSocket ClientWebSocket
+    {
+        get => _WebSocket;
+        set
+        {
+            Init(value);
+        }
+    }
+
+    public WebSocketState State => ClientWebSocket.State;
+    public string SecWebSocketProtocol => ClientWebSocket.SubProtocol;
+
+    public SemaphoreSlim EndConnSemaphore { get; } = new SemaphoreSlim(0);
+
+    public CancellationToken DisconnetCancellationToken => disconnectCancellationTokenSource.Token;
+
+    //public event OCPPClientDataEventHandler<WsSession, String> m_ReceiveData;
+
+    public event EventHandler<string> SessionClosed;
+
+    private CancellationTokenSource disconnectCancellationTokenSource = new CancellationTokenSource();
+    private Task ReceiveLoopTask;
+    private readonly ILogger<WsSession> logger;
+
+    private void Init(WebSocket webSocket)
+    {
+        _WebSocket = webSocket;
+        LastActiveTime = DateTime.UtcNow;
+        ReceiveLoopTask = StartReceivd(webSocket, disconnectCancellationTokenSource.Token);
+    }
+
+    private async Task StartReceivd(WebSocket webSocket, CancellationToken token)
+    {
+        logger.LogInformation("{id} {func} {Path} Start", SessionID, nameof(StartReceivd), Path);
+
+        byte[] prevBuffer = new byte[0];
+        byte[] receivdBuffer = new byte[0];
+        int bufferExpand = 1;
+        int receivedBytes = 0;
+        while (!token.IsCancellationRequested)
+        {
+            var tempReceiveBuffer = new byte[1024 * 4];
+            WebSocketReceiveResult result = null;
+
+            try
+            {
+                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(tempReceiveBuffer), token);
+            }
+            catch (Exception e)
+            {
+                _ = BruteClose(e.Message);
+                break;
+            }
+            LastActiveTime = DateTime.UtcNow;
+
+            if (result == null || result.CloseStatus.HasValue)
+            {
+                //closed gracefully
+                await GracefulClose(result.CloseStatus.Value);
+                break;
+            }
+
+            prevBuffer = receivdBuffer;
+            receivdBuffer = new byte[1024 * 4 * bufferExpand];
+            Array.Copy(prevBuffer, 0, receivdBuffer, 0, receivedBytes);
+            Array.Copy(tempReceiveBuffer, 0, receivdBuffer, receivedBytes, result.Count);
+            receivedBytes += result.Count;
+
+            if (!result.EndOfMessage)
+            {
+                bufferExpand++;
+                continue;
+            }
+
+            var received = Encoding.UTF8.GetString(receivdBuffer, 0, receivedBytes);
+            //logger.LogInformation("{func}:{Path} {value}", nameof(StartReceivd), Path, received);
+
+            HandleReceivedData(received);
+
+            bufferExpand = 1;
+            receivedBytes = 0;
+        }
+    }
+
+    internal virtual void HandleReceivedData(string data)
+    {
+
+    }
+
+    internal Task Send(string dataString)
+    {
+        //logger.LogInformation("{func}:{Path} {value}", nameof(Send), Path, dataString);
+
+        var data = Encoding.UTF8.GetBytes(dataString);
+        return Send(data);
+    }
+
+    internal Task Close()
+    {
+        return ServerClose();
+    }
+
+    private async Task Send(byte[] data)
+    {
+        try
+        {
+            await ClientWebSocket.SendAsync(data, WebSocketMessageType.Text, endOfMessage: true, cancellationToken: disconnectCancellationTokenSource.Token);
+        }
+        catch (Exception e)
+        {
+            logger.LogInformation("{func} {Path} exception:{msg}", nameof(Send), Path, e.Message);
+        }
+    }
+
+    private Task ServerClose()
+    {
+        //logger.LogInformation("{func}:{Path}", nameof(ServerClose), Path);
+
+        SessionClosed?.Invoke(this, "ServerShutdown");
+        return InternalClose(WebSocketCloseStatus.NormalClosure, "ServerShutdown");
+    }
+
+    private Task GracefulClose(WebSocketCloseStatus closeStatus)
+    {
+        //logger.LogInformation("{func}:{Path} {value}", nameof(GracefulClose), Path, closeStatus);
+
+        SessionClosed?.Invoke(this, closeStatus.ToString());
+        return InternalClose(closeStatus, null);
+    }
+
+    private Task BruteClose(string description)
+    {
+        //logger.LogInformation("{func}:{Path} {value}", nameof(ServerClose), Path, description);
+
+        SessionClosed?.Invoke(this, description);
+        return InternalClose(WebSocketCloseStatus.EndpointUnavailable, description);
+    }
+
+    private async Task InternalClose(WebSocketCloseStatus closeStatus, string description)
+    {
+        try
+        {
+            await _WebSocket.CloseAsync(closeStatus, description, default);
+        }
+        catch
+        {
+        }
+        finally
+        {
+            _WebSocket.Dispose();
+        }
+
+        disconnectCancellationTokenSource.Cancel();
+        EndConnSemaphore.Release();
+    }
+}

+ 0 - 19
EVCB_OCPP.WSServer/SuperSocket.Command/ProcessCallCmd.cs

@@ -1,19 +0,0 @@
-using OCPPServer.Protocol;
-using SuperWebSocket.SubProtocol;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.SuperSocket.Command
-{
-    public class ProcessCallCmd : SubCommandBase<ClientData>
-    {
-        public override void ExecuteCommand(ClientData session, SubRequestInfo requestInfo)
-        {
-            session.ReceiveData(session, requestInfo.Body);
-        }
-
-        public override string Name
-        {
-            get { return "2"; }
-        }
-    }
-}

+ 0 - 19
EVCB_OCPP.WSServer/SuperSocket.Command/ProcessCallErrorCmd.cs

@@ -1,19 +0,0 @@
-using OCPPServer.Protocol;
-using SuperWebSocket.SubProtocol;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.SuperSocket.Command
-{
-    public class ProcessCallErrorCmd : SubCommandBase<ClientData>
-    {
-        public override void ExecuteCommand(ClientData session, SubRequestInfo requestInfo)
-        {
-            session.ReceiveData(session, requestInfo.Body);
-        }
-
-        public override string Name
-        {
-            get { return "4"; }
-        }
-    }
-}

+ 0 - 19
EVCB_OCPP.WSServer/SuperSocket.Command/ProcessCallResultCmd.cs

@@ -1,19 +0,0 @@
-using OCPPServer.Protocol;
-using SuperWebSocket.SubProtocol;
-using System.Threading.Tasks;
-
-namespace EVCB_OCPP.WSServer.SuperSocket.Command
-{
-    public class ProcessCallResultCmd : SubCommandBase<ClientData>
-    {
-        public override void ExecuteCommand(ClientData session, SubRequestInfo requestInfo)
-        {
-            session.ReceiveData(session, requestInfo.Body);
-        }
-
-        public override string Name
-        {
-            get { return "3"; }
-        }
-    }
-}

+ 0 - 128
EVCB_OCPP.WSServer/SuperSocket.Protocol/ClientData.cs

@@ -1,128 +0,0 @@
-
-using EVCB_OCPP.Packet.Messages.Basic;
-using EVCB_OCPP.WSServer.Dto;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Threading.Tasks;
-using SuperWebSocket;
-using SuperSocket.SocketBase;
-
-namespace OCPPServer.Protocol
-{
-    public class ClientData : WebSocketSession<ClientData>
-    { /// <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 bool IsPending { set; get; } = true;
-        public bool IsCheckIn { set; get; }
-
-        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; }
-
-        #region Billing
-
-        public Dictionary<string,string> UserPrices { set; get; }
-
-        public Dictionary<string, string> UserDisplayPrices { set; get; }
-
-        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 StationId { set; get; }
-
-
-
-        public delegate void OCPPClientDataEventHandler<ClientData, String>(ClientData clientdata, String msg);
-
-        public event OCPPClientDataEventHandler<ClientData, String> m_ReceiveData;
-
-        public ClientData()
-        {
-            IsAC = true;
-            IsCheckIn = false;
-            ChargeBoxId = SessionID;
-            MachineId = SessionID;
-            UserPrices = new Dictionary<string, string>();
-            UserDisplayPrices = new Dictionary<string, string>();
-        }
-
-
-
-        /// <summary>
-        /// Sends the raw binary data to client.
-        /// </summary>
-        /// <param name="data">The data.</param>
-        /// <param name="offset">The offset.</param>
-        /// <param name="length">The length.</param>
-        public void SendRawData(byte[] data, int offset, int length)
-        {
-            base.Send(data, offset, length);
-        }
-
-        //receive data event trigger
-        public void ReceiveData(ClientData clientdata, string msg)
-        {
-            if (m_ReceiveData != null)
-                m_ReceiveData(clientdata, msg);
-        }
-
-        /// <summary>
-        /// Called when [session closed].
-        /// </summary>
-        /// <param name="reason">The reason.</param>
-        protected override void OnSessionClosed(CloseReason reason)
-        {
-        }
-
-    }
-}

+ 0 - 8
EVCB_OCPP.WSServer/SuperSocket.Protocol/OCPPLog.cs

@@ -1,8 +0,0 @@
-
-
-using Microsoft.Extensions.Logging;
-using System;
-
-namespace OCPPServer.SubProtocol
-{
-}

+ 0 - 5
EVCB_OCPP.WSServer/SuperSocket.Protocol/OCPPLogFactory.cs

@@ -1,5 +0,0 @@
-using System.Collections.Generic;
-
-namespace OCPPServer.SubProtocol
-{
-}

+ 0 - 35
EVCB_OCPP.WSServer/SuperSocket.Protocol/OCPPSubCommandConverter.cs

@@ -1,35 +0,0 @@
-using SuperSocket.SocketBase.Protocol;
-using SuperWebSocket.SubProtocol;
-
-namespace OCPPServer.SubProtocol
-{
-    public class OCPPSubCommandParser : IRequestInfoParser<SubRequestInfo>
-    {
-        #region ISubProtocolCommandParser Members
-
-        /// <summary>
-        /// Parses the request info.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <returns></returns>
-        public SubRequestInfo ParseRequestInfo(string source)
-        {
-            var cmd = source.Trim();
-            int pos = cmd.IndexOf(',');
-            string name;
-
-            if (pos > 0)
-            {
-                name = cmd.Substring(pos - 1, 1);
-            }
-            else
-            {
-                name = "4";
-            }
-
-            return new SubRequestInfo(name, "", source);
-        }
-
-        #endregion ISubProtocolCommandParser Members
-    }
-}

+ 0 - 322
EVCB_OCPP.WSServer/SuperSocket.Protocol/OCPPSubProtocol.cs

@@ -1,322 +0,0 @@
-using Microsoft.Extensions.Logging;
-using OCPPServer.Protocol;
-using SuperSocket.Common;
-using SuperSocket.SocketBase;
-using SuperSocket.SocketBase.Protocol;
-using SuperWebSocket;
-using SuperWebSocket.Config;
-using SuperWebSocket.SubProtocol;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-
-namespace OCPPServer.SubProtocol
-{
-    public class OCPPSubProtocol : OCPPSubProtocol<ClientData>
-    {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol"/> class.
-        /// </summary>
-        public OCPPSubProtocol()
-            : base(Assembly.GetCallingAssembly())
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol"/> class.
-        /// </summary>
-        /// <param name="name">The sub protocol name.</param>
-        public OCPPSubProtocol(string name)
-            : base(name, Assembly.GetCallingAssembly())
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol"/> class.
-        /// </summary>
-        /// <param name="commandAssembly">The command assembly.</param>
-        public OCPPSubProtocol(Assembly commandAssembly)
-            : base(commandAssembly)
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="OCPPSubProtocol"/> class.
-        /// </summary>
-        /// <param name="commandAssemblies">The command assemblies.</param>
-        public OCPPSubProtocol(IEnumerable<Assembly> commandAssemblies)
-            : base(commandAssemblies)
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="OCPPSubProtocol"/> class.
-        /// </summary>
-        /// <param name="name">The sub protocol name.</param>
-        /// <param name="commandAssembly">The command assembly.</param>
-        public OCPPSubProtocol(string name, Assembly commandAssembly)
-            : base(name, commandAssembly)
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="OCPPSubProtocol"/> class.
-        /// </summary>
-        /// <param name="name">The sub protocol name.</param>
-        /// <param name="commandAssemblies">The command assemblies.</param>
-        public OCPPSubProtocol(string name, IEnumerable<Assembly> commandAssemblies)
-            : base(name, commandAssemblies)
-        {
-
-        }
-
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="OCPPSubProtocol"/> class.
-        /// </summary>
-        /// <param name="name">The name.</param>
-        /// <param name="commandAssemblies">The command assemblies.</param>
-        /// <param name="requestInfoParser">The request info parser.</param>
-        public OCPPSubProtocol(string name, IEnumerable<Assembly> commandAssemblies, IRequestInfoParser<SubRequestInfo> requestInfoParser)
-            : base(name, commandAssemblies, requestInfoParser)
-        {
-
-        }
-
-        public ILogger Getmlog()
-        {
-            return getlog();
-        }
-
-    }
-
-    public class OCPPSubProtocol<TWebSocketSession> : SubProtocolBase<TWebSocketSession>
-       where TWebSocketSession : WebSocketSession<TWebSocketSession>, new()
-    {
-        /// <summary>
-        /// Default basic sub protocol name
-        /// </summary>
-        public const string DefaultName = "ocpp1.6";//"OCPP1.6";
-
-        private List<Assembly> m_CommandAssemblies = new List<Assembly>();
-
-        private Dictionary<string, ISubCommand<TWebSocketSession>> m_CommandDict;
-
-        private ILogger m_Logger;
-
-        private SubCommandFilterAttribute[] m_GlobalFilters;
-
-        internal static BasicSubProtocol<TWebSocketSession> CreateDefaultSubProtocol()
-        {
-            var commandAssembly = typeof(TWebSocketSession).Assembly;
-
-            if (commandAssembly == Assembly.GetExecutingAssembly())
-                commandAssembly = Assembly.GetEntryAssembly();
-
-            return new BasicSubProtocol<TWebSocketSession>(DefaultName, commandAssembly);
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol&lt;TWebSocketSession&gt;"/> class with the calling aseembly as command assembly
-        /// </summary>
-        public OCPPSubProtocol()
-            : this(DefaultName, Assembly.GetCallingAssembly())
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol&lt;TWebSocketSession&gt;"/> class with the calling aseembly as command assembly
-        /// </summary>
-        /// <param name="name">The sub protocol name.</param>
-        public OCPPSubProtocol(string name)
-            : this(name, Assembly.GetCallingAssembly())
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol&lt;TWebSocketSession&gt;"/> class with command assemblies
-        /// </summary>
-        /// <param name="commandAssemblies">The command assemblies.</param>
-        public OCPPSubProtocol(IEnumerable<Assembly> commandAssemblies)
-            : this(DefaultName, commandAssemblies, new OCPPSubCommandParser())
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol&lt;TWebSocketSession&gt;"/> class with single command assembly.
-        /// </summary>
-        /// <param name="commandAssembly">The command assembly.</param>
-        public OCPPSubProtocol(Assembly commandAssembly)
-            : this(DefaultName, new List<Assembly> { commandAssembly }, new OCPPSubCommandParser())
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol&lt;TWebSocketSession&gt;"/> class with name and single command assembly.
-        /// </summary>
-        /// <param name="name">The sub protocol name.</param>
-        /// <param name="commandAssembly">The command assembly.</param>
-        public OCPPSubProtocol(string name, Assembly commandAssembly)
-            : this(name, new List<Assembly> { commandAssembly }, new OCPPSubCommandParser())
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol&lt;TWebSocketSession&gt;"/> class with name and command assemblies.
-        /// </summary>
-        /// <param name="name">The sub protocol name.</param>
-        /// <param name="commandAssemblies">The command assemblies.</param>
-        public OCPPSubProtocol(string name, IEnumerable<Assembly> commandAssemblies)
-            : this(name, commandAssemblies, new OCPPSubCommandParser())
-        {
-
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BasicSubProtocol&lt;TWebSocketSession&gt;"/> class.
-        /// </summary>
-        /// <param name="name">The name.</param>
-        /// <param name="commandAssemblies">The command assemblies.</param>
-        /// <param name="requestInfoParser">The request info parser.</param>
-        public OCPPSubProtocol(string name, IEnumerable<Assembly> commandAssemblies, IRequestInfoParser<SubRequestInfo> requestInfoParser)
-            : base(name)
-        {
-            //The items in commandAssemblies may be null, so filter here
-            m_CommandAssemblies.AddRange(commandAssemblies.Where(a => a != null));
-            SubRequestParser = requestInfoParser;
-        }
-
-
-
-        #region ISubProtocol Members
-
-        private void DiscoverCommands()
-        {
-            var subCommands = new List<ISubCommand<TWebSocketSession>>();
-
-            foreach (var assembly in m_CommandAssemblies)
-            {
-                subCommands.AddRange(assembly.GetImplementedObjectsByInterface<ISubCommand<TWebSocketSession>>());
-            }
-
-#if DEBUG
-            var cmdbuilder = new StringBuilder();
-            cmdbuilder.AppendLine(string.Format("SubProtocol {0} found the commands below:", this.Name));
-
-            foreach (var c in subCommands)
-            {
-                cmdbuilder.AppendLine(c.Name);
-            }
-
-
-            m_Logger.LogDebug(cmdbuilder.ToString());
-#endif
-
-            m_CommandDict = new Dictionary<string, ISubCommand<TWebSocketSession>>(subCommands.Count, StringComparer.OrdinalIgnoreCase);
-
-            subCommands.ForEach(c =>
-            {
-                var fc = c as ISubCommandFilterLoader;
-
-                if (fc != null)
-                    fc.LoadSubCommandFilters(m_GlobalFilters);
-
-                m_CommandDict.Add(c.Name, c);
-            }
-                );
-        }
-
-
-        private bool ResolveCommmandAssembly(string definition)
-        {
-            try
-            {
-                var assemblies = AssemblyUtil.GetAssembliesFromString(definition);
-
-                if (assemblies.Any())
-                    m_CommandAssemblies.AddRange(assemblies);
-
-                return true;
-            }
-            catch (Exception e)
-            {
-                m_Logger.LogError(e,e.Message);
-                return false;
-            }
-        }
-
-        public ILogger getlog()
-        {
-            return m_Logger;
-        }
-
-        /// <summary>
-        /// Tries get command from the sub protocol's command inventory.
-        /// </summary>
-        /// <param name="name">The name.</param>
-        /// <param name="command">The command.</param>
-        /// <returns></returns>
-        public override bool TryGetCommand(string name, out ISubCommand<TWebSocketSession> command)
-        {
-            return m_CommandDict.TryGetValue(name, out command);
-        }
-
-
-
-        public override bool Initialize(IAppServer appServer, SubProtocolConfig protocolConfig, ILogger logger)
-        {
-            m_Logger = logger;
-
-            var config = appServer.Config;
-
-            m_GlobalFilters = appServer.GetType()
-                    .GetCustomAttributes(true)
-                    .OfType<SubCommandFilterAttribute>()
-                    .Where(a => string.IsNullOrEmpty(a.SubProtocol) || Name.Equals(a.SubProtocol, StringComparison.OrdinalIgnoreCase)).ToArray();
-
-            if (Name.Equals(DefaultName, StringComparison.OrdinalIgnoreCase))
-            {
-                var commandAssembly = config.Options.GetValue("commandAssembly");
-
-                if (!string.IsNullOrEmpty(commandAssembly))
-                {
-                    if (!ResolveCommmandAssembly(commandAssembly))
-                        return false;
-                }
-            }
-
-            if (protocolConfig != null && protocolConfig.Commands != null)
-            {
-                foreach (var commandConfig in protocolConfig.Commands)
-                {
-                    var assembly = commandConfig.Options.GetValue("assembly");
-
-                    if (!string.IsNullOrEmpty(assembly))
-                    {
-                        if (!ResolveCommmandAssembly(assembly))
-                            return false;
-                    }
-                }
-            }
-
-            //Always discover commands
-            DiscoverCommands();
-
-            return true;
-        }
-
-        #endregion
-    }
-}

+ 0 - 251
EVCB_OCPP.WSServer/SuperSocket/OCPPWSServer.cs

@@ -1,251 +0,0 @@
-using EVCB_OCPP.WSServer.Service;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using OCPPServer.Protocol;
-using SuperSocket.SocketBase;
-using SuperWebSocket;
-using SuperWebSocket.SubProtocol;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Security;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
-
-namespace EVCB_OCPP.WSServer.SuperSocket;
-
-public class OCPPWSServer : WebSocketServer<ClientData>
-{
-
-    private readonly ILogger logger;
-    private readonly IConfiguration configuration;
-    private readonly IMainDbService mainDbService;
-
-    /// <summary>
-    /// 可允許連線Clinet數
-    /// </summary>
-    public int connectNum { get; set; }
-
-    /// <summary>
-    /// 是否限制連線Clinet數
-    /// </summary>
-    public bool beConnectLimit { get; set; }
-
-    /// <summary>
-    /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
-    /// </summary>
-    /// <param name="subProtocols">The sub protocols.</param>
-    public OCPPWSServer(
-        IConfiguration configuration,
-        IMainDbService mainDbService,
-        ILogger<OCPPWSServer> logger)
-        : base(new List<ISubProtocol<ClientData>>())
-    {
-        this.configuration = configuration;
-        this.mainDbService = mainDbService;
-        this.logger = logger;
-    }
-
-    /// <summary>
-    /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
-    /// </summary>
-    /// <param name="subProtocol">The sub protocol.</param>
-    //public OCPPWSServer(ISubProtocol<ClientData> subProtocol, IServiceProvider serviceProvider)
-    //    : base(subProtocol)
-    //{
-    //    this.configuration = serviceProvider.GetService<IConfiguration>();
-
-    //    logger = serviceProvider.GetService<ILogger<OCPPWSServer>>();
-    //}
-
-    /// <summary>
-    /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
-    /// </summary>
-    //public OCPPWSServer(IServiceProvider serviceProvider)
-    //    : base(new List<ISubProtocol<ClientData>>())
-    //{
-    //    this.configuration = serviceProvider.GetService<IConfiguration>();
-
-    //    logger = serviceProvider.GetService<ILogger<OCPPWSServer>>();
-    //}
-
-    protected override bool ValidateClientCertificate(ClientData session, object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
-    {
-        //  Console.WriteLine(string.Format("{0} :{1}", session.ChargeBoxId + " ValidateClientCertificate", sslPolicyErrors));
-        return true;
-        // return base.ValidateClientCertificate(session, sender, certificate, chain, sslPolicyErrors);
-    }
-
-    protected override bool ValidateHandshake(ClientData session, string origin)
-    {
-        session.ISOCPP20 = session.SecWebSocketProtocol.ToLower().Contains("ocpp2.0");
-
-        int securityProfile = 0;
-        string authorizationKey = string.Empty;
-        if (string.IsNullOrEmpty(session.Path))
-        {
-            //logger.Log();
-            logger.LogWarning("===========================================");
-            logger.LogWarning("session.Path EMPTY");
-            logger.LogWarning("===========================================");
-        }
-
-        string[] words = session.Path.Split('/');
-        session.ChargeBoxId = words.Last();
-
-        foreach (var denyModel in GlobalConfig.DenyModelNames)
-        {
-            if (string.IsNullOrEmpty(denyModel)) break;
-            if (session.ChargeBoxId.StartsWith(denyModel))
-            {
-
-                StringBuilder responseBuilder = new StringBuilder();
-
-                responseBuilder.AppendFormatWithCrCf(@"HTTP/{0} {1} {2}", "1.1",
-                (int)HttpStatusCode.Unauthorized, @"Unauthorized");
-
-                responseBuilder.AppendWithCrCf();
-                string sb = responseBuilder.ToString();
-                byte[] data = Encoding.UTF8.GetBytes(sb);
-
-                ((IWebSocketSession)session).SendRawData(data, 0, data.Length);
-                logger.LogTrace(sb);
-                return false;
-            }
-        }
-
-        if (configuration["MaintainMode"] == "1")
-        {
-            session.ChargeBoxId = session.ChargeBoxId + "_2";
-        }
-
-        logger.LogInformation(string.Format("ValidateHandshake: {0}", session.Path));
-        bool isExistedSN = false;
-        bool authorizated = false;
-
-        var info = mainDbService.GetMachineIdAndCustomerInfo(session.ChargeBoxId).Result;
-        //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)
-        {
-            StringBuilder responseBuilder = new StringBuilder();
-
-            responseBuilder.AppendFormatWithCrCf(@"HTTP/{0} {1} {2}", "1.1",
-            (int)HttpStatusCode.NotFound, @"Not Found");
-
-            responseBuilder.AppendWithCrCf();
-            string sb = responseBuilder.ToString();
-            byte[] data = Encoding.UTF8.GetBytes(sb);
-            ((IWebSocketSession)session).SendRawData(data, 0, data.Length);
-
-            logger.LogInformation(sb);
-            return false;
-        }
-
-        //var configVaule = db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.ConfigureName == StandardConfiguration.SecurityProfile)
-        //                  .Select(x => x.ConfigureSetting).FirstOrDefault();
-        var configVaule = mainDbService.GetMachineSecurityProfile(session.ChargeBoxId).Result;
-        int.TryParse(configVaule, out securityProfile);
-
-        if (session.ISOCPP20)
-        {
-            // 1.6 server only support change server  function
-            securityProfile = 0;
-        }
-
-        if (securityProfile == 3 && session.UriScheme == "ws")
-        {
-            StringBuilder responseBuilder = new StringBuilder();
-
-            responseBuilder.AppendFormatWithCrCf(@"HTTP/{0} {1} {2}", "1.1",
-            (int)HttpStatusCode.Unauthorized, @"Unauthorized");
-
-            responseBuilder.AppendWithCrCf();
-            string sb = responseBuilder.ToString();
-            byte[] data = Encoding.UTF8.GetBytes(sb);
-
-            ((IWebSocketSession)session).SendRawData(data, 0, data.Length);
-            logger.LogInformation(sb);
-            return false;
-        }
-
-        if (securityProfile == 1 || securityProfile == 2)
-        {
-            if (securityProfile == 2 && session.UriScheme == "ws")
-            {
-                authorizated = false;
-            }
-
-            if (session.Items.ContainsKey("Authorization") || session.Items.ContainsKey("authorization"))
-            {
-                //authorizationKey = db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.ConfigureName == StandardConfiguration.AuthorizationKey)
-                //                    .Select(x => x.ConfigureSetting).FirstOrDefault();
-                authorizationKey = mainDbService.GetMachineAuthorizationKey(session.ChargeBoxId).Result;
-
-                if (session.ISOCPP20)
-                {
-                    // 1.6 server only support change server  function
-                    securityProfile = 0;
-                }
-
-                logger.LogInformation("***********Authorization   ");
-
-                if (!string.IsNullOrEmpty(authorizationKey))
-                {
-                    string base64Encoded = session.Items.ContainsKey("Authorization") ? session.Items["Authorization"].ToString().Replace("Basic ", "") : session.Items["authorization"].ToString().Replace("Basic ", "");
-                    byte[] data = Convert.FromBase64String(base64Encoded);
-                    string[] base64Decoded = Encoding.ASCII.GetString(data).Split(':');
-                    logger.LogInformation("***********Authorization   " + Encoding.ASCII.GetString(data));
-                    if (base64Decoded.Count() == 2 && base64Decoded[0] == session.ChargeBoxId && base64Decoded[1] == authorizationKey)
-                    {
-                        authorizated = true;
-                    }
-                }
-
-
-
-
-
-            }
-            else
-            {
-                authorizated = true;
-
-            }
-
-
-
-            if (!authorizated)
-            {
-                StringBuilder responseBuilder = new StringBuilder();
-
-                responseBuilder.AppendFormatWithCrCf(@"HTTP/{0} {1} {2}", "1.1",
-                (int)HttpStatusCode.Unauthorized, @"Unauthorized");
-
-                responseBuilder.AppendWithCrCf();
-                string sb = responseBuilder.ToString();
-                byte[] data = Encoding.UTF8.GetBytes(sb);
-
-                ((IWebSocketSession)session).SendRawData(data, 0, data.Length);
-                logger.LogInformation(sb);
-                return false;
-            }
-        }
-
-
-
-
-
-        logger.LogInformation(string.Format("ValidateHandshake PASS: {0}", session.Path));
-        return true;
-    }
-}

+ 0 - 39
EVCB_OCPP.WSServer/SuperSocket/OCPPWSServerFactory.cs

@@ -1,39 +0,0 @@
-using EVCB_OCPP.WSServer.Service;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using OCPPServer.Protocol;
-using SuperWebSocket.SubProtocol;
-using System;
-using System.Collections.Generic;
-
-namespace EVCB_OCPP.WSServer.SuperSocket;
-
-public interface IOCPPWSServerFactory
-{
-    OCPPWSServer Create(IEnumerable<ISubProtocol<ClientData>> subProtocols);
-}
-
-public class OCPPWSServerFactory : IOCPPWSServerFactory
-{
-
-    public OCPPWSServerFactory(
-        IConfiguration configuration,
-        IMainDbService mainDbService,
-        ILoggerFactory loggerFactory,
-        IServiceProvider serviceProvider)
-    {
-        this.loggerFactory = loggerFactory;
-        this.serviceProvider = serviceProvider;
-    }
-    public OCPPWSServer Create(IEnumerable<ISubProtocol<ClientData>> subProtocols)
-    {
-        var logger = loggerFactory.CreateLogger<OCPPWSServer>();
-        var server = serviceProvider.GetRequiredService<OCPPWSServer>();
-        server.RegisterSubProtocol(subProtocols);
-        return server;
-    }
-
-    private readonly ILoggerFactory loggerFactory;
-    private readonly IServiceProvider serviceProvider;
-}

+ 151 - 161
EVCB_OCPP.WSServer/appsettings.json

@@ -1,162 +1,152 @@
-{
-  "WSPort": 54089,
-  "WSSPort": "",
-  "LocalAuthAPI": "",
-  "LogProvider": "NLog",
-  "OCPP20_WSUrl": "ws://ocpp.phihong.com.tw:5004",
-  "OCPP20_WSSUrl": "ws://ocpp.phihong.com.tw:5004",
-  "MaintainMode": 0,
-  "superSocket": {
-    "Servers": [
-      {
-        "Name": "SuperWebSocket",
-        "serverTypeName": "SuperWebSocket",
-        "Certificate": {
-          "filePath": "localhost.pfx",
-          "password": "supersocket",
-          "storeName": "My",
-          "thumbprint": "‎3f07fb28c158843209db8f51bfe748dbe9f52399",
-          "storeLocation": "LocalMachine",
-          "clientCertificateRequired": "false",
-          "keyStorageFlags": "Exportable"
-        }
-      }
-    ]
-  },
-  "SuperSocketServerCertificate": {
-    "filePath": "localhost.pfx",
-    "password": "supersocket",
-    "storeName": "My",
-    "thumbprint": "‎3f07fb28c158843209db8f51bfe748dbe9f52399",
-    "storeLocation": "LocalMachine",
-    "clientCertificateRequired": "false",
-    "keyStorageFlags": "Exportable"
-  },
-  "Logging": {
-    "LogLevel": {
-      "Default": "Information",
-      "Microsoft.AspNetCore": "Warning"
-    }
-  },
-  "NLog": {
-    "targets": {
-      "async": true,
-      "f": {
-        "type": "File",
-        "fileName": "/home/logs/server/${shortdate}.log",
-        "layout": "${longdate} ${uppercase:${level}} ${message}"
-      },
-      "ws": {
-        "type": "File",
-        "fileName": "/home/logs/server/WS_${shortdate}.log",
-        "layout": "${longdate} ${uppercase:${level}} ${message}"
-      },
-      "Console": {
-        "type": "Console",
-        "layout": "${longdate} ${uppercase:${level}} ${message}"
-      },
-      "auth": {
-        "type": "File",
-        "fileName": "/home/logs/server/Auth_${shortdate}.log",
-        "layout": "${longdate} ${callsite} ${uppercase:${level}} ${message}"
-      }
-    },
-    "rules": [
-      {
-        "ruleName": "OCPPServer",
-        "logger": "OCPPServer.*",
-        "minLevel": "Info",
-        "writeTo": "ws"
-      },
-      {
-        "ruleName": "SuperWebSocket",
-        "logger": "SuperWebSocket.*",
-        "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,
+  "MaxBootCnt": 55,
+  "BootReservCnt": 50,
+  "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}} ${callsite} ${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",
+        "final": true
+      },
+      {
+        "ruleName": "OuterBusinessService",
+        "logger": "EVCB_OCPP.WSServer.Service.OuterBusinessService",
+        "level": "Info",
+        "writeTo": "auth",
+        "final": true
+      },
+      {
+        "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;",
+    "OnlineRecordDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_OnlineRecord;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;"
+  }
 }

+ 39 - 0
LocalTest_Build.ps1

@@ -0,0 +1,39 @@
+# 設定 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
+
+$ssha = git rev-parse --short head
+
+#wite ssha to file
+$ssha | Out-File ssha
+
+podman build ./ -t  $fulltag --label [gitcommit=$ssha,author=$username]
+
+#remove ssha file
+Remove-Item ssha

+ 110 - 0
Prerelease_Build.ps1

@@ -0,0 +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 --tags -f
+$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
+}

+ 7 - 0
Prod_Build.bat

@@ -0,0 +1,7 @@
+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
+docker build ./ -t evdevcontainerregistry.azurecr.io/server:%version% --label "git-commit=%ssha%"
+docker push evdevcontainerregistry.azurecr.io/server:%version%

+ 10 - 0
Prod_Build_podman.bat

@@ -0,0 +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%
+podman push evdevcontainerregistry.azurecr.io/server:%version%

+ 101 - 0
Release_Build.ps1

@@ -0,0 +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 --tags -f
+$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>

+ 5 - 5
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:test
-docker push evdevcontainerregistry.azurecr.io/server:test
+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%

+ 1 - 0
entrypoint.sh

@@ -1,5 +1,6 @@
 #!/bin/bash
 set -e
+service cron start
 service ssh start
 #dotnet run --project ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
 #/app/publish/EVCB_OCPP.WSServer

+ 1 - 0
schedule

@@ -0,0 +1 @@
+0 18 * * 0 /home/JobScript/backup_logs.sh

+ 1 - 0
version.txt

@@ -0,0 +1 @@
+Docker_v1.1.39