151 Commits 5ae9f9b4eb ... c758e3587b

Auteur SHA1 Message Date
  shayne_lo c758e3587b fix query il y a 4 mois
  shayne_lo ba7b7ea95d update web db [StationMachine] Protocol version when EVSE connected il y a 4 mois
  shayne_lo 8f6f4ea6b5 update connection log for ipv6 record il y a 4 mois
  shayne_lo dbad17b181 fix mis spelling il y a 5 mois
  shayne_lo 50c0a0efbe add heat beat interval check to HealthCheckTriggerJob il y a 5 mois
  shayne_lo 84ea5eafac code clean il y a 5 mois
  shayne_lo 5b5e437336 update limited boot cnt il y a 5 mois
  shayne_lo 0ca6e12ce7 test pass 1 il y a 5 mois
  shayne_lo 5c19ce3457 add get diagnostic restrict il y a 5 mois
  shayne_lo e667c00de9 add dotnet react build il y a 5 mois
  shayne_lo 94931c5cee remove set machine connection type in initEVSE il y a 5 mois
  shayne_lo 9c064b328a fix billing with instant stop report missing info il y a 5 mois
  shayne_lo fd1832eaf8 add sn check for docker env il y a 6 mois
  shayne_lo 0868e9ac81 handle vendorId null il y a 6 mois
  shayne_lo 43ee5458ea try fix ErrorInfo not suplied il y a 6 mois
  shayne_lo 866a2c2677 set completed_session not print nu llparameter il y a 6 mois
  shayne_lo 116c893777 add handle if CallPartnerApiOnSchedule = 1 and InstantStopTxReport = 1 il y a 6 mois
  shayne_lo 97e50d1802 add isbilling stop transaction report time il y a 6 mois
  shayne_lo 93ef5bffc6 add InstantStopTxReport record il y a 6 mois
  shayne_lo 59dc023cc7 Isolate instant stop report from billing il y a 6 mois
  shayne_lo 0ddd9b6162 utlize BillingDone to mark transaction can be reported il y a 7 mois
  shayne_lo 573b59160f isolate period energy request il y a 7 mois
  shayne_lo bf3d5dcb3b fix IsBilling not turn off il y a 7 mois
  Robert a7fa809c90 update git ftech for tag update il y a 7 mois
  Robert f41983c07b remove try get message from db if not received il y a 7 mois
  Robert 0e5bf7ba79 1.fix Dictionary tryInsert error il y a 7 mois
  Jessica Tseng ece7eee5af 1. Modify customId when retrieving the configuration with the key "CentralChargeBoxId"/"ChargeBoxId" il y a 7 mois
  Jessica Tseng 929450d6a7 ****Update two DLLs**** il y a 7 mois
  Jessica Tseng 8e981d224f ****Update two DLLs**** il y a 7 mois
  Robert 1232011d87 remove ws and auth from normal log il y a 8 mois
  Robert 97be2ccbdd fix new line il y a 9 mois
  Robert 313ac107db fix release script il y a 9 mois
  Robert c254d0a45e 1. add new build scripts il y a 9 mois
  Robert 2914d55c2d dont respond transaction related request before is checkedin il y a 9 mois
  Robert 598eed2a4e fix prod podman build bat il y a 9 mois
  Robert d51a72dc13 fix GetEvseStation il y a 9 mois
  Robert c02d2c4c3e add podman build bat il y a 9 mois
  Robert fbe5001c23 increase StationMachine param ChargeBoxId length limit to 50 il y a 9 mois
  Robert f7034af926 fix vendor id empty handling il y a 9 mois
  Robert d292001923 optimize update MachineError FinishedTime il y a 9 mois
  Robert 37ba2df96f fix vendorId empty not handle correctly il y a 9 mois
  Robert 92b370a3e0 fix fill error status finish time il y a 9 mois
  Robert 43beee6b53 1. handle vendor id report empty il y a 9 mois
  Robert 4d118385bb 1. add filter for adding machine error to db il y a 9 mois
  Robert d20e3c7ac7 1. add back GetConfiguration value null handling il y a 10 mois
  Robert 8a22e6eeab get config for every connection il y a 10 mois
  Robert b12b046230 make sure rawScheme is in lower case il y a 10 mois
  Robert f345cb7c57 fix issue - station config not wait il y a 10 mois
  Robert 0db06ced49 update version il y a 10 mois
  Robert 761e4aece2 parallal update evse station config il y a 10 mois
  Robert a6825b38ab change OccurenceConstraintViolation description il y a 10 mois
  Robert 8d5c00f752 skip 3mins problem il y a 10 mois
  Robert cae7085dd3 simplfy header record il y a 10 mois
  Robert 6f811110be dont send if transactionId null il y a 10 mois
  Robert 5667d8047b add get scheme from X-Forwarded-Proto il y a 10 mois
  Robert b6599eab0e increase ValidateHandshake log level il y a 10 mois
  Robert 9233a36fe3 update Docker_v1.1.25 il y a 10 mois
  Robert 72a66f16fb set default db to test db il y a 10 mois
  Robert 392f8b344c update version Docker_v1.1.24 il y a 10 mois
  Robert 778c5d4706 fix url default setting il y a 10 mois
  Robert 696e8a6bb1 Merge branch 'Docker' into Docker_VID il y a 10 mois
  Robert 3435758060 remove hard coded port il y a 10 mois
  Jessica Tseng 26917898e8 1.Update MachineError schema from Domain Dll commitId:e90c45d5 il y a 10 mois
  Robert a3b5167b70 fix new config not saved il y a 10 mois
  Robert 0f15991956 test pass by four settings il y a 10 mois
  Robert 7e01976cce re enable StationConfigPollingJob schedule il y a 10 mois
  Robert 747742b77a code clean il y a 10 mois
  Robert 58663ef12a stress test pass il y a 10 mois
  Robert 8ca2932cd2 temp commit il y a 11 mois
  Robert 2aaec4c169 temp commit il y a 11 mois
  Robert 6f7106eeee temp il y a 11 mois
  Robert 78db9fbcd2 temp commit il y a 11 mois
  Robert d3bcb6d23e try optimize perfornance il y a 11 mois
  Robert b331f6d6f4 update auth param version il y a 11 mois
  Robert e9c17f227a temp commit il y a 11 mois
  Robert 03bcddfd69 temp commit il y a 11 mois
  Robert 730993d4e7 temp commit il y a 11 mois
  Robert af26e83dd2 remove public Station access, prevent unexpect change by other process il y a 11 mois
  Robert aefbfbbb24 Merge branch 'Docker' into Docker_msg il y a 11 mois
  Robert 4a5b568e95 test server pass il y a 11 mois
  Robert f5c320f2ab set PnC connectorId = -1 if preparing connector not found il y a 1 an
  Robert 198d90a0e3 fix KeepAliveInterval cause crash il y a 1 an
  Robert c999503090 change ping pon inteval to max il y a 1 an
  Robert d88dbeec54 master Commit 92ea5c42: il y a 1 an
  Robert eff9bdd0dc fix MachineConfigurations NULL Data il y a 1 an
  Robert f9b6050e71 fix logic might not send new station config il y a 1 an
  Robert 31303c1bef fix dependency logic il y a 1 an
  Robert 8cdf6557f8 Merge branch 'Docker' into Docker_msg il y a 1 an
  Robert d2b7e835c8 update CDFA url il y a 1 an
  Robert d38a9ad7e4 update version il y a 1 an
  Robert 103e527656 get original ip from X-Forwarded-For il y a 1 an
  Robert 803601bd32 test pass il y a 1 an
  Robert ac4ffe1d98 update version il y a 1 an
  Robert 6a1d861ec3 fix auth fail when 0 il y a 1 an
  Robert 9caaab534a stash il y a 1 an
  Robert 6786ac566d stash il y a 1 an
  Robert 0bcc2a8213 Merge branch 'Docker' into Docker_msg il y a 1 an
  Robert fc42b9a563 add connection log HourIndex il y a 1 an
  Robert c4a0f6a277 update version il y a 1 an
  Robert 6b6d27933a Add ReConfirmMessage catch exception il y a 1 an
  Robert c6112528f1 test pass il y a 1 an
  Robert d1ed77a08d clean code il y a 1 an
  Robert a2fc78db27 private test passed il y a 1 an
  Robert 3bb912dde9 add log fr AcceptWebSocketAsync il y a 1 an
  Robert 4f485ddeac fix direct connect scheme check error il y a 1 an
  Robert fd08be54a9 update to version Docker_v1.1.9 il y a 1 an
  Robert bebc2ac74f update version to Docker_v1.1.8 il y a 1 an
  Jessica Tseng d140590045 Follow master branch with commit 11d74e0a il y a 1 an
  Jessica Tseng 4fef2e8db0 Add Authorize log il y a 1 an
  Robert b1d71d1c59 client diconnect log cleanup il y a 1 an
  Robert 0e9a427747 sync EBUS change il y a 1 an
  Robert 29d881d8ea Merge branch 'Docker' of https://dev.azure.com/ZerovaSD/OCPP%20Backend/_git/EVCB_OCPP.OCPPServer.16J into Docker il y a 1 an
  Jessica Tseng 4e72944e5a Fix the issue where the backend responds with "ocpp2.0.1" when the charger claims to support two protocols. il y a 1 an
  Jessica Tseng 1fe9d723ff Ignore 98f0e1a391e466abbb6290c6eb0368608d489af7 il y a 1 an
  Jessica Tseng 37d1b8bc6e 1. Follow Domain.dll with commit =dd3d295a12faa338d7c870470eaa85ad5c39ad25 il y a 1 an
  Robert db69dba8ef fix reference il y a 1 an
  Robert 98f0e1a391 update version il y a 1 an
  Robert f913e51d61 fix main db error il y a 1 an
  Robert 82fb402265 update doman dll reference il y a 1 an
  Jessica Tseng 5324ed6c36 add main commit il y a 1 an
  Jessica Tseng 6951a8af06 Remove Superwebsocket dependency il y a 1 an
  Robert abcfe62abd update vesion il y a 1 an
  Robert 5d154fa43a 修正Server呼叫客戶充電完成紀錄API可能crash問題 il y a 1 an
  Robert 719d09c337 update version il y a 1 an
  Robert f119248b15 fix DiagnosticsStatusNotification il y a 1 an
  Robert 10c5866dfd update version il y a 1 an
  Robert e953c14804 fix not EndOfMessage not copy previous msg il y a 1 an
  Robert 4ab1c97082 chage base to asp.net original il y a 1 an
  Robert 0cc970fd41 update version il y a 1 an
  Robert 75138c36af fix dappfix error il y a 1 an
  Robert 124190df9c faster HeartBeatCheckTrigger il y a 1 an
  Robert 92dd361714 fix dapper select in il y a 1 an
  Robert ebc21206ef change console log to logger il y a 1 an
  Robert fb0f135236 simplify code il y a 1 an
  Robert ebe8915aa3 fix Iccid il y a 1 an
  Robert 4fd39d53f0 1. fix IsCheckIn il y a 1 an
  Robert 643b2503fe fix worn label il y a 1 an
  Robert 49b980ab74 optimize GroupHandler il y a 1 an
  Robert 77df1c4659 add main commit 798df193053386b10fdc3adc038b4e0953fa7875 il y a 1 an
  Robert ac296eebed add main commit e37c009b84cff595a796b7799ad87ab09ffd93c0 il y a 1 an
  Robert b8db5b723c add main commit 64e71ca0b667470aa06cc00293031cef7800ffd1 il y a 1 an
  Robert 4cb624f982 1. fix reconnect EVSE ghosted il y a 1 an
  Robert 76090ad0fe add main commit 44477 il y a 1 an
  Robert f5a8cdcfbf add main db commit 4b3798b9 il y a 1 an
  Robert 131750b37c add main commit abfcf96e il y a 1 an
  Robert 51a78b0dff add main commit 327fc il y a 1 an
  Robert 1a3c1784ac add main commit 81611 il y a 1 an
  Robert a598ae262a add main commit bba355 il y a 1 an
  Robert 90aaf88faa change ExecuteFirmwareManagementRequest async il y a 1 an
  Robert 94119e656b add main db commit 9bfa63004 il y a 1 an
  Robert df8046e0e3 add back mian db commit il y a 1 an
98 fichiers modifiés avec 13289 ajouts et 10516 suppressions
  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