From d00c45363a15a4bda23ba296695bb19d31264343 Mon Sep 17 00:00:00 2001 From: Xq Yang <2290299376@qq.com> Date: Thu, 20 Jun 2024 09:23:01 +0800 Subject: [PATCH] =?UTF-8?q?7=E5=8F=B7=E5=8E=82=E6=88=BF=E7=AB=8B=E5=BA=93?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E8=83=BD=E7=94=A8=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 + Tools/ApiTool/ApiTool.csproj | 14 + Tools/ApiTool/Dto/ApiResponseInfo.cs | 84 ++ Tools/ApiTool/WebApiPost.cs | 120 +++ Tools/CirculateTool/CirculateTool.csproj | 10 + Tools/CirculateTool/CirculationAttribute.cs | 30 + Tools/CirculateTool/StartCirculation.cs | 110 +++ Tools/DataCheck/CheckData.cs | 55 ++ Tools/DataCheck/DataCheck.csproj | 10 + Tools/DataCheck/DataRulesAttribute.cs | 18 + Tools/EncryptTool/EncryptTool.csproj | 9 + Tools/EncryptTool/Md5Encrypt.cs | 52 ++ Tools/HkCamera/Class1.cs | 6 + Tools/HkCamera/HkCamera.csproj | 10 + Tools/LedSimple/LEDColor.cs | 14 + Tools/LedSimple/LEDDLL.cs | 844 ++++++++++++++++++ Tools/LedSimple/LedSimple.csproj | 20 + Tools/LedSimple/lv_led_64.dll | Bin 0 -> 616448 bytes Tools/LogTool/LogTool.csproj | 10 + Tools/LogTool/WcsLog.cs | 104 +++ Tools/PlcTool/PlcTool.csproj | 15 + Tools/PlcTool/Siemens/DataConvert.cs | 72 ++ Tools/PlcTool/Siemens/Entity/PlcDBName.cs | 13 + Tools/PlcTool/Siemens/Entity/SemS7Result.cs | 32 + .../Siemens/Entity/SiemensS7Connection.cs | 19 + .../PLCAttribute/PlcDBAddressAttribute.cs | 7 + .../PLCAttribute/PlcDBNameAttribute.cs | 7 + .../Siemens/PLCAttribute/PlcIPAttribute.cs | 7 + .../Siemens/PLCAttribute/PlcIdAttribute.cs | 7 + .../Siemens/PLCAttribute/PlcKindAttribute.cs | 16 + .../Siemens/PLCAttribute/RackAttribute.cs | 7 + .../Siemens/PLCAttribute/SlotAttribute.cs | 7 + Tools/PlcTool/Siemens/SiemensS7.cs | 842 +++++++++++++++++ Tools/SocketTool/Entity/ExecuteResult.cs | 68 ++ Tools/SocketTool/Entity/ScanCodeClass.cs | 44 + Tools/SocketTool/Entity/SocketModel.cs | 41 + Tools/SocketTool/SocketClient.cs | 362 ++++++++ Tools/SocketTool/SocketTool.csproj | 10 + WcsMain/.config/dotnet-tools.json | 12 + .../DataEntity/WmsEntity/ApplyInRequest.cs | 28 + .../WmsEntity/SendWmsTaskStatusRequest.cs | 40 + .../WmsEntity/UploadPickStandRequest.cs | 27 + .../DataEntity/WmsEntity/WmsResponse.cs | 18 + .../WcsExceptionFilterAttribute.cs | 27 + .../ResponseFilterAttribute.cs | 118 +++ .../WmsApiExceptionFilterAttribute.cs | 31 + .../Dto/Equipment/ConveyStatusResponse.cs | 28 + .../Dto/Equipment/PickStandInfoResponse.cs | 22 + .../Dto/Equipment/StackerStatusResponse.cs | 40 + .../Equipment/QueryStandStatusRequest.cs | 13 + .../Equipment/QueryStandStatusResponse.cs | 25 + .../Dto/WMSEntity/WmsApiResponse.cs | 35 + .../WMSEntity/WmsTask/DisposeStandRequest.cs | 36 + .../WMSEntity/WmsTask/GetStackerRequest.cs | 70 ++ .../WMSEntity/WmsTask/SetPickTaskRequest.cs | 30 + .../WmsTask/UpdateStackerTaskStatusRequest.cs | 35 + .../Controllers/Dto/WcsApiResponse.cs | 57 ++ .../ApiAccept/GetApiAcceptWithPageRequest.cs | 41 + .../GetApiRequestWithPageRequest.cs | 40 + .../Dto/WcsDto/Config/EditeConfigRequest.cs | 48 + .../WcsDto/Config/GetConfigWithPageRequest.cs | 31 + .../CusPickTask/UpdatePickTaskRequest.cs | 34 + .../Dto/WcsDto/DB/EditeDBRequest.cs | 36 + .../Dto/WcsDto/DB/GetDBWithPlcNameRequest.cs | 26 + .../Dto/WcsDto/DB/GetDBWithPlcNameResponse.cs | 49 + .../Dto/WcsDto/ElTag/AddTaskInfoRequest.cs | 83 ++ .../Dto/WcsDto/ElTag/EditTaskInfoRequest.cs | 77 ++ .../GetStackerTaskNewDestinationRequest.cs | 31 + .../Dto/WcsDto/ElTag/QueryTaskRequest.cs | 53 ++ .../Dto/WcsDto/ElTag/ShowNumRequest.cs | 20 + .../WcsDto/Equipment/ResetStackerRequest.cs | 14 + .../Equipment/StackerContinueRequest.cs | 14 + .../Location/GetLocationWithPageRequest.cs | 54 ++ .../WcsDto/Location/UpdateLocationRequest.cs | 90 ++ .../Dto/WcsDto/Menu/AddMenuRequest.cs | 60 ++ .../Dto/WcsDto/Menu/GetMenuWithPageRequest.cs | 31 + .../Dto/WcsDto/Menu/UpdateMenuRequest.cs | 61 ++ .../Dto/WcsDto/PLC/EditePLCRequest.cs | 59 ++ .../WcsDto/Settings/EditSettingsRequest.cs | 40 + .../Dto/WcsDto/Socket/EditSocketRequest.cs | 44 + .../Dto/WcsDto/Stacker/EditStackerRequest.cs | 63 ++ .../Stacker/GetStackerStatusResponse.cs | 101 +++ .../GetSysMsgWithPageRequest.cs | 25 + .../Dto/WcsDto/SystemController/LogRequest.cs | 23 + .../Dto/WcsDto/User/GetUserWithPageRequest.cs | 45 + .../Dto/WcsDto/User/LoginRequest.cs | 24 + .../Dto/WcsDto/User/LoginResponse.cs | 87 ++ .../WcsDto/UserGroup/AddUserGroupRequest.cs | 24 + .../WcsDto/UserRule/UpdateUserRuleRequest.cs | 20 + .../WcsTask/GetWcsTaskWithPageRequest.cs | 53 ++ .../WcsTask/UpdateWcsTaskStatusRequest.cs | 31 + .../WmsTask/GetWmsTaskWithPageRequest.cs | 56 ++ .../Dto/WcsDto/WmsTask/SetWmsTask.cs | 71 ++ .../ApiServe/Controllers/TestController.cs | 72 ++ .../ThreeDController/TaskController.cs | 35 + .../WcsController/ApiAcceptController.cs | 24 + .../WcsController/ApiRequestController.cs | 32 + .../WcsController/ConfigController.cs | 41 + .../WcsController/ElTagController.cs | 72 ++ .../WcsController/EquipmentController.cs | 48 + .../WcsController/LocationController.cs | 48 + .../WcsController/MenuController.cs | 52 ++ .../WcsController/PlcController.cs | 50 ++ .../WcsController/PlcDbController.cs | 62 ++ .../WcsController/RunningInfoController.cs | 55 ++ .../WcsController/SettingController.cs | 39 + .../WcsController/SocketController.cs | 48 + .../WcsController/StackerController.cs | 49 + .../WcsController/UserController.cs | 44 + .../WcsController/UserGroupController.cs | 50 ++ .../WcsController/UserRuleController.cs | 39 + .../WcsController/WcsTaskController.cs | 61 ++ .../WcsController/WmsTaskController.cs | 60 ++ .../WmsController/EquipmentController.cs | 43 + .../WmsController/WmsTaskController.cs | 60 ++ .../ApiServe/Factory/WcsApiResponseFactory.cs | 170 ++++ .../ApiServe/Factory/WmsApiResponseFactory.cs | 123 +++ .../Service/TreeDService/TaskService.cs | 53 ++ .../Service/WcsService/ApiAcceptService.cs | 29 + .../Service/WcsService/ApiRequestService.cs | 43 + .../Service/WcsService/ConfigService.cs | 79 ++ .../Service/WcsService/ElTagService.cs | 89 ++ .../Service/WcsService/EquipmentService.cs | 60 ++ .../Service/WcsService/LocationService.cs | 72 ++ .../Service/WcsService/MenuService.cs | 77 ++ .../Service/WcsService/PlcDbService.cs | 101 +++ .../ApiServe/Service/WcsService/PlcService.cs | 84 ++ .../Service/WcsService/RunningInfoService.cs | 69 ++ .../Service/WcsService/SettingService.cs | 56 ++ .../Service/WcsService/SocketService.cs | 107 +++ .../Service/WcsService/StackerService.cs | 129 +++ .../Service/WcsService/UserGroupService.cs | 95 ++ .../Service/WcsService/UserRuleService.cs | 46 + .../Service/WcsService/UserService.cs | 107 +++ .../Service/WcsService/WcsTaskService.cs | 94 ++ .../Service/WcsService/WmsTaskService.cs | 137 +++ .../Service/WmsService/EquipmentService.cs | 37 + .../Service/WmsService/WmsTaskService.cs | 151 ++++ WcsMain/AppEntity/LED/LEDData.cs | 86 ++ .../AppEntity/SystemData/AppConfigEntity.cs | 117 +++ .../SystemData/AppSettingJsonEntity.cs | 54 ++ .../ConnectPlcWithCirculation.cs | 35 + .../CommonCirculation/DataClear.cs | 53 ++ .../CommonCirculation/HeartBeat.cs | 18 + .../CommonCirculation/LedShow.cs | 38 + .../CirculationTask/ElTag/LightElTag.cs | 97 ++ .../CirculationTask/ElTag/OffElTag.cs | 91 ++ .../Business/CirculationTask/ScanMethod.cs | 75 ++ .../CirculationTask/Stacker/CheckAccount.cs | 159 ++++ .../Stacker/ExeTaskDoubleFork.cs | 469 ++++++++++ .../CirculationTask/Stacker/ExecuteWcsTask.cs | 410 +++++++++ .../StackerConvey/CheckAccount.cs | 108 +++ .../StackerConvey/ExecuteScanMethod.cs | 32 + .../TaskData/ResolveWmsTask.cs | 278 ++++++ .../CirculationTask/TestCirculation.cs | 17 + WcsMain/Business/CommonAction/ClearData.cs | 104 +++ .../CommonAction/CusBindingVehicle.cs | 34 + WcsMain/Business/CommonAction/LEDUsing.cs | 152 ++++ .../Business/CommonAction/ScanCodeAction.cs | 29 + .../CommonAction/SendWmsTaskStatus.cs | 311 +++++++ .../CommonAction/WCSTaskExecuteEvent.cs | 147 +++ .../CommonAction/WMSApiResponseAction.cs | 52 ++ .../Business/CommonAction/WmsTaskAction.cs | 73 ++ WcsMain/Business/Convey/ConnectPlcServe.cs | 109 +++ .../DataHandler/BaseConveyDataHandler.cs | 136 +++ .../DataHandler/GetRouter/BaseGetRouter.cs | 30 + .../DataHandler/GetRouter/DeliverGetRouter.cs | 8 + .../DataHandler/GetRouter/LoginGetRouter.cs | 9 + .../DataHandler/GetRouter/PickGetRouter.cs | 8 + .../DataHandler/GetRouter/RecheckGetRouter.cs | 8 + .../GetRouter/ReplenishGetRouter.cs | 8 + WcsMain/Common/CommonData.cs | 49 + WcsMain/Common/CommonTool.cs | 76 ++ WcsMain/ConsoleLog.cs | 252 ++++++ WcsMain/DataBase/Dao/AppApiAcceptDao.cs | 105 +++ WcsMain/DataBase/Dao/AppApiRequestDao.cs | 111 +++ WcsMain/DataBase/Dao/AppConfigDao.cs | 114 +++ WcsMain/DataBase/Dao/AppDBDao.cs | 146 +++ WcsMain/DataBase/Dao/AppElTagBaseDao.cs | 94 ++ WcsMain/DataBase/Dao/AppElTagTaskDao.cs | 160 ++++ WcsMain/DataBase/Dao/AppLocationDao.cs | 207 +++++ WcsMain/DataBase/Dao/AppMenuDao.cs | 140 +++ WcsMain/DataBase/Dao/AppPLCDao.cs | 141 +++ WcsMain/DataBase/Dao/AppRouterMethodDao.cs | 39 + WcsMain/DataBase/Dao/AppSettingsDao.cs | 135 +++ WcsMain/DataBase/Dao/AppStackerDao.cs | 102 +++ WcsMain/DataBase/Dao/AppTcpDao.cs | 121 +++ WcsMain/DataBase/Dao/AppUserDao.cs | 86 ++ WcsMain/DataBase/Dao/AppUserGroupDao.cs | 69 ++ WcsMain/DataBase/Dao/AppUserRuleDao.cs | 68 ++ WcsMain/DataBase/Dao/AppVehicleBindingDao.cs | 93 ++ WcsMain/DataBase/Dao/AppWcsTaskDao.cs | 482 ++++++++++ WcsMain/DataBase/Dao/AppWmsTaskDao.cs | 496 ++++++++++ WcsMain/DataBase/Dao/BsPickStandDao.cs | 50 ++ WcsMain/DataBase/MixDao/TaskDao.cs | 198 ++++ WcsMain/DataBase/TableEntity/AppApiAccept.cs | 111 +++ WcsMain/DataBase/TableEntity/AppApiRequest.cs | 84 ++ WcsMain/DataBase/TableEntity/AppConfig.cs | 49 + WcsMain/DataBase/TableEntity/AppDB.cs | 51 ++ WcsMain/DataBase/TableEntity/AppElTagBase.cs | 70 ++ WcsMain/DataBase/TableEntity/AppElTagTask.cs | 131 +++ WcsMain/DataBase/TableEntity/AppLocation.cs | 162 ++++ WcsMain/DataBase/TableEntity/AppMenu.cs | 75 ++ WcsMain/DataBase/TableEntity/AppPLC.cs | 79 ++ .../DataBase/TableEntity/AppRouterMethod.cs | 31 + WcsMain/DataBase/TableEntity/AppSettings.cs | 49 + WcsMain/DataBase/TableEntity/AppStacker.cs | 71 ++ WcsMain/DataBase/TableEntity/AppTcp.cs | 62 ++ WcsMain/DataBase/TableEntity/AppUser.cs | 60 ++ WcsMain/DataBase/TableEntity/AppUserGroup.cs | 30 + WcsMain/DataBase/TableEntity/AppUserRule.cs | 23 + .../DataBase/TableEntity/AppVehicleBinding.cs | 30 + WcsMain/DataBase/TableEntity/AppWcsTask.cs | 167 ++++ WcsMain/DataBase/TableEntity/AppWmsTask.cs | 128 +++ WcsMain/DataBase/TableEntity/BsPickStand.cs | 30 + WcsMain/DataService/DataBaseData.cs | 171 ++++ WcsMain/DataService/EnumData.cs | 20 + .../ElTag/Atop/AtopEnum/AtopSubCommandEnum.cs | 11 + WcsMain/ElTag/Atop/AtopEnum/BuzzerType.cs | 14 + WcsMain/ElTag/Atop/AtopEnum/LedColor.cs | 14 + WcsMain/ElTag/Atop/AtopEnum/LedStatus.cs | 14 + WcsMain/ElTag/Atop/AtopEnum/TagButtons.cs | 11 + WcsMain/ElTag/Atop/AtopEnum/TagMode.cs | 10 + WcsMain/ElTag/Atop/BaseOprDataHandler.cs | 109 +++ WcsMain/ElTag/Atop/ConnectOprServe.cs | 196 ++++ WcsMain/ElTag/Atop/Entity/TagReturnInfo.cs | 64 ++ WcsMain/ElTag/Atop/Entity/TagSendInfo.cs | 228 +++++ WcsMain/ElTag/Atop/OprTcpClient.cs | 16 + WcsMain/Enum/ApiServer/ApiResponseCodeEnum.cs | 18 + WcsMain/Enum/FucResultEnum.cs | 11 + WcsMain/Enum/General/TrueFalseEnum.cs | 10 + WcsMain/Enum/Location/LocationStatusEnum.cs | 13 + WcsMain/Enum/Plc/PickTaskRouterEnum.cs | 11 + WcsMain/Enum/Plc/StackerConveyModelEnum.cs | 6 + WcsMain/Enum/Plc/StackerConveyStatusEnum.cs | 6 + WcsMain/Enum/SendWmsTaskStatusEnum.cs | 20 + .../Enum/Stacker/StackerControlModeEnum.cs | 17 + WcsMain/Enum/Stacker/StackerStatusEnum.cs | 22 + WcsMain/Enum/Stacker/StackerUseStatusEnum.cs | 11 + WcsMain/Enum/StandTypeEnum.cs | 15 + WcsMain/Enum/TaskEnum/ElTagTaskStatusEnum.cs | 15 + WcsMain/Enum/TaskEnum/ElTagTaskTypeEnum.cs | 10 + WcsMain/Enum/TaskEnum/TaskCategoryEnum.cs | 12 + WcsMain/Enum/TaskEnum/TaskTypeEnum.cs | 15 + WcsMain/Enum/TaskEnum/WcsTaskStatusEnum.cs | 16 + WcsMain/Enum/TaskEnum/WcsTaskTypeEnum.cs | 13 + WcsMain/Enum/TaskEnum/WmsTaskStatusEnum.cs | 13 + WcsMain/Enum/TaskEnum/WmsTaskTypeEnum.cs | 13 + WcsMain/Enum/TaskFeedBackTypeEnum.cs | 13 + WcsMain/Enum/Tcp/TcpType.cs | 12 + WcsMain/Enum/UserStatusEnum.cs | 10 + WcsMain/EquipOperation/ConnectPLCs.cs | 54 ++ .../EquipOperation/Convey/ConveyOperation.cs | 267 ++++++ WcsMain/EquipOperation/ElTag/AtopOperation.cs | 102 +++ .../EquipOperation/Entity/ConveyPLCTask.cs | 23 + .../Entity/Stacker/StackerInfo.cs | 118 +++ .../Entity/Stacker/StackerPlcTask.cs | 145 +++ .../Entity/StackerConvey/StackerConveyInfo.cs | 76 ++ .../StackerConvey/StackerConveyPlcTask.cs | 97 ++ .../Entity/StackerFeedbackStatus.cs | 15 + .../Entity/TaskFeedBackEntity.cs | 27 + .../Stacker/StackerOperation.cs | 304 +++++++ .../StackerConvey/StackerConveyOperation.cs | 277 ++++++ .../ExtendMethod/AppLocationExtendMethod.cs | 49 + .../ExtendMethod/AppStackerExtendMethod.cs | 22 + .../ExtendMethod/AppWcsTaskExtendMethod.cs | 145 +++ WcsMain/ExtendMethod/StackerExtendMethod.cs | 53 ++ WcsMain/ExtendMethod/StringExtendMethod.cs | 83 ++ WcsMain/ExtendMethod/TaskExtendMethod.cs | 25 + WcsMain/Language/Readme.txt | 1 + WcsMain/Plugins/WcsCirculation.cs | 54 ++ WcsMain/Plugins/WmsWebApiPost.cs | 19 + WcsMain/Program.cs | 65 ++ .../PublishProfiles/FolderProfile.pubxml | 23 + .../PublishProfiles/FolderProfile.pubxml.user | 11 + WcsMain/Properties/launchSettings.json | 31 + WcsMain/Socket/SocketOperation.cs | 76 ++ WcsMain/StartAction/AutofacModule.cs | 31 + WcsMain/StartAction/AutowiredSelector.cs | 13 + WcsMain/StartAction/HostService.cs | 76 ++ WcsMain/StartAction/LoadingRunningData.cs | 116 +++ WcsMain/StartAction/ServiceStart.cs | 216 +++++ WcsMain/StaticData/StaticString.cs | 13 + WcsMain/Tcp/Client/BaseTcpClient.cs | 314 +++++++ WcsMain/Tcp/Client/PlcTcpClient.cs | 101 +++ WcsMain/Tcp/Entity/Convey/GetRouterData.cs | 63 ++ WcsMain/Tcp/Entity/Convey/SetRouterData.cs | 78 ++ WcsMain/Tcp/Entity/Message/MsgHeader.cs | 29 + WcsMain/Tcp/Entity/Message/MsgInfo.cs | 35 + WcsMain/Tcp/Entity/PlcTcpClientSendResult.cs | 16 + WcsMain/Tcp/Entity/TcpClientSendResult.cs | 19 + WcsMain/Tcp/Entity/TcpServeConnectionData.cs | 49 + .../AppConfig/ConfigKeyAttribute.cs | 16 + .../AutoFacAttribute/AutowiredAttribute.cs | 10 + .../AutoFacAttribute/ComponentAttribute.cs | 7 + .../AutoFacAttribute/ServiceAttribute.cs | 10 + WcsMain/WcsMain.csproj | 56 ++ WcsMain/WcsMain.csproj.user | 8 + WcsMain/WebSocket/WebSocketOperation.cs | 63 ++ WcsMain/appsettings.Development.json | 8 + WcsMain/appsettings.json | 23 + WcsService.sln | 176 ++++ 302 files changed, 21817 insertions(+) create mode 100644 .gitignore create mode 100644 Tools/ApiTool/ApiTool.csproj create mode 100644 Tools/ApiTool/Dto/ApiResponseInfo.cs create mode 100644 Tools/ApiTool/WebApiPost.cs create mode 100644 Tools/CirculateTool/CirculateTool.csproj create mode 100644 Tools/CirculateTool/CirculationAttribute.cs create mode 100644 Tools/CirculateTool/StartCirculation.cs create mode 100644 Tools/DataCheck/CheckData.cs create mode 100644 Tools/DataCheck/DataCheck.csproj create mode 100644 Tools/DataCheck/DataRulesAttribute.cs create mode 100644 Tools/EncryptTool/EncryptTool.csproj create mode 100644 Tools/EncryptTool/Md5Encrypt.cs create mode 100644 Tools/HkCamera/Class1.cs create mode 100644 Tools/HkCamera/HkCamera.csproj create mode 100644 Tools/LedSimple/LEDColor.cs create mode 100644 Tools/LedSimple/LEDDLL.cs create mode 100644 Tools/LedSimple/LedSimple.csproj create mode 100644 Tools/LedSimple/lv_led_64.dll create mode 100644 Tools/LogTool/LogTool.csproj create mode 100644 Tools/LogTool/WcsLog.cs create mode 100644 Tools/PlcTool/PlcTool.csproj create mode 100644 Tools/PlcTool/Siemens/DataConvert.cs create mode 100644 Tools/PlcTool/Siemens/Entity/PlcDBName.cs create mode 100644 Tools/PlcTool/Siemens/Entity/SemS7Result.cs create mode 100644 Tools/PlcTool/Siemens/Entity/SiemensS7Connection.cs create mode 100644 Tools/PlcTool/Siemens/PLCAttribute/PlcDBAddressAttribute.cs create mode 100644 Tools/PlcTool/Siemens/PLCAttribute/PlcDBNameAttribute.cs create mode 100644 Tools/PlcTool/Siemens/PLCAttribute/PlcIPAttribute.cs create mode 100644 Tools/PlcTool/Siemens/PLCAttribute/PlcIdAttribute.cs create mode 100644 Tools/PlcTool/Siemens/PLCAttribute/PlcKindAttribute.cs create mode 100644 Tools/PlcTool/Siemens/PLCAttribute/RackAttribute.cs create mode 100644 Tools/PlcTool/Siemens/PLCAttribute/SlotAttribute.cs create mode 100644 Tools/PlcTool/Siemens/SiemensS7.cs create mode 100644 Tools/SocketTool/Entity/ExecuteResult.cs create mode 100644 Tools/SocketTool/Entity/ScanCodeClass.cs create mode 100644 Tools/SocketTool/Entity/SocketModel.cs create mode 100644 Tools/SocketTool/SocketClient.cs create mode 100644 Tools/SocketTool/SocketTool.csproj create mode 100644 WcsMain/.config/dotnet-tools.json create mode 100644 WcsMain/ApiClient/DataEntity/WmsEntity/ApplyInRequest.cs create mode 100644 WcsMain/ApiClient/DataEntity/WmsEntity/SendWmsTaskStatusRequest.cs create mode 100644 WcsMain/ApiClient/DataEntity/WmsEntity/UploadPickStandRequest.cs create mode 100644 WcsMain/ApiClient/DataEntity/WmsEntity/WmsResponse.cs create mode 100644 WcsMain/ApiServe/ControllerFilter/ExceptionFilter/WcsExceptionFilterAttribute.cs create mode 100644 WcsMain/ApiServe/ControllerFilter/ResponseFilterAttribute.cs create mode 100644 WcsMain/ApiServe/ControllerFilter/WmsApiExceptionFilterAttribute.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/Equipment/ConveyStatusResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/Equipment/PickStandInfoResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/Equipment/StackerStatusResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WMSEntity/Equipment/QueryStandStatusRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WMSEntity/Equipment/QueryStandStatusResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsApiResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/DisposeStandRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/GetStackerRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/SetPickTaskRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/UpdateStackerTaskStatusRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsApiResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/ApiAccept/GetApiAcceptWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/ApiRequest/GetApiRequestWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Config/EditeConfigRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Config/GetConfigWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/CusPickTask/UpdatePickTaskRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/EditeDBRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/GetDBWithPlcNameRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/GetDBWithPlcNameResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/AddTaskInfoRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/EditTaskInfoRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/GetStackerTaskNewDestinationRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/QueryTaskRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/ShowNumRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Equipment/ResetStackerRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Equipment/StackerContinueRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Location/GetLocationWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Location/UpdateLocationRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/AddMenuRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/GetMenuWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/UpdateMenuRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/PLC/EditePLCRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Settings/EditSettingsRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Socket/EditSocketRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Stacker/EditStackerRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/Stacker/GetStackerStatusResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/SystemController/GetSysMsgWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/SystemController/LogRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/User/GetUserWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/User/LoginRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/User/LoginResponse.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/UserGroup/AddUserGroupRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/UserRule/UpdateUserRuleRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/WcsTask/GetWcsTaskWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/WcsTask/UpdateWcsTaskStatusRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/WmsTask/GetWmsTaskWithPageRequest.cs create mode 100644 WcsMain/ApiServe/Controllers/Dto/WcsDto/WmsTask/SetWmsTask.cs create mode 100644 WcsMain/ApiServe/Controllers/TestController.cs create mode 100644 WcsMain/ApiServe/Controllers/ThreeDController/TaskController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/ApiAcceptController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/ApiRequestController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/ConfigController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/ElTagController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/EquipmentController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/LocationController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/MenuController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/PlcController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/PlcDbController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/RunningInfoController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/SettingController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/SocketController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/StackerController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/UserController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/UserGroupController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/UserRuleController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/WcsTaskController.cs create mode 100644 WcsMain/ApiServe/Controllers/WcsController/WmsTaskController.cs create mode 100644 WcsMain/ApiServe/Controllers/WmsController/EquipmentController.cs create mode 100644 WcsMain/ApiServe/Controllers/WmsController/WmsTaskController.cs create mode 100644 WcsMain/ApiServe/Factory/WcsApiResponseFactory.cs create mode 100644 WcsMain/ApiServe/Factory/WmsApiResponseFactory.cs create mode 100644 WcsMain/ApiServe/Service/TreeDService/TaskService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/ApiAcceptService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/ApiRequestService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/ConfigService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/ElTagService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/EquipmentService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/LocationService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/MenuService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/PlcDbService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/PlcService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/RunningInfoService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/SettingService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/SocketService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/StackerService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/UserGroupService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/UserRuleService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/UserService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/WcsTaskService.cs create mode 100644 WcsMain/ApiServe/Service/WcsService/WmsTaskService.cs create mode 100644 WcsMain/ApiServe/Service/WmsService/EquipmentService.cs create mode 100644 WcsMain/ApiServe/Service/WmsService/WmsTaskService.cs create mode 100644 WcsMain/AppEntity/LED/LEDData.cs create mode 100644 WcsMain/AppEntity/SystemData/AppConfigEntity.cs create mode 100644 WcsMain/AppEntity/SystemData/AppSettingJsonEntity.cs create mode 100644 WcsMain/Business/CirculationTask/CommonCirculation/ConnectPlcWithCirculation.cs create mode 100644 WcsMain/Business/CirculationTask/CommonCirculation/DataClear.cs create mode 100644 WcsMain/Business/CirculationTask/CommonCirculation/HeartBeat.cs create mode 100644 WcsMain/Business/CirculationTask/CommonCirculation/LedShow.cs create mode 100644 WcsMain/Business/CirculationTask/ElTag/LightElTag.cs create mode 100644 WcsMain/Business/CirculationTask/ElTag/OffElTag.cs create mode 100644 WcsMain/Business/CirculationTask/ScanMethod.cs create mode 100644 WcsMain/Business/CirculationTask/Stacker/CheckAccount.cs create mode 100644 WcsMain/Business/CirculationTask/Stacker/ExeTaskDoubleFork.cs create mode 100644 WcsMain/Business/CirculationTask/Stacker/ExecuteWcsTask.cs create mode 100644 WcsMain/Business/CirculationTask/StackerConvey/CheckAccount.cs create mode 100644 WcsMain/Business/CirculationTask/StackerConvey/ExecuteScanMethod.cs create mode 100644 WcsMain/Business/CirculationTask/TaskData/ResolveWmsTask.cs create mode 100644 WcsMain/Business/CirculationTask/TestCirculation.cs create mode 100644 WcsMain/Business/CommonAction/ClearData.cs create mode 100644 WcsMain/Business/CommonAction/CusBindingVehicle.cs create mode 100644 WcsMain/Business/CommonAction/LEDUsing.cs create mode 100644 WcsMain/Business/CommonAction/ScanCodeAction.cs create mode 100644 WcsMain/Business/CommonAction/SendWmsTaskStatus.cs create mode 100644 WcsMain/Business/CommonAction/WCSTaskExecuteEvent.cs create mode 100644 WcsMain/Business/CommonAction/WMSApiResponseAction.cs create mode 100644 WcsMain/Business/CommonAction/WmsTaskAction.cs create mode 100644 WcsMain/Business/Convey/ConnectPlcServe.cs create mode 100644 WcsMain/Business/Convey/DataHandler/BaseConveyDataHandler.cs create mode 100644 WcsMain/Business/Convey/DataHandler/GetRouter/BaseGetRouter.cs create mode 100644 WcsMain/Business/Convey/DataHandler/GetRouter/DeliverGetRouter.cs create mode 100644 WcsMain/Business/Convey/DataHandler/GetRouter/LoginGetRouter.cs create mode 100644 WcsMain/Business/Convey/DataHandler/GetRouter/PickGetRouter.cs create mode 100644 WcsMain/Business/Convey/DataHandler/GetRouter/RecheckGetRouter.cs create mode 100644 WcsMain/Business/Convey/DataHandler/GetRouter/ReplenishGetRouter.cs create mode 100644 WcsMain/Common/CommonData.cs create mode 100644 WcsMain/Common/CommonTool.cs create mode 100644 WcsMain/ConsoleLog.cs create mode 100644 WcsMain/DataBase/Dao/AppApiAcceptDao.cs create mode 100644 WcsMain/DataBase/Dao/AppApiRequestDao.cs create mode 100644 WcsMain/DataBase/Dao/AppConfigDao.cs create mode 100644 WcsMain/DataBase/Dao/AppDBDao.cs create mode 100644 WcsMain/DataBase/Dao/AppElTagBaseDao.cs create mode 100644 WcsMain/DataBase/Dao/AppElTagTaskDao.cs create mode 100644 WcsMain/DataBase/Dao/AppLocationDao.cs create mode 100644 WcsMain/DataBase/Dao/AppMenuDao.cs create mode 100644 WcsMain/DataBase/Dao/AppPLCDao.cs create mode 100644 WcsMain/DataBase/Dao/AppRouterMethodDao.cs create mode 100644 WcsMain/DataBase/Dao/AppSettingsDao.cs create mode 100644 WcsMain/DataBase/Dao/AppStackerDao.cs create mode 100644 WcsMain/DataBase/Dao/AppTcpDao.cs create mode 100644 WcsMain/DataBase/Dao/AppUserDao.cs create mode 100644 WcsMain/DataBase/Dao/AppUserGroupDao.cs create mode 100644 WcsMain/DataBase/Dao/AppUserRuleDao.cs create mode 100644 WcsMain/DataBase/Dao/AppVehicleBindingDao.cs create mode 100644 WcsMain/DataBase/Dao/AppWcsTaskDao.cs create mode 100644 WcsMain/DataBase/Dao/AppWmsTaskDao.cs create mode 100644 WcsMain/DataBase/Dao/BsPickStandDao.cs create mode 100644 WcsMain/DataBase/MixDao/TaskDao.cs create mode 100644 WcsMain/DataBase/TableEntity/AppApiAccept.cs create mode 100644 WcsMain/DataBase/TableEntity/AppApiRequest.cs create mode 100644 WcsMain/DataBase/TableEntity/AppConfig.cs create mode 100644 WcsMain/DataBase/TableEntity/AppDB.cs create mode 100644 WcsMain/DataBase/TableEntity/AppElTagBase.cs create mode 100644 WcsMain/DataBase/TableEntity/AppElTagTask.cs create mode 100644 WcsMain/DataBase/TableEntity/AppLocation.cs create mode 100644 WcsMain/DataBase/TableEntity/AppMenu.cs create mode 100644 WcsMain/DataBase/TableEntity/AppPLC.cs create mode 100644 WcsMain/DataBase/TableEntity/AppRouterMethod.cs create mode 100644 WcsMain/DataBase/TableEntity/AppSettings.cs create mode 100644 WcsMain/DataBase/TableEntity/AppStacker.cs create mode 100644 WcsMain/DataBase/TableEntity/AppTcp.cs create mode 100644 WcsMain/DataBase/TableEntity/AppUser.cs create mode 100644 WcsMain/DataBase/TableEntity/AppUserGroup.cs create mode 100644 WcsMain/DataBase/TableEntity/AppUserRule.cs create mode 100644 WcsMain/DataBase/TableEntity/AppVehicleBinding.cs create mode 100644 WcsMain/DataBase/TableEntity/AppWcsTask.cs create mode 100644 WcsMain/DataBase/TableEntity/AppWmsTask.cs create mode 100644 WcsMain/DataBase/TableEntity/BsPickStand.cs create mode 100644 WcsMain/DataService/DataBaseData.cs create mode 100644 WcsMain/DataService/EnumData.cs create mode 100644 WcsMain/ElTag/Atop/AtopEnum/AtopSubCommandEnum.cs create mode 100644 WcsMain/ElTag/Atop/AtopEnum/BuzzerType.cs create mode 100644 WcsMain/ElTag/Atop/AtopEnum/LedColor.cs create mode 100644 WcsMain/ElTag/Atop/AtopEnum/LedStatus.cs create mode 100644 WcsMain/ElTag/Atop/AtopEnum/TagButtons.cs create mode 100644 WcsMain/ElTag/Atop/AtopEnum/TagMode.cs create mode 100644 WcsMain/ElTag/Atop/BaseOprDataHandler.cs create mode 100644 WcsMain/ElTag/Atop/ConnectOprServe.cs create mode 100644 WcsMain/ElTag/Atop/Entity/TagReturnInfo.cs create mode 100644 WcsMain/ElTag/Atop/Entity/TagSendInfo.cs create mode 100644 WcsMain/ElTag/Atop/OprTcpClient.cs create mode 100644 WcsMain/Enum/ApiServer/ApiResponseCodeEnum.cs create mode 100644 WcsMain/Enum/FucResultEnum.cs create mode 100644 WcsMain/Enum/General/TrueFalseEnum.cs create mode 100644 WcsMain/Enum/Location/LocationStatusEnum.cs create mode 100644 WcsMain/Enum/Plc/PickTaskRouterEnum.cs create mode 100644 WcsMain/Enum/Plc/StackerConveyModelEnum.cs create mode 100644 WcsMain/Enum/Plc/StackerConveyStatusEnum.cs create mode 100644 WcsMain/Enum/SendWmsTaskStatusEnum.cs create mode 100644 WcsMain/Enum/Stacker/StackerControlModeEnum.cs create mode 100644 WcsMain/Enum/Stacker/StackerStatusEnum.cs create mode 100644 WcsMain/Enum/Stacker/StackerUseStatusEnum.cs create mode 100644 WcsMain/Enum/StandTypeEnum.cs create mode 100644 WcsMain/Enum/TaskEnum/ElTagTaskStatusEnum.cs create mode 100644 WcsMain/Enum/TaskEnum/ElTagTaskTypeEnum.cs create mode 100644 WcsMain/Enum/TaskEnum/TaskCategoryEnum.cs create mode 100644 WcsMain/Enum/TaskEnum/TaskTypeEnum.cs create mode 100644 WcsMain/Enum/TaskEnum/WcsTaskStatusEnum.cs create mode 100644 WcsMain/Enum/TaskEnum/WcsTaskTypeEnum.cs create mode 100644 WcsMain/Enum/TaskEnum/WmsTaskStatusEnum.cs create mode 100644 WcsMain/Enum/TaskEnum/WmsTaskTypeEnum.cs create mode 100644 WcsMain/Enum/TaskFeedBackTypeEnum.cs create mode 100644 WcsMain/Enum/Tcp/TcpType.cs create mode 100644 WcsMain/Enum/UserStatusEnum.cs create mode 100644 WcsMain/EquipOperation/ConnectPLCs.cs create mode 100644 WcsMain/EquipOperation/Convey/ConveyOperation.cs create mode 100644 WcsMain/EquipOperation/ElTag/AtopOperation.cs create mode 100644 WcsMain/EquipOperation/Entity/ConveyPLCTask.cs create mode 100644 WcsMain/EquipOperation/Entity/Stacker/StackerInfo.cs create mode 100644 WcsMain/EquipOperation/Entity/Stacker/StackerPlcTask.cs create mode 100644 WcsMain/EquipOperation/Entity/StackerConvey/StackerConveyInfo.cs create mode 100644 WcsMain/EquipOperation/Entity/StackerConvey/StackerConveyPlcTask.cs create mode 100644 WcsMain/EquipOperation/Entity/StackerFeedbackStatus.cs create mode 100644 WcsMain/EquipOperation/Entity/TaskFeedBackEntity.cs create mode 100644 WcsMain/EquipOperation/Stacker/StackerOperation.cs create mode 100644 WcsMain/EquipOperation/StackerConvey/StackerConveyOperation.cs create mode 100644 WcsMain/ExtendMethod/AppLocationExtendMethod.cs create mode 100644 WcsMain/ExtendMethod/AppStackerExtendMethod.cs create mode 100644 WcsMain/ExtendMethod/AppWcsTaskExtendMethod.cs create mode 100644 WcsMain/ExtendMethod/StackerExtendMethod.cs create mode 100644 WcsMain/ExtendMethod/StringExtendMethod.cs create mode 100644 WcsMain/ExtendMethod/TaskExtendMethod.cs create mode 100644 WcsMain/Language/Readme.txt create mode 100644 WcsMain/Plugins/WcsCirculation.cs create mode 100644 WcsMain/Plugins/WmsWebApiPost.cs create mode 100644 WcsMain/Program.cs create mode 100644 WcsMain/Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 WcsMain/Properties/PublishProfiles/FolderProfile.pubxml.user create mode 100644 WcsMain/Properties/launchSettings.json create mode 100644 WcsMain/Socket/SocketOperation.cs create mode 100644 WcsMain/StartAction/AutofacModule.cs create mode 100644 WcsMain/StartAction/AutowiredSelector.cs create mode 100644 WcsMain/StartAction/HostService.cs create mode 100644 WcsMain/StartAction/LoadingRunningData.cs create mode 100644 WcsMain/StartAction/ServiceStart.cs create mode 100644 WcsMain/StaticData/StaticString.cs create mode 100644 WcsMain/Tcp/Client/BaseTcpClient.cs create mode 100644 WcsMain/Tcp/Client/PlcTcpClient.cs create mode 100644 WcsMain/Tcp/Entity/Convey/GetRouterData.cs create mode 100644 WcsMain/Tcp/Entity/Convey/SetRouterData.cs create mode 100644 WcsMain/Tcp/Entity/Message/MsgHeader.cs create mode 100644 WcsMain/Tcp/Entity/Message/MsgInfo.cs create mode 100644 WcsMain/Tcp/Entity/PlcTcpClientSendResult.cs create mode 100644 WcsMain/Tcp/Entity/TcpClientSendResult.cs create mode 100644 WcsMain/Tcp/Entity/TcpServeConnectionData.cs create mode 100644 WcsMain/WcsAttribute/AppConfig/ConfigKeyAttribute.cs create mode 100644 WcsMain/WcsAttribute/AutoFacAttribute/AutowiredAttribute.cs create mode 100644 WcsMain/WcsAttribute/AutoFacAttribute/ComponentAttribute.cs create mode 100644 WcsMain/WcsAttribute/AutoFacAttribute/ServiceAttribute.cs create mode 100644 WcsMain/WcsMain.csproj create mode 100644 WcsMain/WcsMain.csproj.user create mode 100644 WcsMain/WebSocket/WebSocketOperation.cs create mode 100644 WcsMain/appsettings.Development.json create mode 100644 WcsMain/appsettings.json create mode 100644 WcsService.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f3d037 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +Tools/*/bin +WcsMain/bin +Tools/*/obj +WcsMain/obj +bin +obj +.vs +.idea diff --git a/Tools/ApiTool/ApiTool.csproj b/Tools/ApiTool/ApiTool.csproj new file mode 100644 index 0000000..df21c88 --- /dev/null +++ b/Tools/ApiTool/ApiTool.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + AnyCPU;x64;x86 + + + + + + + diff --git a/Tools/ApiTool/Dto/ApiResponseInfo.cs b/Tools/ApiTool/Dto/ApiResponseInfo.cs new file mode 100644 index 0000000..674b1d8 --- /dev/null +++ b/Tools/ApiTool/Dto/ApiResponseInfo.cs @@ -0,0 +1,84 @@ +using System.Text; + +namespace ApiTool.Dto; + +public class ApiResponseInfo +{ + /// + /// 请求 URL + /// + public string? RequestUrl { get; set; } + + /// + /// 请求字符串 + /// + public string? RequestMsg { get; set; } + + /// + /// 响应字符串 + /// + public string? ResponseMsg { get; set; } + + /// + /// 请求时间 + /// + public DateTime? RequestTime { get; set; } + + /// + /// 响应时间 + /// + public DateTime? ResponseTime { get; set; } + + /// + /// 是否发送成功 + /// + /// + /// 注意:这里仅表示服务端有响应 + /// + public bool IsSend { get; set; } + + /// + /// 请求方式 + /// + public string? RequestMethod { get; set; } + + /// + /// 请求耗时 + /// + public double UseTime { get; set; } + + /// + /// 返回的异常,没有异常返回 null + /// + public Exception? RequestException { get; set; } + + /// + /// 重写toString + /// + /// + public override string ToString() + { + StringBuilder builder = new(); + builder.AppendLine($"[请求结果] {IsSend}"); + builder.AppendLine($"[请求方式] {RequestMethod}"); + builder.AppendLine($"[请求地址] {RequestUrl}"); + builder.AppendLine($"[请求信息] {RequestMsg}"); + builder.AppendLine($"[响应信息] {ResponseMsg}"); + builder.AppendLine($"[请求时间] {RequestTime}"); + builder.AppendLine($"[响应时间] {RequestTime}"); + builder.AppendLine($"[请求耗时] {UseTime} ms"); + if (RequestException != default) + { + builder.AppendLine($"[异常信息] {RequestException.Message}"); + } + return builder.ToString(); + } +} + +public class ApiResponseInfo : ApiResponseInfo where T : class, new() +{ + /// + /// 响应的实体类 + /// + public T? ResponseEntity { get; set; } +} \ No newline at end of file diff --git a/Tools/ApiTool/WebApiPost.cs b/Tools/ApiTool/WebApiPost.cs new file mode 100644 index 0000000..17c395a --- /dev/null +++ b/Tools/ApiTool/WebApiPost.cs @@ -0,0 +1,120 @@ +using System.Diagnostics; +using System.Net.Http.Headers; +using System.Text; +using ApiTool.Dto; +using Newtonsoft.Json; + + +namespace ApiTool; + +public class WebApiPost +{ + /* + * 作者:菻蔃 + * + * 版本时间:2024年5月10日 + * + */ + + public WebApiPost() { } + + private string? _baseUrl = string.Empty; + + private Action? _apiAction; + + public WebApiPost(Action apiAction) + { + _apiAction = apiAction; + } + + public WebApiPost(string url, Action action) + { + _baseUrl = url; + _apiAction = action; + } + + /// + /// 设置响应事件, + /// + /// + public void SetResponseAction(Action action) + { + _apiAction = action; + } + public void SetBaseUrl(string url) + { + _baseUrl = url; + } + + /// + /// 执行POST请求 + /// + /// + /// + /// + /// + /// + /// + public ApiResponseInfo HttpPost(TRequest requestEntity, string method = "", int time = 10000) where TRequest : class where TResponse : class, new() + { + ApiResponseInfo result = new() + { + RequestMethod = "POST" + }; + string address = _baseUrl + method; + Encoding encoding = Encoding.UTF8; + Stopwatch sw = new(); + string sendMes = JsonConvert.SerializeObject(requestEntity); + sw.Start(); + try + { + HttpContent content = new StringContent(sendMes, encoding, "application/json"); + HttpClient client = new(); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + client.Timeout = new TimeSpan(0, 0, 0, 0, time); + result.RequestTime = DateTime.Now; + var requestTask = client.PostAsync(address, content); + requestTask.Wait(); + var responseResult = requestTask.Result; + if (responseResult.IsSuccessStatusCode) + { + var responseRead = responseResult.Content.ReadAsStringAsync(); + responseRead.Wait(); + string responseString = responseRead.Result; + result.IsSend = true; + result.RequestMsg = sendMes; + result.RequestUrl = address; + result.ResponseMsg = responseString; + result.ResponseEntity = JsonConvert.DeserializeObject(responseString); + } + else + { + var responseCode = responseResult.StatusCode; + var responseRead = responseResult.Content.ReadAsStringAsync(); + responseRead.Wait(); + string responseString = responseRead.Result; + result.IsSend = false; + result.RequestMsg = sendMes; + result.RequestUrl = address; + result.RequestException = new Exception($"[{responseCode}]{responseString}"); + } + } + catch (Exception ex) + { + result.IsSend = false; + result.RequestMsg = sendMes; + result.RequestUrl = address; + result.RequestException = ex; + } + result.ResponseTime = DateTime.Now; + sw.Stop(); + result.ResponseTime = DateTime.Now; + TimeSpan ts = sw.Elapsed; + result.UseTime = ts.TotalMilliseconds; + _apiAction?.Invoke(result); + return result; + } + + +} \ No newline at end of file diff --git a/Tools/CirculateTool/CirculateTool.csproj b/Tools/CirculateTool/CirculateTool.csproj new file mode 100644 index 0000000..22e1b2e --- /dev/null +++ b/Tools/CirculateTool/CirculateTool.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + AnyCPU;x64;x86 + + + diff --git a/Tools/CirculateTool/CirculationAttribute.cs b/Tools/CirculateTool/CirculationAttribute.cs new file mode 100644 index 0000000..a821bc6 --- /dev/null +++ b/Tools/CirculateTool/CirculationAttribute.cs @@ -0,0 +1,30 @@ +namespace CirculateTool; + +/// +/// 一个类里面的方法加上这个特性就表示需要被循环执行 +/// +/// +/// 一个类里面的方法加上这个特性就表示需要被循环执行 +/// +/// 循环时间,默认500ms +/// 方法描述 +/// 方法描述 +[AttributeUsage(AttributeTargets.All)] +public class CirculationAttribute(string? methodDescription = null, int circulationTime = 500, string[]? tags = null) : Attribute +{ + + /// + /// 循环时间 + /// + public int CirculationTime { get; } = circulationTime; + + /// + /// 方法描述 + /// + public string? MethodDescription { get; } = methodDescription; + + /// + /// 方法或者类的标记 + /// + public string[]? Tags { get; } = tags; +} \ No newline at end of file diff --git a/Tools/CirculateTool/StartCirculation.cs b/Tools/CirculateTool/StartCirculation.cs new file mode 100644 index 0000000..d7babef --- /dev/null +++ b/Tools/CirculateTool/StartCirculation.cs @@ -0,0 +1,110 @@ +using System.Reflection; + +namespace CirculateTool; +/* + * 作者:菻蔃 + * 版本时间:2023年04月15日 + * + * 注意配合特性使用 + * + */ + +/// +/// 定时任务类 +/// +public class StartCirculation +{ + + /// + /// 触发的异常 + /// + public event ExceptionHandlerEvent? ExceptionHandler; + + public delegate void ExceptionHandlerEvent(string methodDescription, Exception ex); + + /// + /// 显示相关信息 + /// + public event MessageHandlerEvent? MessageHandler; + + public delegate void MessageHandlerEvent(string message); + + /// + /// 默认的循环时间 + /// + private readonly int _defaultCirculationTime = 500; + + /// + /// 启动一个程序集里面带有的类里面的定时方法 + /// + /// + /// + public virtual void StartAssemblyCirculation(Assembly assembly, object[]? instanceParams = null) + { + Type[] types = assembly.GetTypes(); + if (types.Length == 0) return; + foreach (Type type in types) + { + var attributes = type.GetCustomAttributes(false); + foreach (var attribute in attributes) + { + if (attribute is not CirculationAttribute) continue; + StartTask(type, instanceParams); + break; + } + } + } + + /// + /// 开启一个实例里面所有已经添加了特性的方法 + /// + /// + /// + public virtual void StartTask(Type type, object[]? instanceParams = null) + { + var methods = type.GetMethods(); + foreach (var method in methods) + { + var attributes = method.GetCustomAttributes(false); + foreach (var attribute in attributes) + { + if (attribute is not CirculationAttribute needDurable) continue; + string methodDescription = needDurable.MethodDescription ?? $"{type.Name}.{method.Name}"; + bool Action() => (bool)(method.Invoke(Activator.CreateInstance(type, instanceParams), []) ?? false); + StartTask(Action, methodDescription, needDurable.CirculationTime); + break; + } + } + } + + /// + /// 开启一个方法 + /// + /// + /// + /// + /// + public virtual async void StartTask(Func action, string? description = null, int? durableTime = null) + { + int durableTimeValue = durableTime ?? _defaultCirculationTime; + string methodDescription = description ?? action.Method.Name; + CancellationTokenSource cts = new(); + PeriodicTimer timer = new(new TimeSpan(0, 0, 0, 0, durableTimeValue)); + MessageHandler?.Invoke($"定时器:{methodDescription},已经启动,执行间隔为:{durableTimeValue} 毫秒。"); + while (await timer.WaitForNextTickAsync(cts.Token)) + { + try + { + var result = action(); + if (result) continue; + await cts.CancelAsync(); + MessageHandler?.Invoke($"定时器:{methodDescription},主动结束。"); + return; // 该return会结束这个线程 + } + catch (Exception ex) + { + ExceptionHandler?.Invoke(methodDescription, ex); + } + } + } +} \ No newline at end of file diff --git a/Tools/DataCheck/CheckData.cs b/Tools/DataCheck/CheckData.cs new file mode 100644 index 0000000..288345a --- /dev/null +++ b/Tools/DataCheck/CheckData.cs @@ -0,0 +1,55 @@ +using System.Text.RegularExpressions; + +namespace DataCheck; +/* + * 作者:icewint + * + */ + +/// +/// 数据校验类 +/// +public class CheckData +{ + /// + /// 校验是否满足设定的规则,需要添加 特性 + /// + /// + /// + /// + public static bool CheckDataRules(T data) where T : class + { + Type type = typeof(T); + var properties = type.GetProperties(); + foreach (var property in properties) + { + string? proValue = property.GetValue(data)?.ToString(); + var attributes = property.GetCustomAttributes(false); + foreach (var attribute in attributes) + { + if (attribute is DataRulesAttribute dataRules) + { + // 判断是否允许为 NULL + if (!dataRules.AllowNull && proValue == null) + { + // 如果不允许为 null 但是为 null 了就返回错误 + return false; + } + // 下面是允许为 null 的情况 + if (proValue == null) + { + // 允许 null 并且为 null 满足要求 + continue; + } + if (!Regex.IsMatch(proValue, dataRules.RegexRule)) + { + // 允许为 null 但不是 null 且不满足数据要求 + return false; + } + } + } + } + return true; + } + +} \ No newline at end of file diff --git a/Tools/DataCheck/DataCheck.csproj b/Tools/DataCheck/DataCheck.csproj new file mode 100644 index 0000000..0191b67 --- /dev/null +++ b/Tools/DataCheck/DataCheck.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + AnyCPU;x64;x86 + + + diff --git a/Tools/DataCheck/DataRulesAttribute.cs b/Tools/DataCheck/DataRulesAttribute.cs new file mode 100644 index 0000000..ee1c8d4 --- /dev/null +++ b/Tools/DataCheck/DataRulesAttribute.cs @@ -0,0 +1,18 @@ +namespace DataCheck; + +/// +/// 数据校验规则 +/// +[AttributeUsage(AttributeTargets.Property)] +public class DataRulesAttribute(bool allowNull = false, string regexRule = ".*") : Attribute +{ + /// + /// 是否允许空值 + /// + public readonly bool AllowNull = allowNull; + + /// + /// 正则表达式 + /// + public readonly string RegexRule = regexRule; +} \ No newline at end of file diff --git a/Tools/EncryptTool/EncryptTool.csproj b/Tools/EncryptTool/EncryptTool.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/Tools/EncryptTool/EncryptTool.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Tools/EncryptTool/Md5Encrypt.cs b/Tools/EncryptTool/Md5Encrypt.cs new file mode 100644 index 0000000..a30d361 --- /dev/null +++ b/Tools/EncryptTool/Md5Encrypt.cs @@ -0,0 +1,52 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace EncryptTool; + + +/* + * 作者:菻蔃 + * 注意:MD5 加密会被机器容易的破解,请勿用于机密数据 + * 版本时间:2024年1月21日 + */ + + +/// +/// MD5 加密 +/// +public static class Md5Encrypt +{ + /// + /// 魔改版加密,用于加密密码,无法破解,本人也无法知道原密码 + /// + /// 需要加密的字符串 + /// 加密后返回的长度 + /// + public static string EncryptPassword(string? password, int length = 255) + { + if (string.IsNullOrEmpty(password)) { return string.Empty; } + var result = string.Empty; + var i = 0; + while (result.Length < length) + { + var bytes = MD5.HashData(Encoding.UTF8.GetBytes(password)); + var tempStr = Convert.ToBase64String(bytes); + tempStr = Regex.Replace(tempStr, "\\W", "").ToUpper(); + if (i % 2 == 0) + { + result += Regex.Replace(tempStr, "[0-9]", ""); + } + else + { + result += Regex.Replace(tempStr, "[A-Z]|[a-z]", ""); + } + password = tempStr; + i++; + } + return result[..length]; + } + + + +} diff --git a/Tools/HkCamera/Class1.cs b/Tools/HkCamera/Class1.cs new file mode 100644 index 0000000..3e64fba --- /dev/null +++ b/Tools/HkCamera/Class1.cs @@ -0,0 +1,6 @@ +namespace HkCamera; + +public class Class1 +{ + +} \ No newline at end of file diff --git a/Tools/HkCamera/HkCamera.csproj b/Tools/HkCamera/HkCamera.csproj new file mode 100644 index 0000000..0191b67 --- /dev/null +++ b/Tools/HkCamera/HkCamera.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + AnyCPU;x64;x86 + + + diff --git a/Tools/LedSimple/LEDColor.cs b/Tools/LedSimple/LEDColor.cs new file mode 100644 index 0000000..1d94a8c --- /dev/null +++ b/Tools/LedSimple/LEDColor.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LedSimple; + +public enum LEDColor +{ + Red = 0xff, + Green = 0xff00, + Yellow = 0xffff +} \ No newline at end of file diff --git a/Tools/LedSimple/LEDDLL.cs b/Tools/LedSimple/LEDDLL.cs new file mode 100644 index 0000000..c7c1810 --- /dev/null +++ b/Tools/LedSimple/LEDDLL.cs @@ -0,0 +1,844 @@ +using System.Runtime.InteropServices; + +namespace LedSimple; + +public class Leddll +{ + //颜色值 R 0x0000ff G 0x00ff00 B 0xff0000 + public const int COLOR_RED = 0xff; //红色 + public const int COLOR_GREEN = 0xff00; //绿色 + public const int COLOR_YELLOW = 0xffff; //黄色 + + public const int ADDTYPE_STRING = 0; //添加类型为字符串 + public const int ADDTYPE_FILE = 1; //添加类型为文件 + + public const int OK = 0;//函数返回成功 + + //******节目定时启用日期时间星期的标志宏*************************************************************************** + public const int ENABLE_DATE = 0x01; + public const int ENABLE_TIME = 0x02; + public const int ENABLE_WEEK = 0x04; + //***************************************************************************************************************** + + //******节目定时星期里某天启用宏*********************************************************** + public const int WEEK_MON = 0x01; + public const int WEEK_TUES = 0x02; + public const int WEEK_WEN = 0x04; + public const int WEEK_THUR = 0x08; + public const int WEEK_FRI = 0x10; + public const int WEEK_SAT = 0x20; + public const int WEEK_SUN = 0x40; + //***************************************************************************** + + //[StructLayout(LayoutKind.Sequential, Size = 8, CharSet = CharSet.Unicode, Pack = 1)] + + //**通讯设置结构体********************************************************* + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct COMMUNICATIONINFO + { + public int LEDType; ////LED类型 0.6代T系A系XC系 1.6代E系 2.X1X2 3.7代C系 + public int SendType; //通讯方式 0.为Tcp发送(又称固定IP通讯), 1.广播发送(又称单机直连) 2.串口通讯 3.磁盘保存 4.广域网通讯 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + public string IpStr; //LED屏的IP地址,只有通讯方式为0时才需赋值,其它通讯方式无需赋值 + public int Commport; //串口号,只有通讯方式为2时才需赋值,其它通讯方式无需赋值 + public int Baud; //波特率,只有通讯方式为2时才需赋值,其它通讯方式无需赋值, 0.9600 1.57600 2.115200 直接赋值 9600,19200,38400,57600,115200亦可 + public int LedNumber; //LED的屏号,只有通讯方式为2时,且用485通讯时才需赋值,其它通讯方式无需赋值 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string OutputDir; //磁盘保存的目录,只有通讯方式为3时才需赋值,其它通讯方式无需赋值 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 19)] + public string networkIdStr; //网络ID,只有通讯方式为4时才需赋值,其它通讯方式无需赋值 + }; + //*********************************************************************** + + //**区域坐标结构体********************************************************* + public struct AREARECT + { + public int left; //区域左上角横坐标 + public int top; //区域左上角纵坐标 + public int width; //区域的宽度 + public int height; //区域的高度 + }; + //**************************************************************************** + //***字体属性结构对********************************************************** + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct FONTPROP + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string FontName; //字体名 + public int FontSize; //字号(单位磅) + public int FontColor; //字体颜色 + public int FontBold; //是否加粗 + public int FontItalic; //是否斜体 + public int FontUnderLine; //时否下划线 + }; + //**************************************************************************** + + //**页面显示的属性结构体**************************************************** + public struct PLAYPROP + { + public int InStyle; //入场特技值(取值范围 0-38) + public int OutStyle; //退场特技值(现无效,预留,置0) + public int Speed; //特技显示速度(取值范围1-255) 值越大,速度越慢 + public int DelayTime; //页面留停时间(1-65535) 注:当入场特技为连续左移、连续右移、连续上移、连续下移时,此参数无效 + }; + /* 特技值对应 + 0=立即显示 + 1=随机 + 2=左移 + 3=右移 + 4=上移 + 5=下移 + 6=连续左移 + 7=连续右移 + 8=连续上移 + 9=连续下移 + 10=闪烁 + 11=激光字(向上) + 12=激光字(向下) + 13=激光字(向左) + 14=激光字(向右) + 15=水平交叉拉幕 + 16=上下交叉拉幕 + 17=左右切入 + 18=上下切入 + 19=左覆盖 + 20=右覆盖 + 21=上覆盖 + 22=下覆盖 + 23=水平百叶(左右) + 24=水平百叶(右左) + 25=垂直百叶(上下) + 26=垂直百叶(下上) + 27=左右对开 + 28=上下对开 + 29=左右闭合 + 30=上下闭合 + 31=向左拉伸 + 32=向右拉伸 + 33=向上拉伸 + 34=向下拉伸 + 35=分散向左拉伸 + 36=分散向右拉伸 + 37=冒泡 + 38=下雪 + */ + //******************************************************************************* + //**设置节目定时属性结构体**************************************************** + public struct PROGRAMTIME + { + public int EnableFlag; //启用定时的标记,ENABLE_DATE为启用日期,ENABLE_TIME为启用时间,ENABLE_WEEK为启用星期,可用或运算进行组合,如 ENABLE_DATE | ENABLE_TIME | ENABLE_WEEK + public int WeekValue; //启用星期后,选择要定时的星期里的某些天,用宏 WEEK_MON,WEEK_TUES,WEEK_WEN,WEEK_THUR,WEEK_FRI,WEEK_SAT,WEEK_SUN 通过或运算进行组合 + public int StartYear; //起始年 + public int StartMonth; //起始月 + public int StartDay; //起始日 + public int StartHour; //起姐时 + public int StartMinute; //起始分 + public int StartSecond; //起始秒 + public int EndYear; //结束年 + public int EndMonth; //结束月 + public int EndDay; //结束日 + public int EndHour; //结束时 + public int EndMinute; //结束分 + public int EndSecond; //结束秒 + }; + //********************************************************************************** + //数字时钟属性结构体********************************************************************************* + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct DIGITALCLOCKAREAINFO + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string ShowStr; //自定义显示字符串 + //[MarshalAs(UnmanagedType.Struct)] + public FONTPROP ShowStrFont; //自定义显示字符串以及日期星期时间的字体属性,注意此字体属性里的FontColor只对自定义显示字体有效,其它项的颜色有单独的颜色属性,属性的赋值见FONTPROP结构体说明 + public int TimeLagType; //时差类型 0为超前,1为滞后 + public int HourNum; //时差小时数 + public int MiniteNum; //时差分钟数 + public int DateFormat; //日期格式 0.YYYY年MM月DD日 1.YY年MM月DD日 2.MM/DD/YYYY 3.YYYY/MM/DD 4.YYYY-MM-DD 5.YYYY.MM.DD 6.MM.DD.YYYY 7.DD.MM.YYYY + public int DateColor; //日期字体颜色 格式是16进制 BBGGRR(如:红色0xff 绿色0xff00 黄色0xffff) + public int WeekFormat; //星期格式 0.星期X 1.Monday 2.Mon. + public int WeekColor; //星期字体颜色 + public int TimeFormat; //时间格式 0.HH时mm分ss秒 1.HH時mm分ss秒 2.HH:mm:ss 3.上午 HH:mm:ss 4.AM HH:mm:ss 5.HH:mm:ss 上午 6.HH:mm:ss AM + public int TimeColor; //时间字体颜色 + public int IsShowYear; //是否显示年 TRUE为显示 FALSE不显示 下同 + public int IsShowWeek; //是否显示星期 + public int IsShowMonth; //是否显示月 + public int IsShowDay; //是否显示日 + public int IsShowHour; //是否显示时 + public int IsShowMinute; //是否显示分 + public int IsShowSecond; //是否显示秒 + public int IsMutleLineShow; //是否多行显示 + }; + //****************************************************************************** + //**模拟时钟属性结构体********************************************************* + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct CLOCKAREAINFO + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string ShowStr; //自定义显示字符串 + public FONTPROP ShowStrFont; //自定义显示字符串字体属性 + public int TimeLagType; //时差类型 0为超前,1为滞后 + public int HourNum; //时差小时数 + public int MiniteNum; //时差分钟数 + public int ClockType; //表盘类型 0.圆形 1.正方形 + public int HourMarkColor; //时标颜色 格式是16进制 BBGGRR(如:红色0xff 绿色0xff00 黄色0xffff) + public int HourMarkType; //时标类型 0.圆形 1.正方形 + public int HourMarkWidth; //时标宽度 1~16 + public int MiniteMarkColor; //分标颜色 + public int MiniteMarkType; //分标类型 0.圆形 1.正方形 + public int MiniteMarkWidth; //分标宽度 1~16 + public int HourPointerColor; //时针颜色 + public int MinutePointerColor; //分针颜色 + public int SecondPointerColor; //秒针颜色 + public int HourPointerWidth; //时针的宽度 1~5 + public int MinutePointerWidth; //分针的宽度 1~5 + public int SecondPointerWidth; //秒针的宽度 1~5 + public int IsShowDate; //是否显示日期 + public int DateFormat; //日期格式 0.YYYY年MM月DD日 1.YY年MM月DD日 2.MM/DD/YYYY 3.YYYY/MM/DD 4.YYYY-MM-DD 5.YYYY.MM.DD 6.MM.DD.YYYY 7.DD.MM.YYYY + public FONTPROP DateFont; //日期字体属性 + public int IsShowWeek; //是否显示星期 + public int WeekFormat; //星期格式 0.星期X 1.Monday 2.Mon. + public FONTPROP WeekFont; //星期字体属性 + }; + //************************************************************************************** + + //**计时属性结构体********************************************************************** + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct TIMEAREAINFO + { + public int ShowFormat; //显示格式 0.xx天xx时xx分xx秒 1.xx天xx時xx分xx秒 2.xxDayxxHourxxMinxxSec 3.XXdXXhXXmXXs 4.xx:xx:xx:xx + public int nYear; //结束年 + public int nMonth; //结束月 + public int nDay; //结束日 + public int nHour; //结束时 + public int nMinute; //结束分 + public int nSecond; //结束秒 + public int IsShowDay; //是否显示天 + public int IsShowHour; //是否显示时 + public int IsShowMinute; //是否显示分 + public int IsShowSecond; //是否显示秒 + public int IsMutleLineShow; //是否多行显示,指的是自定义文字与计时文字是否分行显示 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string ShowStr; //自定义文字字符串 + public int TimeStrColor; //计时文字的颜色 + public FONTPROP ShowFont; //自定义文字及计时文字颜色,其中FontColor只对文定义文字有效,计时文字颜色为TimeStrColor + }; + //**************************************************************************************** + + + //**LED通讯参数修改结构体***************************************************************** + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct LEDCOMMUNICATIONPARAMETER + { + public int dwMask; //要修改项的标记 0.修改网络通讯参数 1.修改串口通讯参数 2.修改网口和串口通讯参数 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + public string IpStr; //新的IP地址,只有dwMask为0或2时才需赋值,其它值无需赋值,格式例如 192.168.1.100 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + public string NetMaskStr; //新的子网掩码,只有dwMask为0或2时才需赋值,其它值无需赋值,格式例如 255.255.255.0 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + public string GatewayStr; //新的网关,只有dwMask为0或2时才需赋值,其它值无需赋值,格式例如 192.168.1.1 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 18)] + public string MacStr; //新的MAC地址,只有dwMask为0或2时才需赋值,其它值无需赋值,格式例如 12-34-56-78-9a-bc,如无需修改请设为 ff-ff-ff-ff-ff-ff + public int Baud; //波特率,只有dwMask为1或2时才需赋值,其它值无需赋值,0.9600 1.57600 2.115200 + public int LedNumber; //LED屏号 1~255,网络通讯和232通讯赋值 1 即可,485必需和控制卡显示的屏号相同才可通讯 + }; + //***************************************************************************************** + + + //**流水边框属性结构体************************************************************************ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct WATERBORDERINFO + { + public int Flag; //流水边框加载类型标志,0.为动态库预置的边框 1.为从文件加载的边框 + public int BorderType; //边框的类型,Flag为0是有效,0.单色边框 1.双基色边框 2.全彩边框 + public int BorderValue; //边框的值,Flag为0是有效,单色边框取值范围是0~39,双基色边框取值范围是0~34,全彩边框取值范围是0~21 + public int BorderColor; //边框线颜色,Flag为0并且BorderType为0是才有效 + public int BorderStyle; //边框显示的样式 0.固定 1.顺时针 2.逆时针 3.闪烁 + public int BorderSpeed;//边框流动的速度 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string WaterBorderBmpPath; //边框图片文件的路径,注意只能是bmp图片,图片大小必需是宽度为32点,取高度小于等于8 + }; + //********************************************************************************************* + + + + //**定时开关屏设置属性************************************************************************ + public struct ONOFFTIMEINFO + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] TimeFlag; //支持3个定时,1代表打开 0关闭 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] StartHour; //开始时钟 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] StartMinute; //开始分钟 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] EndHour; //结束时钟 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] EndMinute; //结束分钟 + }; + //******************************************************************************************** + + //**定时亮度设置属性************************************************************************** + public struct BRIGHTNESSTIMEINFO + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] TimeFlag; //支持3个定时,1代表打开 0关闭 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] StartHour; //开始时钟 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] StartMinute; //开始分钟 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] EndHour; //结束时钟 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] EndMinute; //结束分钟 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] BrightnessValue; //亮度值0~15 + }; + //******************************************************************************************* + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + public delegate int SERVERINFOCALLBACK(int Msg, int wParam, nint ptr); + + public enum LV_MSG + { + LV_MSG_NONE, + LV_MSG_CARD_ONLINE,//上线通知,通过CARD_INFO结构体指针获取详细上线信息 + LV_MSG_CARD_OFFLINE,//下线通知,通过CARD_INFO结构体指针获取详细下线信息 + }; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct CARD_INFO + { + public int port; //控制卡端口 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + public string ipStr; //控制卡IP + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 19)] + public string networkIdStr; //控制卡唯一网络ID(每张卡都贴有唯一网络ID) + }; + /******************************************************************************************** +* LV_InitLed 初始化屏的类型和颜色顺序(C卡) +* 当Led上显示的文字区域的颜色与下发的不一致, 请的确认Led 屏的RGB顺序,并调用此接口 +* 参数说明 +* nLedType 屏类型 0.6代T系A系XC系 1.6代E系 2.X1X2 3.7代C系 4: E5,E6 +* nRgb 模组的RGB顺序,仅C卡有效,其他卡固定为0. C卡时, 0: R->G->B 1: G->R->B 2:R->B->G 3:B->R->G 4:B->G->R 5:G->B->R +* 返回值 无 +* +********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_InitLed", CharSet = CharSet.Unicode)] + public static extern void LV_InitLed(int nLedType, int nRgb); + + + /******************************************************************************************** + * LV_CreateProgramEx 创建节目对象,返回类型为 HPROGRAM + * + * 参数说明 + * LedWidth 屏的宽度 + * LedHeight 屏的高度 + * ColorType 屏的颜色 1.单色 2.双基色 3.三基色 注:C卡全彩参数为3 X系列卡参数固定为 4 + * GrayLevel 灰度等级 赋值 1-5对应的灰度等级分别为 无,4,8,16,32 注:目前C系列的卡才支持,其它型号(T,A,U,XC,W,E,X)参数必须为0 + * SaveType 节目保存位置,默认为0保存存为flash节目,3保存为ram节目 + * 注:flash节目掉电不清除,ram节目掉电清除。应用场景需要实时刷新的,建议保存为ram节目, 目前仅C卡程序才支持切换, + * 其他卡默认出货为flash程序,如果需要RAM程序请联系业务或者在官网下载,然后使用Led Player对卡进行升级 + * 返回值 + * 0 创建节目对象失败 + * 非0 创建节目对象成功 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_CreateProgramEx", CharSet = CharSet.Unicode)] + public static extern nint LV_CreateProgramEx(int LedWidth, int LedHeight, int ColorType, int GrayLevel, int SaveType); + + /********************************************************************************************* + * LV_AddProgram 添加一个节目 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * ProgramTime 节目播放时长 0.节目播放时长 非0.指定播放时长 + * LoopCount 循环播放次数 1-255 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddProgram", CharSet = CharSet.Unicode)] + public static extern int LV_AddProgram(nint hProgram, int ProgramNo, int ProgramTime, int LoopCount); + + /********************************************************************************************* + * LV_SetProgramTime 设置节目定时 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * pProgramTime 节目定时属性,设置方式见PROGRAMTIME结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_SetProgramTime", CharSet = CharSet.Unicode)] + public static extern int LV_SetProgramTime(nint hProgram, int ProgramNo, ref PROGRAMTIME pProgramTime); + + /********************************************************************************************* + * LV_AddImageTextArea 添加一个图文区域 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * pAreaRect 区域坐标属性,设置方式见AREARECT结构体注示 + * nLayout 区域层号,1.前景区(默认) 0.背景区 注:除C系列,其它默认为1 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddImageTextArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddImageTextArea(nint hProgram, int ProgramNo, int AreaNo, ref AREARECT pAreaRect, int nLayout); + + /********************************************************************************************* + * LV_AddFileToImageTextArea 添加一个文件到图文区 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * FilePath 文件路径,支持的文件类型有 txt rtf bmp gif png jpg jpeg tiff + * pPlayProp 显示的属性,设置方式见PLAYPROP结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddFileToImageTextArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddFileToImageTextArea(nint hProgram, int ProgramNo, int AreaNo, string FilePath, ref PLAYPROP pPlayProp); + + /********************************************************************************************* + * LV_AddSingleLineTextToImageTextArea 添加一个单行文本到图文区 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * AddType 添加的类型 0.为字符串 1.文件(只支持txt和rtf文件) + * AddStr AddType为0则为字符串数据,AddType为1则为文件路径 + * pFontProp 如果AddType为字符串类型或AddType为文件类型且文件为txt则可传入以赋值的该结构体,其它可赋NULL + * pPlayProp 显示的属性,设置方式见PLAYPROP结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddSingleLineTextToImageTextArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddSingleLineTextToImageTextArea(nint hProgram, int ProgramNo, int AreaNo, int AddType, string AddStr, ref FONTPROP pFontProp, ref PLAYPROP pPlayProp); + + /********************************************************************************************* + * LV_AddMultiLineTextToImageTextArea 添加一个多行文本到图文区 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * AddType 添加的类型 0.为字符串 1.文件(只支持txt和rtf文件) + * AddStr AddType为0则为字符串数据,AddType为1则为文件路径 换行符(\n) + * pFontProp 如果AddType为字符串类型或AddType为文件类型且文件为txt则可传入以赋值的该结构体,其它可赋NULL + * pPlayProp 显示的属性,设置方式见PLAYPROP结构体注示 + * nAlignment 水平对齐样式,0.左对齐 1.右对齐 2.水平居中 (注意:只对字符串和txt文件有效) + * IsVCenter 是否垂直居中 0.置顶(默认) 1.垂直居中 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddMultiLineTextToImageTextArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddMultiLineTextToImageTextArea(nint hProgram, int ProgramNo, int AreaNo, int AddType, string AddStr, ref FONTPROP pFontProp, ref PLAYPROP pPlayProp, int nAlignment, int IsVCenter); + + /********************************************************************************************* + * LV_AddStaticTextToImageTextArea 添加一个静止文本到图文区 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * AddType 添加的类型 0.为字符串 1.文件(只支持txt和rtf文件) + * AddStr AddType为0则为字符串数据,AddType为1则为文件路径 + * pFontProp 如果AddType为字符串类型或AddType为文件类型且文件为txt则可传入以赋值的该结构体,其它可赋NULL + * DelayTime 显示的时长 1~65535 + * nAlignment 水平对齐样式,0.左对齐 1.右对齐 2.水平居中 (注意:只对字符串和txt文件有效) + * IsVCenter 是否垂直居中 0.置顶(默认) 1.垂直居中 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddStaticTextToImageTextArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddStaticTextToImageTextArea(nint hProgram, int ProgramNo, int AreaNo, int AddType, string AddStr, ref FONTPROP pFontProp, int DelayTime, int nAlignment, int IsVCenter); + + /********************************************************************************************* + * LV_QuickAddSingleLineTextArea 快速添加一个向左移的单行文本区域 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * pAreaRect 区域坐标属性,设置方式见AREARECT结构体注示 + * AddType 添加的类型 0.为字符串 1.文件(只支持txt和rtf文件) + * AddStr AddType为0则为字符串数据,AddType为1则为文件路径 + * pFontProp 如果AddType为字符串类型或AddType为文件类型且文件为txt则可传入以赋值的该结构体,其它可赋NULL + * nSpeed 滚动速度 1~255 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_QuickAddSingleLineTextArea", CharSet = CharSet.Unicode)] + public static extern int LV_QuickAddSingleLineTextArea(nint hProgram, int ProgramNo, int AreaNo, ref AREARECT pAreaRect, int AddType, string AddStr, ref FONTPROP pFontProp, int nSpeed); + + /********************************************************************************************* + * LV_AddDigitalClockArea 添加一个数字时钟区域 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * pAreaRect 区域坐标属性,设置方式见AREARECT结构体注示 + * pDigitalClockAreaInfo 数字时钟属性,见DIGITALCLOCKAREAINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddDigitalClockArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddDigitalClockArea(nint hProgram, int ProgramNo, int AreaNo, ref AREARECT pAreaRect, ref DIGITALCLOCKAREAINFO pDigitalClockAreaInfo); + + /********************************************************************************************* + * LV_AddTimeArea 添加一个计时区域 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号(取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * pAreaRect 区域坐标属性,设置方式见AREARECT结构体注示 + * pTimeAreaInfo 计时属性,见TIMEAREAINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddTimeArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddTimeArea(nint hProgram, int ProgramNo, int AreaNo, ref AREARECT pAreaRect, ref TIMEAREAINFO pTimeAreaInfo); + + /********************************************************************************************* + * LV_AddClockArea 添加一个模拟时钟区域 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * pAreaRect 区域坐标属性,设置方式见AREARECT结构体注示 + * pClockAreaInfo 模拟时钟属性,见CLOCKAREAINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddClockArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddClockArea(nint hProgram, int ProgramNo, int AreaNo, ref AREARECT pAreaRect, ref CLOCKAREAINFO pClockAreaInfo); + + /********************************************************************************************* + * LV_AddNeiMaArea 添加一个内码区域 + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 从0开始(0-255) + * AreaNo 区域号 (1-255) + * pAreaRect 区域坐标属性,设置方式见AREARECT结构体注示 + * 除C卡外的其他卡,内码区域的起点X和Y坐标必须为8的整数倍,如0,8,16等, 宽高必须大于所用字体大小 + * NeiMaStr 文本字符串 注:字符串编码是GB2312 + * FontSize 字体大小 16 24 32 + * FontColor 文字颜色 格式BBGGRR 0xff 红色 0xff00 绿色 0xffff黄色 + * pPlayProp 显示的属性,设置方式见PLAYPROP结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddNeiMaArea", CharSet = CharSet.Unicode)] + public static extern int LV_AddNeiMaArea(nint hProgram, int ProgramNo, int AreaNo, ref AREARECT pAreaRect, string NeiMaStr, int FontSize, int FontColor, ref PLAYPROP pPlayProp); + + + /********************************************************************************************* + * LV_RefreshNeiMaArea 刷新内码区域 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * NeiMaStr 刷新的数据字符串,格式可以查看<<内码区域局部更新协议>>文档 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_RefreshNeiMaArea", CharSet = CharSet.Unicode)] + public static extern int LV_RefreshNeiMaArea(ref COMMUNICATIONINFO pCommunicationInfo, string NeiMaStr); + + /********************************************************************************************* + * LV_AddWaterBorder 添加一个流水边框区域 + * + * 参数说明 + * hProgram 节目对象句柄 + * ProgramNo 节目号 (取值范围0-255)(从0开始) + * AreaNo 区域号 (取值范围1-255) + * pAreaRect 区域坐标属性,设置方式见AREARECT结构体注示 + * pWaterBorderInfo 流水边框属性,见WATERBORDERINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AddWaterBorder", CharSet = CharSet.Unicode)] + public static extern int LV_AddWaterBorder(nint hProgram, int ProgramNo, int AreaNo, ref AREARECT pAreaRect, ref WATERBORDERINFO pWaterBorderInfo); + + /********************************************************************************************* + * LV_DeleteProgram 销毁节目对象(注意:如果此节目对象不再使用,请调用此函数销毁,否则会造成内存泄露) + * + * 参数说明 + * hProgram 节目对象句柄 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_DeleteProgram", CharSet = CharSet.Unicode)] + public static extern void LV_DeleteProgram(nint hProgram); + + /********************************************************************************************* + * LV_Send 发送节目,此发送为一对一发送 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * hProgram 节目对象句柄 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_Send", CharSet = CharSet.Unicode)] + public static extern int LV_Send(ref COMMUNICATIONINFO pCommunicationInfo, nint hProgram); + + /********************************************************************************************* + * LV_TestOnline 测试LED屏是否可连接上 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_TestOnline", CharSet = CharSet.Unicode)] + public static extern int LV_TestOnline(ref COMMUNICATIONINFO pCommunicationInfo); + + /********************************************************************************************* + * LV_SetBasicInfoEx 设置基本屏参 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * ColorType 屏的颜色 1.单色 2.双基色 3.三基色 注:C卡全彩参数为3 X系列卡参数固定为 4 + * GrayLevel 灰度等级 赋值 1-5对应的灰度等级分别为 无,4,8,16,32 注:目前C系列的卡才支持,其它型号(T,A,U,XC,W,E,X)参数必须为0 + * LedWidth 屏的宽度点数 + * LedHeight 屏的高度点数 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_SetBasicInfoEx", CharSet = CharSet.Unicode)] + public static extern int LV_SetBasicInfoEx(ref COMMUNICATIONINFO pCommunicationInfo, int ColorType, int GrayLevel, int LedWidth, int LedHeight); + + /********************************************************************************************* + * LV_SetOEDA 设置OE DA + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * Oe OE 0.低有效 1.高有效 + * Da DA 0.负极性 1.正极性 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_SetOEDA", CharSet = CharSet.Unicode)] + public static extern int LV_SetOEDA(ref COMMUNICATIONINFO pCommunicationInfo, int Oe, int Da); + + /********************************************************************************************* + * LV_AdjustTime 校时 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_AdjustTime", CharSet = CharSet.Unicode)] + public static extern int LV_AdjustTime(ref COMMUNICATIONINFO pCommunicationInfo); + + /********************************************************************************************* + * LV_PowerOnOff 开关屏 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * OnOff 开关值 0.开屏 1.关屏 2.重启(仅支持C卡) + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_PowerOnOff", CharSet = CharSet.Unicode)] + public static extern int LV_PowerOnOff(ref COMMUNICATIONINFO pCommunicationInfo, int OnOff); + + /********************************************************************************************* + * LV_TimePowerOnOff 定时开关屏 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * pTimeInfo 定时开关屏属性,详见ONOFFTIMEINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_TimePowerOnOff", CharSet = CharSet.Unicode)] + public static extern int LV_TimePowerOnOff(ref COMMUNICATIONINFO pCommunicationInfo, ref ONOFFTIMEINFO pTimeInfo); + + /********************************************************************************************* + * LV_SetBrightness 设置亮度 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * BrightnessValue 亮度值 0~15 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_SetBrightness", CharSet = CharSet.Unicode)] + public static extern int LV_SetBrightness(ref COMMUNICATIONINFO pCommunicationInfo, int BrightnessValue); + + /********************************************************************************************* + * LV_TimeBrightness 定时亮度 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * pBrightnessTimeInfo 定时亮度属性,详见BRIGHTNESSTIMEINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_TimeBrightness", CharSet = CharSet.Unicode)] + public static extern int LV_TimeBrightness(ref COMMUNICATIONINFO pCommunicationInfo, ref BRIGHTNESSTIMEINFO pBrightnessTimeInfo); + + /********************************************************************************************* + * LV_LedTest LED测试 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * TestValue 测试值 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_LedTest", CharSet = CharSet.Unicode)] + public static extern int LV_LedTest(ref COMMUNICATIONINFO pCommunicationInfo, int TestValue); + + /********************************************************************************************* + * LV_TimeLocker LED定时锁屏 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * LockerYear 锁屏年 + * LockerMonth 锁屏月 + * LockerDay 锁屏日 + * LockerHour 锁屏时 + * LockerMinute 锁屏分 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_TimeLocker", CharSet = CharSet.Unicode)] + public static extern int LV_TimeLocker(ref COMMUNICATIONINFO pCommunicationInfo, int LockerYear, int LockerMonth, int LockerDay, int LockerHour, int LockerMinute); + + /********************************************************************************************* + * LV_CancelLocker 取消定时锁屏 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_CancelLocker", CharSet = CharSet.Unicode)] + public static extern int LV_CancelLocker(ref COMMUNICATIONINFO pCommunicationInfo); + + /********************************************************************************************* + * LV_SetLedCommunicationParameter 设置LED通讯参数 + * + * 参数说明 + * pCommunicationInfo 通讯参数,赋值方式见COMMUNICATIONINFO结构体注示 + * pLedCommunicationParameter 详见LEDCOMMUNICATIONPARAMETER结构体注示 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_SetLedCommunicationParameter", CharSet = CharSet.Unicode)] + public static extern int LV_SetLedCommunicationParameter(ref COMMUNICATIONINFO pCommunicationInfo, ref LEDCOMMUNICATIONPARAMETER pLedCommunicationParameter); + /********************************************************************************************* + * LV_LedInitServer 启动控制卡心跳包服务 注:C2M、C4M才支持 + * + * 参数说明 + * port 监听的端口 + * 返回值 + * 0 成功 + * 非0 失败,调用LV_GetError来获取错误信息 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_LedInitServer", CharSet = CharSet.Unicode)] + public static extern int LV_LedInitServer(int port); + /********************************************************************************************* + * LV_LedShudownServer 断开控制卡心跳包服务 注:C2M、C4M才支持 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_LedShudownServer", CharSet = CharSet.Unicode)] + public static extern int LV_LedShudownServer(); + /********************************************************************************************* + * LV_RegisterLedServerCallback 注册回调函数 注:C2M、C4M才支持 + * + * 参数说明 + * serverCallback 回调函数 + ********************************************************************************************/ + [DllImport("lv_led_64.dll", EntryPoint = "LV_RegisterLedServerCallback", CharSet = CharSet.Unicode)] + public static extern int LV_RegisterLedServerCallback(SERVERINFOCALLBACK serverCallback); + + /********************************************************************************************* + * LV_GetError 获取错误信息(只支持中文) + * + * 参数说明 + * nErrCode 函数执行返回的错误代码 + * 返回值 + * 错误信息字符串 + ********************************************************************************************/ + public static string LS_GetError(int nErrCode) + { + string ErrStr = nErrCode switch + { + -1 => "无效的节目句柄。", + -2 => "节目已经存在。", + -3 => "指定的节目不存在。", + -4 => "定的区域不存在。", + -5 => "创建socket失败。", + -6 => "错误的回复包。", + -7 => "不支持的文件类型。", + -8 => "IP网关掩码或MAC字符串格式错误。", + -9 => "错误的波特率。", + -10 => "文件路径不存在。", + -11 => "区域重叠。", + -12 => "打开文件失败。", + -14 => "区域已存在。", + -15 => "无效的发送类型。", + -16 => "绘图失败。", + -17 => "创建文件夹失败。", + -30 => "打开串口失败。", + -31 => "设置串口超时失败。", + -32 => "设置串口缓冲区失败。", + -33 => "串口发送数据失败。", + -34 => "串口接收数据失败。", + -35 => "串口设置失败。", + -36 => "串口接收数据超时。", + -37 => "USB不支持群发。", + -38 => "发送取消。", + -100 => "网络连接失败。", + -101 => "网络发送失败。", + -102 => "网络接收数据失败。", + -103 => "bind失败。", + -104 => "无可用网卡。", + 0xc140 => "Logo与参屏大小不适应。", + 0xdaa3 => "控制器繁忙。", + 0xd5b0 => "固件程序型号不匹配。", + 0xd5b4 => "不是有效的固件程序。", + 0xdab8 => "节目颜色或屏宽高与控制卡屏参设定值不一致。", + 0xc1ba => "超出控制卡带载。", + 0xdab5 => "节目数据大小超过允许的最大值。", + _ => "未定义错误。", + }; + return ErrStr; + } + +} \ No newline at end of file diff --git a/Tools/LedSimple/LedSimple.csproj b/Tools/LedSimple/LedSimple.csproj new file mode 100644 index 0000000..e49f24c --- /dev/null +++ b/Tools/LedSimple/LedSimple.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + AnyCPU;x64;x86 + true + + + + + Always + + + Always + + + + diff --git a/Tools/LedSimple/lv_led_64.dll b/Tools/LedSimple/lv_led_64.dll new file mode 100644 index 0000000000000000000000000000000000000000..fabcd78d976792a20604386c30775b0dfbce7df5 GIT binary patch literal 616448 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
Ra&-+hl*>uQ7b`vN@0E8njC0xzw5 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�%{0 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

T| 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 z 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 + + + net8.0 + enable + enable + AnyCPU;x64;x86 + + + diff --git a/Tools/LogTool/WcsLog.cs b/Tools/LogTool/WcsLog.cs new file mode 100644 index 0000000..2286ebc --- /dev/null +++ b/Tools/LogTool/WcsLog.cs @@ -0,0 +1,104 @@ +using System.Text; + +namespace LogTool; + +public class WcsLog +{ + private static WcsLog? instance; + + public static WcsLog Instance() + { + instance ??= new WcsLog(); + return instance; + } + + + private static readonly object writeLog = new(); + ///

+ /// 通用写日志类 + /// + /// 文件夹名称 + /// 日志内容 + /// 是否在日志中自动添加时间 + public void WriteLog(string LogName, string strLog, bool addTime = true) + { + Task.Factory.StartNew(() => + { + lock (writeLog) + { + string LogAddress = AppDomain.CurrentDomain.BaseDirectory + "Log\\" + LogName; + DirectoryInfo di = new(LogAddress); + if (di.Exists == false) { di.Create(); } + StringBuilder logBuilder = new(); + if (addTime) + { + logBuilder.AppendLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}]"); + } + logBuilder.AppendLine($"{strLog}"); + string logFileName = LogAddress + "\\" + DateTime.Now.ToString("yyyy-MM-dd") + ".log"; + using FileStream fs = new(logFileName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + using StreamWriter sw = new(fs); + try + { + sw.Write(logBuilder); + } + catch (Exception ex) + { + _ = ex; + } + } + }); + } + + /// + /// 写系统日志 + /// + /// + public void WriteSystemLog(string strLog) => WriteLog("系统日志", strLog); + + /// + /// 写事件日志 + /// + /// + public void WriteEventLog(string strLog) => WriteLog("事件日志", strLog); + /// + /// 写事件日志 + /// + /// + public void WriteEventLog(StringBuilder strLog) => WriteLog("事件日志", strLog.ToString(), false); + + /// + /// 写入Tcp日志 + /// + /// + public void WriteTcpLog(StringBuilder strLog) => WriteLog("Tcp日志", strLog.ToString(), false); + + /// + /// 写接口日志 + /// + /// + public void WriteApiRequestLog(string strLog) => WriteLog("接口请求日志", strLog); + + /// + /// 写异常日志 + /// + /// + public void WriteExceptionLog(string strLog) => WriteLog("异常日志", strLog); + /// + /// 写异常日志 + /// + /// + public void WriteExceptionLog(StringBuilder strLog) => WriteLog("异常日志", strLog.ToString(), false); + + /// + /// 写数据库日志 + /// + /// + public void WriteSQLLog(string strLog) => WriteLog("数据库日志", strLog); + + /// + /// 写接口接收日志 + /// + /// + public void WriteApiAcceptLog(string strLog) => WriteLog("接口接收日志", strLog); +} \ No newline at end of file diff --git a/Tools/PlcTool/PlcTool.csproj b/Tools/PlcTool/PlcTool.csproj new file mode 100644 index 0000000..dc01f7f --- /dev/null +++ b/Tools/PlcTool/PlcTool.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + AnyCPU;x64;x86 + + + + + + + + diff --git a/Tools/PlcTool/Siemens/DataConvert.cs b/Tools/PlcTool/Siemens/DataConvert.cs new file mode 100644 index 0000000..ba3d8e6 --- /dev/null +++ b/Tools/PlcTool/Siemens/DataConvert.cs @@ -0,0 +1,72 @@ +using HslCommunication; +using HslCommunication.Profinet.Siemens; +using PlcTool.Siemens.Entity; + +namespace PlcTool.Siemens; + +/// +/// 数据转换类 +/// +public static class DataConvert +{ + + /// + /// 将string转化为西门子PLC类型 + /// 不满足条件的转化为1200 + /// 支持的string: + /// 1500 + /// 1200 + /// 300 + /// 400 + /// 200 + /// 200s + /// + /// + /// + public static SiemensPLCS ToPlcKind(this string? plcKandStr) + { + return plcKandStr switch + { + "1500" => SiemensPLCS.S1500, + "1200" => SiemensPLCS.S1200, + "400" => SiemensPLCS.S400, + "300" => SiemensPLCS.S300, + "200s" => SiemensPLCS.S200Smart, + "200" => SiemensPLCS.S200, + _ => SiemensPLCS.S1200, + }; + } + + /// + /// PLC读写固定返回类 + /// 返回一个新的类, + /// + /// + /// + public static SemS7Result ToSemS7Result(this OperateResult operateResult) + { + return new SemS7Result() + { + Success = operateResult.IsSuccess, + ErrCode = operateResult.ErrorCode, + Message = operateResult.Message + }; + } + /// + /// PLC读写固定返回类 + /// 返回一个新的类, + /// + /// + /// + /// + public static SemS7Result ToSemS7Result(this OperateResult operateResult) + { + return new SemS7Result() + { + Success = operateResult.IsSuccess, + ErrCode = operateResult.ErrorCode, + Message = operateResult.Message, + Value = operateResult.Content + }; + } +} \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/Entity/PlcDBName.cs b/Tools/PlcTool/Siemens/Entity/PlcDBName.cs new file mode 100644 index 0000000..defbf4f --- /dev/null +++ b/Tools/PlcTool/Siemens/Entity/PlcDBName.cs @@ -0,0 +1,13 @@ +namespace PlcTool.Siemens.Entity; + +/// +/// DB 信息 +/// +public class PlcDBName +{ + public uint? PlcId { get; set; } + + public string? DBName { get; set; } + + public string? DBAddress { get; set; } +} \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/Entity/SemS7Result.cs b/Tools/PlcTool/Siemens/Entity/SemS7Result.cs new file mode 100644 index 0000000..65cd207 --- /dev/null +++ b/Tools/PlcTool/Siemens/Entity/SemS7Result.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; +using System.Text; + +namespace PlcTool.Siemens.Entity; + +public class SemS7Result +{ + + public bool Success { get; set; } = false; + + public int? ErrCode { get; set; } + + public string? Message { get; set; } + + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } +} + +/// +/// PLC操作的返回泛型类 +/// +/// +public class SemS7Result : SemS7Result +{ + public T? Value { get; set; } + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } +} \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/Entity/SiemensS7Connection.cs b/Tools/PlcTool/Siemens/Entity/SiemensS7Connection.cs new file mode 100644 index 0000000..59eb467 --- /dev/null +++ b/Tools/PlcTool/Siemens/Entity/SiemensS7Connection.cs @@ -0,0 +1,19 @@ +using HslCommunication.Profinet.Siemens; + +namespace PlcTool.Siemens.Entity; + +/// +/// PLC 连接信息类 +/// +public class SiemensS7Connection +{ + /// + /// PLC 的编号 + /// + public int? PlcId { get; set; } + + /// + /// 连接信息 + /// + public SiemensS7Net? SiemensS7 { get; set; } +} \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/PLCAttribute/PlcDBAddressAttribute.cs b/Tools/PlcTool/Siemens/PLCAttribute/PlcDBAddressAttribute.cs new file mode 100644 index 0000000..2b0233e --- /dev/null +++ b/Tools/PlcTool/Siemens/PLCAttribute/PlcDBAddressAttribute.cs @@ -0,0 +1,7 @@ +namespace PlcTool.Siemens.PLCAttribute; + +/// +/// 用于标记 PLC 通讯地址 +/// +[AttributeUsage(AttributeTargets.Property)] +public class PlcDBAddressAttribute : Attribute { } \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/PLCAttribute/PlcDBNameAttribute.cs b/Tools/PlcTool/Siemens/PLCAttribute/PlcDBNameAttribute.cs new file mode 100644 index 0000000..6489a52 --- /dev/null +++ b/Tools/PlcTool/Siemens/PLCAttribute/PlcDBNameAttribute.cs @@ -0,0 +1,7 @@ +namespace PlcTool.Siemens.PLCAttribute; + +/// +/// 用于标记 PLC 通讯地址的名称 +/// +[AttributeUsage(AttributeTargets.Property)] +public class PlcDBNameAttribute : Attribute { } \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/PLCAttribute/PlcIPAttribute.cs b/Tools/PlcTool/Siemens/PLCAttribute/PlcIPAttribute.cs new file mode 100644 index 0000000..33db43b --- /dev/null +++ b/Tools/PlcTool/Siemens/PLCAttribute/PlcIPAttribute.cs @@ -0,0 +1,7 @@ +namespace PlcTool.Siemens.PLCAttribute; + +/// +/// 用于标记 PLC 的IP +/// +[AttributeUsage(AttributeTargets.Property)] +public class PlcIPAttribute : Attribute { } \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/PLCAttribute/PlcIdAttribute.cs b/Tools/PlcTool/Siemens/PLCAttribute/PlcIdAttribute.cs new file mode 100644 index 0000000..9ca844b --- /dev/null +++ b/Tools/PlcTool/Siemens/PLCAttribute/PlcIdAttribute.cs @@ -0,0 +1,7 @@ +namespace PlcTool.Siemens.PLCAttribute; + +/// +/// 用于标记 PLC 的编号 +/// +[AttributeUsage(AttributeTargets.Property)] +public class PlcIdAttribute : Attribute { } \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/PLCAttribute/PlcKindAttribute.cs b/Tools/PlcTool/Siemens/PLCAttribute/PlcKindAttribute.cs new file mode 100644 index 0000000..2f6b7ec --- /dev/null +++ b/Tools/PlcTool/Siemens/PLCAttribute/PlcKindAttribute.cs @@ -0,0 +1,16 @@ +namespace PlcTool.Siemens.PLCAttribute; + +/// +/// 用于标记PLC的系列 +/// +/// +/// 支持的string: +/// 1500 +/// 1200 +/// 300 +/// 400 +/// 200 +/// 200s +/// +[AttributeUsage(AttributeTargets.Property)] +public class PlcKindAttribute : Attribute { } \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/PLCAttribute/RackAttribute.cs b/Tools/PlcTool/Siemens/PLCAttribute/RackAttribute.cs new file mode 100644 index 0000000..a2fa896 --- /dev/null +++ b/Tools/PlcTool/Siemens/PLCAttribute/RackAttribute.cs @@ -0,0 +1,7 @@ +namespace PlcTool.Siemens.PLCAttribute; + +/// +/// plc 机架 +/// +[AttributeUsage(AttributeTargets.Property)] +public class RackAttribute : Attribute { } \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/PLCAttribute/SlotAttribute.cs b/Tools/PlcTool/Siemens/PLCAttribute/SlotAttribute.cs new file mode 100644 index 0000000..4d33bb4 --- /dev/null +++ b/Tools/PlcTool/Siemens/PLCAttribute/SlotAttribute.cs @@ -0,0 +1,7 @@ +namespace PlcTool.Siemens.PLCAttribute; + +/// +/// plc 插槽 +/// +[AttributeUsage(AttributeTargets.Property)] +public class SlotAttribute : Attribute { } \ No newline at end of file diff --git a/Tools/PlcTool/Siemens/SiemensS7.cs b/Tools/PlcTool/Siemens/SiemensS7.cs new file mode 100644 index 0000000..993af77 --- /dev/null +++ b/Tools/PlcTool/Siemens/SiemensS7.cs @@ -0,0 +1,842 @@ +using System.Reflection; +using System.Text; +using HslCommunication; +using HslCommunication.Profinet.Siemens; +using PlcTool.Siemens.Entity; +using PlcTool.Siemens.PLCAttribute; + +namespace PlcTool.Siemens; + +public class SiemensS7 +{ + + /* + * 作者:菻蔃 + * + * 版本时间:2023年4月15日 + * + */ + + private const string tip = "致其他开发者,此组件为收费组件,若您有意使用,请自行联系作者购买授权,擅自使用本公司激活码将承担责任"; + + /// + /// 存储PLC连接 + /// + readonly List siemensS7Connections = []; + + /// + /// 存储DB块具体在哪个PLC中 + /// + readonly List plcDBNames = []; + + + + /// + /// 操作PLC的类,传入PLC类型,地址,和组件激活码。 + /// + /// + /// + public SiemensS7(string? authorizationCode = null) + { + if (string.IsNullOrEmpty(authorizationCode)) + { + /*致本公司开发者,此为 10.6.1 版本激活码,若激活失败,尝试使用此版本*/ + /*致其他开发者,此组件为收费组件,若您有意使用,请自行联系作者购买授权,擅自使用本公司激活码将承担责任*/ + authorizationCode = "f562cc4c-4772-4b32-bdcd-f3e122c534e3"; + } + bool isAuthorization = Authorization.SetAuthorizationCode(authorizationCode); + if (!isAuthorization) + { + /*这里抛出异常,一定记得提醒开发者注册*/ + throw new Exception("组件未激活,上述激活码可能会因为版本更新而失效,请联系公司获取最新激活码,测试时可注释此行,未激活将只能使用24小时,切记上线前一定记得激活!!!"); + } + } + + /// + /// 添加PLC信息 + /// + /// + /// + /// + public SiemensS7 SetPlcs(List plcs) where T : class + { + if (plcs == null || plcs.Count == 0) + { + throw new Exception("传入数据为空。"); + } + Type type = typeof(T); + var properties = type.GetProperties(); + /*定义两个存储属性的变量*/ + PropertyInfo? plcIdProperty = null; + PropertyInfo? plcIPProperty = null; + PropertyInfo? plcKindProperty = null; + PropertyInfo? rackProperty = null; + PropertyInfo? soltindProperty = null; + /*轮询查找到的属性给变量赋值*/ + foreach (var property in properties) + { + var plcIdattributes = property.GetCustomAttributes(typeof(PlcIdAttribute), false); + if (plcIdattributes.Length != 0) + { + plcIdProperty = property; + } + var plcIPattributes = property.GetCustomAttributes(typeof(PlcIPAttribute), false); + if (plcIPattributes.Length != 0) + { + plcIPProperty = property; + } + var plcKindattributes = property.GetCustomAttributes(typeof(PlcKindAttribute), false); + if (plcKindattributes.Length != 0) + { + plcKindProperty = property; + } + var rackattributes = property.GetCustomAttributes(typeof(RackAttribute), false); + if (rackattributes.Length != 0) + { + rackProperty = property; + } + var soltattributes = property.GetCustomAttributes(typeof(SlotAttribute), false); + if (soltattributes.Length != 0) + { + soltindProperty = property; + } + } + /*判断是否有对应的特性*/ + if (plcIdProperty == null || plcIPProperty == null || plcKindProperty == null) + { + throw new Exception("未正确识别类中的属性特性,需要同时添加 PlcIdAttribution、PlcIPAttribute 和 PlcKindAttribute 三个特性。"); + } + /*添加对应的值*/ + foreach (T plc in plcs) + { + var plckind = plcKindProperty.GetValue(plc)!.ToString().ToPlcKind(); + var plcip = plcIPProperty.GetValue(plc)!.ToString(); + + SiemensS7Connection siemensS7Connection = new() + { + PlcId = Convert.ToInt32(plcIdProperty.GetValue(plc)), + SiemensS7 = new SiemensS7Net(plckind, plcip) + }; + siemensS7Connection.SiemensS7.Rack = Convert.ToByte(rackProperty!.GetValue(plc)); + siemensS7Connection.SiemensS7.Slot = Convert.ToByte(soltindProperty!.GetValue(plc)); + siemensS7Connections.Add(siemensS7Connection); + } + return this; + } + + /// + /// 添加PLC的Db地址信息 + /// + /// + /// + /// + public SiemensS7 SetPlcDB(List? dbNames) where T : class + { + if (dbNames == default || dbNames.Count == 0) + { + throw new Exception("传入的数据为空"); + } + Type type = typeof(T); + var properties = type.GetProperties(); + /*定义两个存储属性的变量*/ + PropertyInfo? plcIdProperty = null; + PropertyInfo? dbNameProperty = null; + PropertyInfo? dbAddressProperty = null; + /*轮询查找到的属性给变量赋值*/ + foreach (var property in properties) + { + var plcIdattributes = property.GetCustomAttributes(typeof(PlcIdAttribute), false); + if (plcIdattributes.Length != 0) + { + plcIdProperty = property; + } + var dbNameattributes = property.GetCustomAttributes(typeof(PlcDBNameAttribute), false); + if (dbNameattributes.Length != 0) + { + dbNameProperty = property; + } + var dbAddressattributes = property.GetCustomAttributes(typeof(PlcDBAddressAttribute), false); + if (dbAddressattributes.Length != 0) + { + dbAddressProperty = property; + } + } + /*判断是否有对应的特性*/ + if (plcIdProperty == null || dbNameProperty == null || dbAddressProperty == null) + { + throw new Exception("未正确识别类中的属性特性,需要同时添加 PlcIdAttribute、PlcDBNameAttribute、PlcDBAddressAttribute 三个特性。"); + } + /*添加对应的值*/ + foreach (T dbName in dbNames) + { + PlcDBName plcDBName = new() + { + PlcId = Convert.ToUInt32(plcIdProperty.GetValue(dbName)), + DBName = dbNameProperty.GetValue(dbName)!.ToString(), + }; + var dbaddress = dbAddressProperty.GetValue(dbName); + plcDBName.DBAddress = dbaddress == null ? "" : dbaddress.ToString(); + plcDBNames.Add(plcDBName); + } + return this; + } + + + /// + /// 连接PLC + /// + /// + /// 请使用异步重载方法 + /// + public SemS7Result ConnectPlcs() + { + if (siemensS7Connections.Count == 0) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = "没有设置PLC连接,请先调用 SetPlcs 方法。" + }; + } + /*连接PLC*/ + foreach (SiemensS7Connection siemensS7Connection in siemensS7Connections) + { + if (siemensS7Connection.SiemensS7 == default) + { + continue; + } + siemensS7Connection.SiemensS7.ConnectTimeOut = 2000; + var connectResult = siemensS7Connection.SiemensS7.ConnectServer(); + if (!connectResult.IsSuccess) + { + // 只要有一个连接失败,直接返回错误信息 + connectResult.Message = + $"PLC:{siemensS7Connection.SiemensS7.IpAddress} 连接失败,异常信息:{connectResult.Message}"; + return connectResult.ToSemS7Result(); + } + } + return new SemS7Result() + { + Success = true, + ErrCode = 0, + Message = "所有PLC连接成功" + }; + } + + #region 根据名称读写PLC数据 + + + /// + /// 根据名称写入PLC值,目前不支持 string + /// 写入前请提前转换好对应的数据格式 + /// + /// + /// + /// + public (SemS7Result result, byte[]? bytes) WritePlcWhithName(string plcDBName, params object[] values) + { + if (values.Length == 0) + { + return (new SemS7Result() + { + Success = false, + ErrCode = 997, + Message = $"写入失败,传入的值为空。" + }, default); + } + /*根据名称获取地址*/ + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return (new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }, default); + } + (Exception? Ex, byte[]? bytes) getByte = GetPlcBytes(values); + if (getByte.Ex != null) + { + return (new SemS7Result() + { + Success = false, + ErrCode = 996, + Message = $"{getByte.Ex.Message}" + }, default); + } + byte[]? bytes = getByte.bytes;//获取转化成功的数组 + OperateResult operateResult = siemensS7.Write(dbAddress, bytes); + return (operateResult.ToSemS7Result(), bytes); + } + + /// + /// 写入bool值 + /// + /// + /// + /// + public SemS7Result WriteBoolWhithName(string plcDBName, params bool[] values) + { + if (values.Length == 0) + { + return new SemS7Result() + { + Success = false, + ErrCode = 997, + Message = $"写入失败,传入的值为空。" + }; + } + /*根据名称获取地址*/ + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + OperateResult operateResult = siemensS7.Write(dbAddress, values); + return operateResult.ToSemS7Result(); + } + + /// + /// 根据地址写入值 + /// + /// /// + /// + /// + /// + public SemS7Result WritePlcWhithAddress(int plcNo, string plcDBAddress, params object[] values) + { + if (values.Length == 0) + { + return new SemS7Result() + { + Success = false, + ErrCode = 997, + Message = $"写入失败,传入的值为空。" + }; + } + /*根据名称获取地址*/ + SiemensS7Net? siemensS7 = GetSiemensS7(plcNo); + if (siemensS7 == default) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = "找不到该PLC连接" + }; + } + (Exception? Ex, byte[]? bytes) getByte = GetPlcBytes(values); + if (getByte.Ex != null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 996, + Message = $"{getByte.Ex.Message}" + }; + } + byte[]? bytes = getByte.bytes;//获取转化成功的数组 + OperateResult operateResult = siemensS7.Write(plcDBAddress, bytes); + return operateResult.ToSemS7Result(); + } + + + + /// + /// 根据名称写入PLC值,写入string + /// 写入前请提前转换好对应的数据格式 + /// + /// + /// + /// 自定的编码信息,一般System.Text.Encoding.ASCII即可,中文需要 Encoding.GetEncoding("gb2312") + /// + public SemS7Result WriteStringWhithName(string plcDBName, string value, Encoding encoding) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.Write(dbAddress, value, encoding).ToSemS7Result(); + } + + /// + /// 读取一个Byte数组 + /// + /// + /// + /// + public SemS7Result ReadByteWithName(string plcDBName, ushort length) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.Read(dbAddress, length).ToSemS7Result(); + } + + /// + /// 读取一个Byte + /// + /// + /// + public SemS7Result ReadByteWithName(string plcDBName) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + SemS7Result actionResult = ReadByteWithName(dbAddress!, 1); + if (!actionResult.Success) + { + return new SemS7Result() + { + Success = false, + ErrCode = actionResult.ErrCode, + Message = actionResult.Message + }; + } + + var values = actionResult.Value; + if (values == default || values.Length == 0) + { + return new SemS7Result() + { + Success = false, + ErrCode = actionResult.ErrCode, + Message = actionResult.Message + }; + } + return new SemS7Result() + { + Success = true, + ErrCode = actionResult.ErrCode, + Message = actionResult.Message, + Value = values[0] + }; + } + + + /// + /// 根据名称读取一定数量的Int16值 + /// 对应PLC数据类型为 W,int, + /// + /// + /// + /// + public SemS7Result ReadInt16WithName(string plcDBName, ushort length) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.ReadInt16(dbAddress, length).ToSemS7Result(); + } + + + + /// + /// 根据名称读取一个Int16值 + /// 对应PLC数据类型为 W,int, + /// + /// + /// + public SemS7Result ReadInt16WithName(string plcDBName) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.ReadInt16(dbAddress).ToSemS7Result(); + } + + /// + /// 根据名称读取一定数量的Int32值 + /// 对应PLC数据类型为 DW,Dint, + /// + /// + /// + /// + public SemS7Result ReadInt32WithName(string plcDBName, ushort length) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.ReadInt32(dbAddress, length).ToSemS7Result(); + } + + /// + /// 根据名称读取一个Int32值 + /// 对应PLC数据类型为 DW,Dint, + /// + /// + /// + public SemS7Result ReadInt32WithName(string plcDBName) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.ReadInt32(dbAddress).ToSemS7Result(); + } + + public SemS7Result ReadUInt32WithName(string plcDBName) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.ReadUInt32(dbAddress).ToSemS7Result(); + } + + /// + /// 根据名称读取一定数量的bool值 + /// 对应PLC数据类型为 X + /// + /// + /// /// + /// + public SemS7Result ReadBoolWithName(string plcDBName, ushort length) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.ReadBool(dbAddress, length).ToSemS7Result(); + } + + /// + /// 根据名称读取一个Int32值 + /// 对应PLC数据类型为 X + /// + /// + /// + public SemS7Result ReadBoolWithName(string plcDBName) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.ReadBool(dbAddress).ToSemS7Result(); + } + + /// + /// 根据名称读取string值 + /// 对应PLC数据类型为 string + /// + /// + /// + /// 自定的编码信息,一般System.Text.Encoding.ASCII即可,中文需要 Encoding.GetEncoding("gb2312") + /// + public SemS7Result ReadStringWithName(string plcDBName, ushort length, Encoding encoding) + { + (SiemensS7Net? siemensS7, string? dbAddress, string? errText) = GetSiemensS7(plcDBName); + if (siemensS7 == null) + { + return new SemS7Result() + { + Success = false, + ErrCode = 999, + Message = errText + }; + } + return siemensS7.ReadString(dbAddress, length, encoding).ToSemS7Result(); + } + + + /// + /// 根据DB名称查找其对应的连接 + /// + /// + /// + private (SiemensS7Net? siemensS7, string? dbAddress, string? errText) GetSiemensS7(string dbName) + { + if (plcDBNames.Count == 0 || siemensS7Connections.Count == 0) + { + return (null, null, "未设置 plc 或者 未设置 DB地址,请调用 SetPlcs 方法设置 plc 或者 调用 SetPlcDB 方法设置 DB地址"); + } + /*找出该地址对应的ID*/ + PlcDBName? plcDBName = plcDBNames.Find(f => f.DBName == dbName); + if (plcDBName == default) + { + return (null, null, "该DB地址不存在,请核实wcs数据录入是否正确"); + } + /*找出该ID对应的连接*/ + SiemensS7Connection? siemensS7Connection = siemensS7Connections.Find(f => f.PlcId == plcDBName.PlcId); + if (siemensS7Connection == default) + { + return (null, null, "该PLC连接不存在,请核实wcs数据录入是否正确"); + } + return (siemensS7Connection.SiemensS7, plcDBName.DBAddress, null); + } + + + /// + /// 根据plcNo 返回连接信息 + /// + /// + /// + private SiemensS7Net? GetSiemensS7(int plcNo) + { + /*找出该ID对应的连接*/ + SiemensS7Connection? siemensS7Connection = siemensS7Connections.Find(f => f.PlcId == plcNo); + if (siemensS7Connection == default) + { + return default; + } + return siemensS7Connection.SiemensS7; + } + + #endregion + + + //NetworkDoubleBase dataTransfrom = new NetworkDoubleBase(); + private readonly SiemensS7Net dataTransfrom = new(SiemensPLCS.S1500); + + #region 将 byte 数组转化为值 + + + /// + /// 将数组转化为值 + /// + /// + /// + /// + /// + public object? Trans(byte[] bytes, int index) where T : struct + { + Type type = typeof(T); + if (type == typeof(short)) + { + return dataTransfrom.ByteTransform.TransInt16(bytes, index); + } + else if (type == typeof(int)) + { + return dataTransfrom.ByteTransform.TransInt32(bytes, index); + } + else if (type == typeof(bool)) + { + return dataTransfrom.ByteTransform.TransBool(bytes, index); + } + else if (type == typeof(double)) + { + return dataTransfrom.ByteTransform.TransDouble(bytes, index); + } + else if (type == typeof(float)) + { + return dataTransfrom.ByteTransform.TransSingle(bytes, index); + } + else if (type == typeof(uint)) + { + return dataTransfrom.ByteTransform.TransUInt32(bytes, index); + } + else if (type == typeof(ushort)) + { + return dataTransfrom.ByteTransform.TransUInt16(bytes, index); + } + else + { + return null; // 不支持的数据类型 + } + + } + + #endregion + + + #region 将传入的值转化成 byte 数组 + /// + /// 将传入的值转化成byte数组 + /// + /// + /// + public (Exception? Ex, byte[]? bytes) GetPlcBytes(object[] values) + { + List bytes = []; + foreach (object value in values) + { + Type type = value.GetType(); + if (type == typeof(short)) + { + short va = Convert.ToInt16(value); + byte[] data = dataTransfrom.ByteTransform.TransByte(va); + bytes.AddRange(data); + } + else if (type == typeof(int)) + { + int va = Convert.ToInt32(value); + byte[] data = dataTransfrom.ByteTransform.TransByte(va); + bytes.AddRange(data); + } + else if (type == typeof(bool)) + { + bool va = Convert.ToBoolean(value); + byte[] data = dataTransfrom.ByteTransform.TransByte(va); + bytes.AddRange(data); + } + else if (type == typeof(double)) + { + double va = Convert.ToDouble(value); + byte[] data = dataTransfrom.ByteTransform.TransByte(va); + bytes.AddRange(data); + } + else if (type == typeof(float)) + { + float va = Convert.ToSingle(value); + byte[] data = dataTransfrom.ByteTransform.TransByte(va); + bytes.AddRange(data); + } + else if (type == typeof(uint)) + { + uint va = Convert.ToUInt32(value); + byte[] data = dataTransfrom.ByteTransform.TransByte(va); + bytes.AddRange(data); + } + else if (type == typeof(ushort)) + { + ushort va = Convert.ToUInt16(value); + byte[] data = dataTransfrom.ByteTransform.TransByte(va); + bytes.AddRange(data); + } + else + { + return (new Exception($"传入的数据中有不支持的数据类型 {type.Name} "), null); + } + } + return (null, [.. bytes]); + } + + #endregion + + + #region 根据名称和偏移量返回地址 + + /// + /// 根据名称和偏移量返回地址 + /// + /// + /// + /// + public string GetAddressWithNameAndBit(string dbName, int moveBit) + { + /* + * 1、判断有几个点 + * 有一个点的不是bool值 + * 有两个点的是bool值 + * 必须写全,如DB100等价于DB100.0,但不允许写DB100,因为容易识别错误 + */ + if (plcDBNames.Count == 0) + { + return string.Empty; // 没有注册DB地址, + } + /*找出该地址对应的ID*/ + PlcDBName? plcDBName = plcDBNames.Find(f => f.DBName == dbName); + if (plcDBName == default) + { + return string.Empty; // 找不到DB地址 + } + + string startDBAddress = plcDBName.DBAddress ?? string.Empty; + string[] dbInfos = startDBAddress.Split('.'); + if (dbInfos.Length == 2) + { + // 只有一个点,判断为非Bool值 + int startPoint = Convert.ToInt32(dbInfos[1]); + string newDBAddress = $"{dbInfos[0]}.{startPoint + moveBit}"; + return newDBAddress; + } + else if (dbInfos.Length == 3) + { + // 有两个点,判断为bool值 + int bigAddress = Convert.ToInt32(dbInfos[1]); + int smallAddress = Convert.ToInt32(dbInfos[2]); + while (moveBit > 0) + { + smallAddress++; + if (smallAddress >= 8) + { + bigAddress++; + smallAddress = 0; + } + moveBit--; + } + + return $"{dbInfos[0]}.{bigAddress}.{smallAddress}"; + } + + return string.Empty; + } + + + #endregion + + + +} \ No newline at end of file diff --git a/Tools/SocketTool/Entity/ExecuteResult.cs b/Tools/SocketTool/Entity/ExecuteResult.cs new file mode 100644 index 0000000..bbcd00a --- /dev/null +++ b/Tools/SocketTool/Entity/ExecuteResult.cs @@ -0,0 +1,68 @@ +namespace SocketTool.Entity; + +public class ExecuteResult +{ + public ExecuteResult() { } + + public ExecuteResult(bool? isSuccess, string? errMeg) + { + IsSuccess = isSuccess; + ErrorMessage = errMeg; + } + /// + /// 是否成功 + /// + public bool? IsSuccess + { + get; + set; + } + /// + /// 错误信息 + /// + public string? ErrorMessage + { + get; + set; + } + /// + /// 返回信息 + /// + public object? ReturnInfo + { + get; + set; + } +} + +/// +/// 信息的返回类 +/// +/// 返回的结果泛型 +public class ExecuteResult where T : class +{ + /// + /// 是否成功 + /// + public bool? IsSuccess + { + get; + set; + } + /// + /// 错误信息 + /// + public string? ErrorMessage + { + get; + set; + } + /// + /// 返回信息 + /// + public T? ReturnInfo + { + get; + set; + } +} \ No newline at end of file diff --git a/Tools/SocketTool/Entity/ScanCodeClass.cs b/Tools/SocketTool/Entity/ScanCodeClass.cs new file mode 100644 index 0000000..2883314 --- /dev/null +++ b/Tools/SocketTool/Entity/ScanCodeClass.cs @@ -0,0 +1,44 @@ +namespace SocketTool.Entity; + +public class ScanCodeClass +{ + /// + /// 传入数据的IP地址 + /// + public string? IpAddress + { + get; + set; + } + /// + /// 整数性扫码编号,请勿混淆 + /// + public int ScanID + { + get; + set; + } + + /// + /// 条码 + /// + public string? Code + { + get; + set; + } + + /// + /// 字符串格式的扫码编号,请勿混淆 + /// + public string? StrScanID + { + get; + set; + } + + public override string ToString() + { + return $"({StrScanID}){Code}"; + } +} \ No newline at end of file diff --git a/Tools/SocketTool/Entity/SocketModel.cs b/Tools/SocketTool/Entity/SocketModel.cs new file mode 100644 index 0000000..a4d3473 --- /dev/null +++ b/Tools/SocketTool/Entity/SocketModel.cs @@ -0,0 +1,41 @@ +using System.Net; +using System.Net.Sockets; + +namespace SocketTool.Entity; + +public class SocketModel +{ + + /// + /// socket信息 + /// + public Socket? Socket + { + get; + set; + } + + /// + /// 连接的远端IP + /// + public IPEndPoint? HostEP { get; set; } + + /// + /// 这个socket所在的线程 + /// + public Thread? Thread + { + get; + set; + } + + /// + /// 连接的socket地址 + /// + public string? SocketIp { get; set; } + + /// + /// 是否连接 + /// + public bool IsConnected { get; set; } +} \ No newline at end of file diff --git a/Tools/SocketTool/SocketClient.cs b/Tools/SocketTool/SocketClient.cs new file mode 100644 index 0000000..c325dbc --- /dev/null +++ b/Tools/SocketTool/SocketClient.cs @@ -0,0 +1,362 @@ +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using SocketTool.Entity; + +namespace SocketTool; + +/// +/// socket 客户端 .net6 版 +/// +public class SocketClient +{ + #region 全局变量 + + private List NeedConnectSockets = []; // 需要连接的socket + + int IPCount;//传入的连接数 + + #endregion + + #region 全局属性 + /// + /// 字符串解析格式 + /// + private Encoding? Encod { get; set; } + #endregion + + /// + /// socket连接类,自带自动重连功能 + /// 传入参数示例:127.0.0.1:8090 + /// + /// + /// + /// + public SocketClient(Encoding? encoding, params string?[] IPPorts) + { + Encod = encoding; + IPCount = IPPorts.Length;//数量赋值 + foreach (string? IPPort in IPPorts) + { + if (string.IsNullOrEmpty(IPPort)) + { + continue; + } + string ipAndPort = IPPort.Replace(":", ":");//将中文冒号替换成英文冒号 + if (!ipAndPort.Contains(':')) + { + // 不含有端口号的直接抛异常,必须处理 + throw new Exception($"地址:{IPPort} 格式不正确,必须处理以后才能正常运行。"); + } + string[] ipp = ipAndPort.Split(':'); + if (ipp.Length != 2 || string.IsNullOrEmpty(ipp[0]) || string.IsNullOrEmpty(ipp[1])) + { + // 拆分后没有地址或者端口号的直接抛异常,必须处理 + throw new Exception($"地址:{IPPort} 格式不正确,必须处理以后才能正常运行。"); + } + string IP = ipp[0]; + int Port = CheckIP(IP, ipp[1]); + if (Port != 0) + { + Socket socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + NeedConnectSockets.Add(new SocketModel() + { + Socket = socket, + SocketIp = IPPort, + IsConnected = false, + HostEP = new IPEndPoint(IPAddress.Parse(IP), Port) + }); + } + } + } + /// + /// 检查IP地址和端口号是否满足格式 + /// 如果格式错误返回0,否则返回Int格式的端口号 + /// + /// + /// + /// + private static int CheckIP(string? IP, string? Portstr) + { + if (string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Portstr)) + { + throw new Exception("传入的IP或者端口存在异常"); + } + int Port;//int格式的端口号 + try + { + Port = Convert.ToInt32(Portstr); + } + catch + { + return 0; + } + string CheckString = "^((1[0-9][0-9]\\.)|(2[0-4][0-9]\\.)|(25[0-5]\\.)|([1-9][0-9]\\.)|([0-9]\\.)){3}((1[0-9][0-9])|(2[0-4][0-9])|(25[0-5])|([1-9][0-9])|([0-9]))$"; + bool IsIPOK = Regex.IsMatch(IP, CheckString); + if (!IsIPOK || Port < 1 || Port > 65535) + { + throw new Exception($"地址:{IP} 的端口号:{Portstr} 格式不正确,必须处理以后才能正常运行。"); + } + return Port; + } + + /// + /// 连接事件,需要调用 + /// + public void Connect() + { + if (NeedConnectSockets.Count < 1) + { + return; + } + // 连接 socket + foreach (SocketModel needConnectSocket in NeedConnectSockets) + { + Thread connectSocket = new(() => + { + ConnectSocket(needConnectSocket); + }) + { + IsBackground = true + }; + connectSocket.Start(); + //Task.Factory.StartNew(()=>ConnectSocket(needConnectSocket)); + } + // 启用重连 + Thread reConnectSocket = new(ReConnectSocket) + { + IsBackground = true + }; + reConnectSocket.Start(); + //Task.Factory.StartNew(ReConnectSocket); + } + /// + /// 连接 socket + /// + /// + private void ConnectSocket(SocketModel socketInfo) + { + while (true)//此循环经本人考虑再三后加上,去掉此循环将导致=>在服务端未开启的情况下开启客户端,后面服务端开启,虽然可以通过下方重连连上服务端,但无法接受数据 + { + try + { + SocketConnecting?.Invoke(socketInfo.SocketIp ?? "");//触发连接中事件 + socketInfo.Socket!.Connect(socketInfo.HostEP!);//尝试连接 + SocketOnline?.Invoke(socketInfo.SocketIp ?? "");//触发连接成功事件 + socketInfo.Thread = Thread.CurrentThread; + socketInfo.IsConnected = true; + break; + } + catch (Exception ex) + { + SocketConnectFail?.Invoke(socketInfo.SocketIp ?? "", ex);//出发连接中事件 + Thread.Sleep(2000); + } + } + Socket socket = socketInfo.Socket; // 拉取地址 + while (true) + { + byte[] recvBytes = new byte[1024 * 1024]; + try + { + int bytes = socket.Receive(recvBytes, recvBytes.Length, SocketFlags.None); + if (bytes <= 0) continue; + GetTcpData?.BeginInvoke(recvBytes, socketInfo.SocketIp ?? "", null, null); + if (GetTcpData != null) continue; + string? recvStr = Encod?.GetString(recvBytes, 0, bytes); //此处编码方式请根据服务端发送的编码方式修改对应,否则可能造成部分字符乱码 + if (string.IsNullOrEmpty(recvStr)) continue; + if (recvStr.Trim().Length > 0) + { + GetSocketMessage?.Invoke(recvStr, socketInfo.SocketIp ?? ""); + if (HandleCodeInfo != null) + { + ManCode(recvStr, socketInfo.SocketIp ?? ""); //处理条码并触发其他事件 + } + } + } + catch (Exception ex) + { + _ = ex; + RecevErrEvent?.Invoke(socketInfo.SocketIp ?? "", ex); + socketInfo.IsConnected = false; + return; + } + } + } + + + /// + /// 重新连接 Socket + /// + private void ReConnectSocket() + { + Thread.Sleep(5000); + while (true) + { + foreach (SocketModel needConnectSocket in NeedConnectSockets) + { + Socket socket = needConnectSocket.Socket!; + Thread.Sleep(300); + try + { + try + { + socket.SendTimeout = 1000; + socket.Send(Encoding.ASCII.GetBytes(" ")); + if (!socket.Connected) + { + needConnectSocket.IsConnected = false; + } + } + catch (Exception ex) + { + _ = ex.ToString(); + SocketOffline?.Invoke(needConnectSocket.SocketIp ?? ""); //触发掉线事件 + try + { + socket.Close(); //关闭已经断开的socket + } + catch (Exception exception) + { + _ = exception; + } + + Thread connectSocket = new(() => + { + ConnectSocket(needConnectSocket); + }) + { + IsBackground = true + }; + connectSocket.Start(); + //Task.Factory.StartNew(() => ConnectSocket(needConnectSocket)); + } + } + catch (Exception ex) + { + _ = ex.ToString(); + } + } + } + + } + + + + + + /// + /// 处理收到的条码,并引发事件 + /// + /// + /// + private void ManCode(string? code, string? IPAddress) + { + //多线程处理,增加程序运行速度 + ThreadPool.QueueUserWorkItem((useless) => + { + List? scanCodeClasses = SplitCode(code, IPAddress); + if (scanCodeClasses != default) + { + foreach (ScanCodeClass scc in scanCodeClasses) + { + HandleCodeInfo?.Invoke(scc); + } + } + }); + } + /// + /// 拆条码,将前缀和条码拆下来 + /// + /// + /// + /// + private List? SplitCode(string? GetCode, string? Ipaddress) + { + if (string.IsNullOrEmpty(GetCode) || string.IsNullOrEmpty(Ipaddress)) + { + return default; + } + if (!GetCode.Contains('(')) + { + return default; + } + List sccs = []; + string[] GetCodeCuts = GetCode.Split('('); + foreach (string GetCodeCut in GetCodeCuts) + { + if (GetCodeCut == "" || GetCodeCut.Trim().Length == 0 || !GetCodeCut.Contains(')')) { continue; } + string[] ScanCode = GetCodeCut.Split(')'); + if (ScanCode.Length < 2) { continue; } + ScanCodeClass scc = new() + { + IpAddress = Ipaddress, + ScanID = Convert.ToInt32(ScanCode[0]), + StrScanID = ScanCode[0], + Code = ScanCode[1] + }; + sccs.Add(scc); + } + return sccs; + } + + #region 事件 + + public delegate void HandleInfo(ScanCodeClass scc); + /// + /// 条码处理事件, + /// ScanID或者ScanIDStr为null的是没有前缀的条码 + /// + public event HandleInfo? HandleCodeInfo; + + + + public delegate void GetMessage(string Message, string IPAddress); + /// + /// 条码处理事件, + /// + public event GetMessage? GetSocketMessage; + + + public delegate void GetData(byte[] data, string IPAddress); + /// + /// 字节返回事件 + /// + public event GetData? GetTcpData; + + + public delegate void OffLine(string IPAddress); + /// + /// 当有地址掉线时触发 + /// + public event OffLine? SocketOffline; + + public delegate void OnLine(string IPAddress); + /// + /// 当地址连接成功时触发 + /// + public event OnLine? SocketOnline; + + public delegate void Connecting(string IPAddress); + /// + /// 当地址连接正在连接时触发 + /// + public event Connecting? SocketConnecting; + + public delegate void ConnectFail(string IPAddress, Exception ex); + /// + /// 当地址连接正在连接时触发 + /// + public event ConnectFail? SocketConnectFail; + + public delegate void RecevErr(string IPAddress, Exception ex); + /// + /// 当地址连接接收数据异常时触发 + /// + public event RecevErr? RecevErrEvent; + #endregion + + + +} \ No newline at end of file diff --git a/Tools/SocketTool/SocketTool.csproj b/Tools/SocketTool/SocketTool.csproj new file mode 100644 index 0000000..22e1b2e --- /dev/null +++ b/Tools/SocketTool/SocketTool.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + AnyCPU;x64;x86 + + + diff --git a/WcsMain/.config/dotnet-tools.json b/WcsMain/.config/dotnet-tools.json new file mode 100644 index 0000000..6b93cca --- /dev/null +++ b/WcsMain/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "7.0.3", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/WcsMain/ApiClient/DataEntity/WmsEntity/ApplyInRequest.cs b/WcsMain/ApiClient/DataEntity/WmsEntity/ApplyInRequest.cs new file mode 100644 index 0000000..a96c3da --- /dev/null +++ b/WcsMain/ApiClient/DataEntity/WmsEntity/ApplyInRequest.cs @@ -0,0 +1,28 @@ +namespace WcsMain.ApiClient.DataEntity.WmsEntity; + +/// +/// 申请入库 +/// +public class ApplyInRequest +{ + /// + /// 申请点位 + /// + public string? Point { get; set; } + + /// + /// 载具号 + /// + public string? VehicleNo { get; set; } + + /// + /// 条码信息 + /// + public string? CodeMessage { get; set; } + + /// + /// 备注 + /// + public string? Remark { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiClient/DataEntity/WmsEntity/SendWmsTaskStatusRequest.cs b/WcsMain/ApiClient/DataEntity/WmsEntity/SendWmsTaskStatusRequest.cs new file mode 100644 index 0000000..16600c6 --- /dev/null +++ b/WcsMain/ApiClient/DataEntity/WmsEntity/SendWmsTaskStatusRequest.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiClient.DataEntity.WmsEntity; + +/// +/// 发送WMS任务状态请求实体 +/// +public class SendWmsTaskStatusRequest +{ + /// + /// 任务号 + /// + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 任务状态 + /// + [JsonPropertyName("taskStatus")] + public int? TaskStatus { get; set; } + + /// + /// 任务类型 + /// + [JsonPropertyName("destination")] + public string? Destination { get; set; } + + /// + /// 载具号 + /// + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 信息 + /// + [JsonPropertyName("message")] + public string? Message { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiClient/DataEntity/WmsEntity/UploadPickStandRequest.cs b/WcsMain/ApiClient/DataEntity/WmsEntity/UploadPickStandRequest.cs new file mode 100644 index 0000000..07be159 --- /dev/null +++ b/WcsMain/ApiClient/DataEntity/WmsEntity/UploadPickStandRequest.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiClient.DataEntity.WmsEntity; + +/// +/// 上传箱子到达拣选口的请求类 +/// +public class UploadPickStandRequest +{ + /// + /// 载具号 + /// + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 拣选站台 + /// + [JsonPropertyName("pickStand")] + public string? PickStand { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiClient/DataEntity/WmsEntity/WmsResponse.cs b/WcsMain/ApiClient/DataEntity/WmsEntity/WmsResponse.cs new file mode 100644 index 0000000..ba6fec1 --- /dev/null +++ b/WcsMain/ApiClient/DataEntity/WmsEntity/WmsResponse.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiClient.DataEntity.WmsEntity; + +public class WmsResponse +{ + /// + /// 代码 + /// + [JsonPropertyName("code")] + public int? Code { get; set; } + + /// + /// 信息 + /// + [JsonPropertyName("message")] + public string? Message { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/ControllerFilter/ExceptionFilter/WcsExceptionFilterAttribute.cs b/WcsMain/ApiServe/ControllerFilter/ExceptionFilter/WcsExceptionFilterAttribute.cs new file mode 100644 index 0000000..bed7e56 --- /dev/null +++ b/WcsMain/ApiServe/ControllerFilter/ExceptionFilter/WcsExceptionFilterAttribute.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using WcsMain.ApiServe.Factory; + +namespace WcsMain.ApiServe.ControllerFilter.ExceptionFilter; + +/// +/// Wcs 接口异常处理 +/// +public class WcsExceptionFilterAttribute : ExceptionFilterAttribute +{ + public override void OnException(ExceptionContext context) + { + base.OnException(context); + if (context.ExceptionHandled == false) + { + var response = WcsApiResponseFactory.ServiceErr($"服务器异常,参考信息:{context.Exception}"); + context.Result = new ObjectResult(response) + { + // 返回状态码设置为200,表示成功 + StatusCode = StatusCodes.Status200OK, + }; + } + context.ExceptionHandled = true; + } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/ControllerFilter/ResponseFilterAttribute.cs b/WcsMain/ApiServe/ControllerFilter/ResponseFilterAttribute.cs new file mode 100644 index 0000000..d9819c8 --- /dev/null +++ b/WcsMain/ApiServe/ControllerFilter/ResponseFilterAttribute.cs @@ -0,0 +1,118 @@ +using LogTool; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Newtonsoft.Json; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.DataService; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.ControllerFilter; + +/// +/// 过滤器,记录日志数据 +/// +[Component] +public class ResponseFilterAttribute(AppApiAcceptDao acceptDao, DataBaseData dataBaseData) : ActionFilterAttribute +{ + private readonly DataBaseData _dataBaseData = dataBaseData; + private readonly AppApiAcceptDao _acceptDao = acceptDao; + + public override void OnActionExecuting(ActionExecutingContext context) + { + base.OnActionExecuting(context); + //请求的方法,GET,POST + string requestMethod = context.HttpContext.Request.Method.ToUpper(); + //记录进入请求的时间 + context.RouteData.Values.Add("StartTime", DateTime.Now); + string requestStr = string.Empty; // 记录请求值 + switch (requestMethod) + { + case "GET": + requestStr = JsonConvert.SerializeObject(context.ActionArguments); + break; + case "POST": + bool isHaveRequestObj = context.ActionArguments.TryGetValue("request", out object? requestObj); + if (isHaveRequestObj) + { + requestStr = JsonConvert.SerializeObject(requestObj); + } + break; + case "PUT": + goto case "GET"; + case "PUSH": + goto case "GET"; + case "DELETE": + goto case "GET"; + } + context.RouteData.Values.Add("requestData", requestStr); + } + + + public override void OnActionExecuted(ActionExecutedContext context) + { + base.OnActionExecuted(context); + + /* 获取返回值,封装成JSON返回 */ + ObjectResult? response = (ObjectResult?)context.Result; + var responseValue = response?.Value; + string responseStr = JsonConvert.SerializeObject(responseValue); // 返回的JSON字符串 + + /* 获取请求值,记下请求日志 */ + HttpRequest request = context.HttpContext.Request; + AppApiAccept accept = new() + { + AcceptId = _dataBaseData.GetNewUUID(), + Path = request.Path.ToString(), + Method = request.Method, + MediaType = request.ContentType, + ClientAddress = + $"{context.HttpContext.Connection.RemoteIpAddress}:{context.HttpContext.Connection.RemotePort}", + }; + + RouteValueDictionary keyValues = request.RouteValues; + // 获取请求时间,和当前时间比较,计算接口执行时间 + bool isHaveStartTime = keyValues.TryGetValue("StartTime", out object? startTimeObj); + DateTime endTime = DateTime.Now; + if (isHaveStartTime) + { + DateTime startTime = (DateTime)(startTimeObj ?? new DateTime(1900, 1, 1)); + accept.RequestTime = startTime; + accept.ResponseTime = endTime; + TimeSpan span = endTime - startTime; + double useTime = span.TotalMilliseconds; //接口用时 + accept.UseTime = useTime; + } + else + { + accept.RequestTime = new DateTime(1900, 1, 1); + accept.ResponseTime = endTime; + accept.UseTime = 0; + } + // 获取请求数据,和返回数据 + var isHaveRequestObject = keyValues.TryGetValue("requestData", out object? requestObj); + if (isHaveRequestObject) + { + string reqMsg = $"{requestObj}"; + accept.RequestMsg = reqMsg; + } + else + { + accept.RequestMsg = "未抓取到请求数据。"; + } + string respMsg = $"{responseStr}"; + accept.ResponseMsg = respMsg; + + if (context.Exception != null) + { + accept.ErrMsg = context.Exception.Message; + } + Task.Factory.StartNew(() => + { + WcsLog.Instance().WriteApiAcceptLog(accept.ToString()); + _acceptDao.Insert(accept); // 插入数据库 + }); + } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/ControllerFilter/WmsApiExceptionFilterAttribute.cs b/WcsMain/ApiServe/ControllerFilter/WmsApiExceptionFilterAttribute.cs new file mode 100644 index 0000000..38fafbc --- /dev/null +++ b/WcsMain/ApiServe/ControllerFilter/WmsApiExceptionFilterAttribute.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity; + +namespace WcsMain.ApiServe.ControllerFilter; + +/// +/// 全局异常处理 +/// +public class WmsApiExceptionFilterAttribute : ExceptionFilterAttribute +{ + + public override void OnException(ExceptionContext context) + { + base.OnException(context); + if (context.ExceptionHandled == false) + { + WmsApiResponse exceptionResponse = new() + { + Code = 9999, + Message = $"您的请求发生异常,请检查数据格式或联系我们,将参考信息提供给我们,参考信息:{context.Exception}" + }; + context.Result = new ObjectResult(exceptionResponse) + { + // 返回状态码设置为200,表示成功 + StatusCode = StatusCodes.Status200OK, + }; + } + context.ExceptionHandled = true; + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/Equipment/ConveyStatusResponse.cs b/WcsMain/ApiServe/Controllers/Dto/Equipment/ConveyStatusResponse.cs new file mode 100644 index 0000000..b598241 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/Equipment/ConveyStatusResponse.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.Equipment; + +/// +/// 输送设备信息 ---- 卡特专用 +/// +public class ConveyStatusResponse +{ + /// + /// 输送机编号,名称 + /// + [JsonPropertyName("conveyNo")] + public string? ConveyNo { get; set; } + + /// + /// 是否含有货物 + /// + [JsonPropertyName("existGoods")] + public bool? ExistGoods { get; set; } + + /// + /// 是否含有AGV + /// + [JsonPropertyName("existAGV")] + public bool? ExistAGV { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/Equipment/PickStandInfoResponse.cs b/WcsMain/ApiServe/Controllers/Dto/Equipment/PickStandInfoResponse.cs new file mode 100644 index 0000000..8ef8c8e --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/Equipment/PickStandInfoResponse.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.Equipment; + +/// +/// 拣选站台信息 +/// +public class PickStandInfoResponse +{ + /// + /// 站台号 + /// + [JsonPropertyName("standId")] + public string? StandId { get; set; } + + /// + /// 箱子号 + /// + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/Equipment/StackerStatusResponse.cs b/WcsMain/ApiServe/Controllers/Dto/Equipment/StackerStatusResponse.cs new file mode 100644 index 0000000..d0b6316 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/Equipment/StackerStatusResponse.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.Equipment; + +/// +/// 堆垛机状态返回实体类 +/// +public class StackerStatusResponse +{ + /// + /// 堆垛机编号 + /// + [JsonPropertyName("stackerNo")] + public string? StackerNo { get; set; } + + /// + /// 堆垛机控制方式 + /// + [JsonPropertyName("controlModel")] + public string? ControlModel { get; set; } + + /// + /// 堆垛机状态 + /// + [JsonPropertyName("onlineStatus")] + public string? OnlineStatus { get; set; } + + /// + /// 堆垛机任务号 + /// + [JsonPropertyName("plcId")] + public string? PlcId { get; set; } + + /// + /// 堆垛机报警编号 + /// + [JsonPropertyName("errCode")] + public string? ErrCode { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WMSEntity/Equipment/QueryStandStatusRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/Equipment/QueryStandStatusRequest.cs new file mode 100644 index 0000000..7f2ed7e --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/Equipment/QueryStandStatusRequest.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WMSEntity.Equipment +{ + public class QueryStandStatusRequest + { + /// + /// 站台类型 + /// + [JsonPropertyName("standType")] + public int? StandType { get; set; } + } +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WMSEntity/Equipment/QueryStandStatusResponse.cs b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/Equipment/QueryStandStatusResponse.cs new file mode 100644 index 0000000..a26ceaa --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/Equipment/QueryStandStatusResponse.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WMSEntity.Equipment +{ + public class QueryStandStatusResponse + { + /// + /// 响应时间 + /// + [JsonPropertyName("responseTime")] + public string? ResponseTime { get; set; } + + /// + /// 动作允许 + /// + [JsonPropertyName("allowAction")] + public bool? AllowAction { get; set; } + + /// + /// 信息 + /// + [JsonPropertyName("msg")] + public string? Msg { get; set; } + } +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsApiResponse.cs b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsApiResponse.cs new file mode 100644 index 0000000..b566169 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsApiResponse.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WMSEntity; + +/// +/// WMS的固定返回类 +/// +public class WmsApiResponse +{ + /// + /// 请求ID + /// + [JsonPropertyName("code")] + public int? Code { get; set; } + + /// + /// 信息 + /// + [JsonPropertyName("message")] + public string? Message { get; set; } + + +} +/// +/// Api统一回复类 +/// +/// +public class WmsApiResponse : WmsApiResponse where T : class +{ + /// + /// 返回数据 + /// + [JsonPropertyName("returnData")] + public T? ReturnData { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/DisposeStandRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/DisposeStandRequest.cs new file mode 100644 index 0000000..afb56cd --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/DisposeStandRequest.cs @@ -0,0 +1,36 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WMSEntity.WmsTask; + +/// +/// wms 推送站台拣选完成 +/// +public class DisposeStandRequest +{ + + /// + /// 拣选站台 + /// + [DataRules] + [JsonPropertyName("pickStand")] + public string? PickStand { get; set; } + + + /// + /// 拣选料箱 + /// + [DataRules] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 状态 + /// + [DataRules] + [JsonPropertyName("status")] + public int? Status { get; set; } + + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/GetStackerRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/GetStackerRequest.cs new file mode 100644 index 0000000..b4b191f --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/GetStackerRequest.cs @@ -0,0 +1,70 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WMSEntity.WmsTask; + +/// +/// SetStackerTask 接口的请求类 +/// 对应方法名称为 GetStackerTask ---- WMS向WCS发送堆垛机任务 +/// +public class GetStackerRequest +{ + /// + /// 任务ID + /// + [DataRules] + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 任务类型 + /// + [DataRules] + [JsonPropertyName("taskType")] + public int? TaskType { get; set; } + + /// + /// 任务起点 + /// + [JsonPropertyName("origin")] + public string? Origin { get; set; } + + /// + /// 优先级 + /// + [JsonPropertyName("priority")] + public int? Priority { get; set; } + + /// + /// 中间点 + /// + [JsonPropertyName("midpoint")] + public string? Midpoint { get; set; } + + /// + /// 任务终点 + /// + [JsonPropertyName("destination")] + public string? Destination { get; set; } + + /// + /// 载具号 + /// + [DataRules] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 载具尺寸 + /// + [JsonPropertyName("vehicleSize")] + public int? VehicleSize { get; set; } + + /// + /// 载具重量 + /// + [JsonPropertyName("weight")] + public decimal? Weight { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/SetPickTaskRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/SetPickTaskRequest.cs new file mode 100644 index 0000000..dcd7502 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/SetPickTaskRequest.cs @@ -0,0 +1,30 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WMSEntity.WmsTask; + +/// +/// SetPickTask 接口的请求实体类 +/// +public class SetPickTaskRequest +{ + /// + /// 载具号 + /// + [DataRules(false, "^.+$")] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 拣选站台 + /// + [DataRules] + [JsonPropertyName("pickStand")] + public List? PickStand { get; set; } + + /// + /// 备注信息 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/UpdateStackerTaskStatusRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/UpdateStackerTaskStatusRequest.cs new file mode 100644 index 0000000..4b7d8d1 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WMSEntity/WmsTask/UpdateStackerTaskStatusRequest.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; +using DataCheck; + +namespace WcsMain.ApiServe.Controllers.Dto.WMSEntity.WmsTask; + +/// +/// UpdateStackerTaskStatus 接口的请求类 +/// +public class UpdateStackerTaskStatusRequest +{ + /// + /// 任务编号 + /// + [DataRules] + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 状态类型 + /// + /// + /// 0 —— 重置任务状态 + /// 3 —— 完成任务 + /// 999 —— 删除任务 + /// + [DataRules] + [JsonPropertyName("taskStatus")] + public int? TaskStatus { get; set; } + + /// + /// 目的地,WMS 重置任务时候会要求更改目的地 + /// + [JsonPropertyName("destination")] + public string? Destination { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsApiResponse.cs b/WcsMain/ApiServe/Controllers/Dto/WcsApiResponse.cs new file mode 100644 index 0000000..c1c77da --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsApiResponse.cs @@ -0,0 +1,57 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto; + +/// +/// Api统一回复类 +/// +public class WcsApiResponse +{ + /// + /// 响应代码 + /// + [JsonPropertyName("code")] + public int Code { get; set; } + + /// + /// 响应信息 + /// + [JsonPropertyName("msg")] + public string Msg { get; set; } = string.Empty; + + +} + +/// +/// Api统一回复类 +/// +/// +public class WcsApiResponse : WcsApiResponse where T : class +{ + /// + /// 返回数据 + /// + [JsonPropertyName("returnData")] + public T? ReturnData { get; set; } +} + + + +/// +/// Api统一回复类 +/// +/// +public class WcsApiResponse : WcsApiResponse where T1 : new() where T2 : class +{ + /// + /// 备用 + /// + [JsonPropertyName("tag")] + public T1? Tag { get; set; } + + /// + /// 返回数据 + /// + [JsonPropertyName("returnData")] + public T2? ReturnData { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/ApiAccept/GetApiAcceptWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ApiAccept/GetApiAcceptWithPageRequest.cs new file mode 100644 index 0000000..ac0f62f --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ApiAccept/GetApiAcceptWithPageRequest.cs @@ -0,0 +1,41 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.ApiAccept; + +public class GetApiAcceptWithPageRequest +{ + + /// + /// 模糊查询字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchStr { get; set; } + + /// + /// 查询时间范围 + /// + [JsonPropertyName("timeRange")] + public List? TimeRange { get; set; } + + /// + /// 分页信息 + /// + [JsonPropertyName("page")] + public ApiAcceptPage? Page { get; set; } +} + +public class ApiAcceptPage +{ + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/ApiRequest/GetApiRequestWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ApiRequest/GetApiRequestWithPageRequest.cs new file mode 100644 index 0000000..37b8f8c --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ApiRequest/GetApiRequestWithPageRequest.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.ApiRequest; + +public class GetApiRequestWithPageRequest +{ + /// + /// 模糊查询字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchStr { get; set; } + + /// + /// 查询时间范围 + /// + [JsonPropertyName("timeRange")] + public List? TimeRange { get; set; } + + /// + /// 分页信息 + /// + [JsonPropertyName("page")] + public ApiRequestPage? Page { get; set; } +} + +public class ApiRequestPage +{ + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Config/EditeConfigRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Config/EditeConfigRequest.cs new file mode 100644 index 0000000..2fb2347 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Config/EditeConfigRequest.cs @@ -0,0 +1,48 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Config; + +public class EditeConfigRequest +{ + /// + /// 配置键 + /// + [DataRules] + [JsonPropertyName("configKey")] + public string? ConfigKey { get; set; } + + /// + /// 配置名称 + /// + [JsonPropertyName("configName")] + public string? ConfigName { get; set; } + + /// + /// 配置值 + /// + [JsonPropertyName("configValue")] + public string? ConfigValue { get; set; } + + /// + /// 配置类型 + /// + [JsonPropertyName("configType")] + public string? ConfigType { get; set; } + + /// + /// 备注信息 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + /// + /// 是否是编辑模式,、 + /// + /// + /// 编辑模式表示更新数据, + /// + [JsonPropertyName("isEdite")] + public bool IsEdite { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Config/GetConfigWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Config/GetConfigWithPageRequest.cs new file mode 100644 index 0000000..2e92006 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Config/GetConfigWithPageRequest.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Config; + + +/// +/// 分页请求获取配置项的请求参数 +/// +public class GetConfigWithPageRequest +{ + /// + /// 查询字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchString { get; set; } + + /// + /// 页面大小 + /// + [JsonPropertyName("pageSize")] + public int? PageSize { get; set; } + + /// + /// 页面页码 + /// + [JsonPropertyName("pageIndex")] + public int? PageIndex { get; set; } + + + +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/CusPickTask/UpdatePickTaskRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/CusPickTask/UpdatePickTaskRequest.cs new file mode 100644 index 0000000..61d18ab --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/CusPickTask/UpdatePickTaskRequest.cs @@ -0,0 +1,34 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.CusPickTask; + +public class UpdatePickTaskRequest +{ + /// + /// 记录号 + /// + [DataRules] + [JsonPropertyName("recordId")] + public string? RecordId { get; set; } + + /// + /// 载具号 + /// + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 拣选站台 + /// + [JsonPropertyName("pickStand")] + public string? PickStand { get; set; } + + /// + /// 是否拣选完成 + /// + [DataRules] + [JsonPropertyName("pickStatus")] + public bool? PickStatus { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/EditeDBRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/EditeDBRequest.cs new file mode 100644 index 0000000..791e749 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/EditeDBRequest.cs @@ -0,0 +1,36 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.DB; + +public class EditeDBRequest +{ + /// + /// PLCID + /// + [JsonPropertyName("plcId")] + public int? PlcId { get; set; } + + /// + /// DB名称 + /// + [JsonPropertyName("dbName")] + public string? DbName { get; set; } + + /// + /// DB地址 + /// + [JsonPropertyName("dbAddress")] + public string? DbAddress { get; set; } + + /// + /// 是否系统级别 + /// + [JsonPropertyName("isSystem")] + public int? IsSystem { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/GetDBWithPlcNameRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/GetDBWithPlcNameRequest.cs new file mode 100644 index 0000000..131cd9f --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/GetDBWithPlcNameRequest.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.DB; + + +/// +/// 返回PLCDB块同时返回 PLC名称的请求实体 +/// +public class GetDBWithPlcNameRequest +{ + /// + /// DB 名称 + /// + [JsonPropertyName("dbName")] + public string? DBName { get; set; } + + /// + /// plc编号 + /// + [JsonPropertyName("plcId")] + public int? PlcId { get; set; } + + + + +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/GetDBWithPlcNameResponse.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/GetDBWithPlcNameResponse.cs new file mode 100644 index 0000000..482e137 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/DB/GetDBWithPlcNameResponse.cs @@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.DB; + +/// +/// 返回PLCDB块同时返回 PLC名称的响应实体 +/// +public class GetDBWithPlcNameResponse +{ + /// + /// plc ID + /// + [JsonPropertyName("plcId")] + public int? PlcId { get; set; } + + /// + /// plc 名称 + /// + [JsonPropertyName("plcName")] + public string? PlcName { get; set;} + + /// + /// db的名称 + /// + [JsonPropertyName("dbName")] + public string? DBName { get; set; } + + /// + /// db 地址 + /// + [JsonPropertyName("dbAddress")] + public string? DBAddress { get; set; } + + /// + /// 是否系统级别 + /// + [JsonPropertyName("isSystem")] + public int? IsSystem { get; set;} + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + + + +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/AddTaskInfoRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/AddTaskInfoRequest.cs new file mode 100644 index 0000000..73ec728 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/AddTaskInfoRequest.cs @@ -0,0 +1,83 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; + +public class AddTaskInfoRequest +{ + /// + /// 任务号 + /// + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 任务组 + /// + [JsonPropertyName("taskGroup")] + public string? TaskGroup { get; set; } + + /// + /// 点位 + /// + [DataRules] + [JsonPropertyName("location")] + public string? Location { get; set; } + + /// + /// 订单号 + /// + [JsonPropertyName("orderId")] + public string? OrderId { get; set; } + + /// + /// 载具号 + /// + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 物料编号 + /// + [JsonPropertyName("goodsId")] + public string? GoodsId { get; set; } + + /// + /// 物料名称 + /// + [JsonPropertyName("goodsName")] + public string? GoodsName { get; set; } + + /// + /// 任务状态 + /// + [JsonPropertyName("taskStatus")] + public int? TaskStatus { get; set; } + + /// + /// 需求数量 + /// + [DataRules] + [JsonPropertyName("needNum")] + public int? NeedNum { get; set; } + + /// + /// 拣选数量 + /// + [JsonPropertyName("pickNum")] + public int? PickNum { get; set; } + + + /// + /// 创建人 + /// + [JsonPropertyName("createPerson")] + public string? CreatePerson { get; set; } + + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/EditTaskInfoRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/EditTaskInfoRequest.cs new file mode 100644 index 0000000..4efd811 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/EditTaskInfoRequest.cs @@ -0,0 +1,77 @@ +using DataCheck; +using SqlSugar; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; + +public class EditTaskInfoRequest +{ + /// + /// 任务号 + /// + [DataRules] + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 点位 + /// + [JsonPropertyName("location")] + public string? Location { get; set; } + + /// + /// 订单号 + /// + [JsonPropertyName("orderId")] + public string? OrderId { get; set; } + + /// + /// 载具号 + /// + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 物料编号 + /// + [JsonPropertyName("goodsId")] + public string? GoodsId { get; set; } + + /// + /// 物料名称 + /// + [JsonPropertyName("goodsName")] + public string? GoodsName { get; set; } + + /// + /// 任务状态 + /// + [JsonPropertyName("taskStatus")] + public int? TaskStatus { get; set; } + + /// + /// 需求数量 + /// + [JsonPropertyName("needNum")] + public int? NeedNum { get; set; } + + /// + /// 拣选数量 + /// + [JsonPropertyName("pickNum")] + public int? PickNum { get; set; } + + + /// + /// 创建人 + /// + [JsonPropertyName("createPerson")] + public string? CreatePerson { get; set; } + + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/GetStackerTaskNewDestinationRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/GetStackerTaskNewDestinationRequest.cs new file mode 100644 index 0000000..972bac3 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/GetStackerTaskNewDestinationRequest.cs @@ -0,0 +1,31 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; + +public class GetStackerTaskNewDestinationRequest +{ + + /// + /// 任务号 + /// + [DataRules] + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 新终点 + /// + [DataRules] + [JsonPropertyName("destination")] + public string? Destination { get; set; } + + /// + /// 载具号 + /// + [DataRules] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/QueryTaskRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/QueryTaskRequest.cs new file mode 100644 index 0000000..1f52c31 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/QueryTaskRequest.cs @@ -0,0 +1,53 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; + +public class QueryTaskRequest +{ + + /// + /// 查询的字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchStr { get; set; } + + /// + /// 任务类型 + /// + [JsonPropertyName("elTagTaskType")] + public List? TaskType { get; set; } + + /// + /// 任务状态 + /// + [JsonPropertyName("elTagTaskStatus")] + public List? TaskStatus { get; set; } + + /// + /// 查询时间范围 + /// + [JsonPropertyName("timeRange")] + public List? TimeRange { get; set; } + + /// + /// 分页信息 + /// + [JsonPropertyName("page")] + public ElTagTaskPage? Page { get; set; } +} + +public class ElTagTaskPage +{ + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } + +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/ShowNumRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/ShowNumRequest.cs new file mode 100644 index 0000000..f4e23b5 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/ElTag/ShowNumRequest.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; + +public class ShowNumRequest +{ + /// + /// 标签名称 + /// + [JsonPropertyName("tagName")] + public int? TagName { get; set; } + + /// + /// 标签显示的数字 + /// + [JsonPropertyName("value")] + public int? Num { get; set; } + + +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Equipment/ResetStackerRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Equipment/ResetStackerRequest.cs new file mode 100644 index 0000000..20623b9 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Equipment/ResetStackerRequest.cs @@ -0,0 +1,14 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Equipment; + +public class ResetStackerRequest +{ + /// + /// 堆垛机编号 + /// + [DataRules] + [JsonPropertyName("stackerId")] + public string? StackerId { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Equipment/StackerContinueRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Equipment/StackerContinueRequest.cs new file mode 100644 index 0000000..702d7e7 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Equipment/StackerContinueRequest.cs @@ -0,0 +1,14 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Equipment; + +public class StackerContinueRequest +{ + /// + /// 堆垛机编号 + /// + [DataRules] + [JsonPropertyName("stackerId")] + public string? StackerId { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Location/GetLocationWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Location/GetLocationWithPageRequest.cs new file mode 100644 index 0000000..8a39603 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Location/GetLocationWithPageRequest.cs @@ -0,0 +1,54 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Location; + +/// +/// GetLocationWithPage 接口请求类 +/// +public class GetLocationWithPageRequest +{ + + /// + /// 查询的字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchStr { get; set; } + + /// + /// 点位状态 + /// + [JsonPropertyName("locationStatus")] + public List? LocationStatus { get; set; } + + /// + /// 点位类型 + /// + [JsonPropertyName("locationType")] + public List? LocationType { get; set; } + + /// + /// 分页信息 + /// + [JsonPropertyName("page")] + public LocationPage? Page { get; set; } + + + +} + + +public class LocationPage +{ + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Location/UpdateLocationRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Location/UpdateLocationRequest.cs new file mode 100644 index 0000000..7570b83 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Location/UpdateLocationRequest.cs @@ -0,0 +1,90 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Location; + +public class UpdateLocationRequest +{ + /// + /// Wcs点位 + /// + [JsonPropertyName("wcsLocation")] + public string? WcsLocation { get; set; } + + /// + /// Wms点位,该列主要用于映射 + /// + [JsonPropertyName("wmsLocation")] + public string? WmsLocation { get; set; } + + /// + /// 巷道编号 + /// + [JsonPropertyName("tunnelNo")] + public int? TunnelNo { get; set; } + + /// + /// 设备编号 + /// + [JsonPropertyName("equipmentId")] + public int? EquipmentId { get; set; } + + /// + /// 点位状态 + /// + [JsonPropertyName("locationStatus")] + public int? LocationStatus { get; set; } + + /// + /// 排 + /// + [JsonPropertyName("queue")] + public int? Queue { get; set; } + + /// + /// 列 + /// + [JsonPropertyName("line")] + public int? Line { get; set; } + + /// + /// 层 + /// + [JsonPropertyName("layer")] + public int? Layer { get; set; } + + /// + /// 深 + /// + [JsonPropertyName("depth")] + public int? Depth { get; set; } + + /// + /// 点位类型 + /// + [JsonPropertyName("locationType")] + public int? LocationType { get; set; } + + /// + /// 载具编号 + /// + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 修改时间 + /// + [JsonPropertyName("modifyTime")] + public DateTime? ModifyTime { get; set; } + + /// + /// 说明信息 + /// + [JsonPropertyName("explain")] + public string? Explain { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/AddMenuRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/AddMenuRequest.cs new file mode 100644 index 0000000..52e48f1 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/AddMenuRequest.cs @@ -0,0 +1,60 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Menu; + +public class AddMenuRequest +{ + /// + /// 主菜单序号 + /// + [JsonPropertyName("mainMenuIndex")] + public string? MainMenuIndex { get; set; } + + /// + /// 主菜单名称 + /// + [JsonPropertyName("mainMenuName")] + public string? MainMenuName { get; set; } + + /// + /// 主菜单图标 + /// + [JsonPropertyName("mainMenuIco")] + public string? MainMenuIco { get; set; } + + /// + /// 次菜单序号 + /// + [JsonPropertyName("minorMenuIndex")] + public string? MinorMenuIndex { get; set; } + + /// + /// 次菜单名称 + /// + [JsonPropertyName("minorMenuName")] + public string? MinorMenuName { get; set; } + + /// + /// 次菜单图标 + /// + [JsonPropertyName("minorMenuIco")] + public string? MinorMenuIco { get; set; } + + /// + /// 次菜单路由 + /// + [JsonPropertyName("minorMenuRouter")] + public string? MinorMenuRouter { get; set; } + + /// + /// 菜单状态 ---- 1 表示可以使用 + /// + [JsonPropertyName("menuStatus")] + public int? MenuStatus { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/GetMenuWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/GetMenuWithPageRequest.cs new file mode 100644 index 0000000..1b070bb --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/GetMenuWithPageRequest.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Menu; + +public class GetMenuWithPageRequest +{ + /// + /// 分页信息 + /// + [JsonPropertyName("page")] + public MenuPage? Page { get; set; } + +} + +public class MenuPage +{ + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } + + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/UpdateMenuRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/UpdateMenuRequest.cs new file mode 100644 index 0000000..2bc0c62 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Menu/UpdateMenuRequest.cs @@ -0,0 +1,61 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Menu; + +public class UpdateMenuRequest +{ + /// + /// 主菜单序号 + /// + [JsonPropertyName("mainMenuIndex")] + public string? MainMenuIndex { get; set; } + + /// + /// 主菜单名称 + /// + [JsonPropertyName("mainMenuName")] + public string? MainMenuName { get; set; } + + /// + /// 主菜单图标 + /// + [JsonPropertyName("mainMenuIco")] + public string? MainMenuIco { get; set; } + + /// + /// 次菜单序号 + /// + [JsonPropertyName("minorMenuIndex")] + public string? MinorMenuIndex { get; set; } + + /// + /// 次菜单名称 + /// + [JsonPropertyName("minorMenuName")] + public string? MinorMenuName { get; set; } + + /// + /// 次菜单图标 + /// + [JsonPropertyName("minorMenuIco")] + public string? MinorMenuIco { get; set; } + + /// + /// 次菜单路由 + /// + [JsonPropertyName("minorMenuRouter")] + public string? MinorMenuRouter { get; set; } + + /// + /// 菜单状态 ---- 1 表示可以使用 + /// + [JsonPropertyName("menuStatus")] + public int? MenuStatus { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/PLC/EditePLCRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/PLC/EditePLCRequest.cs new file mode 100644 index 0000000..cee2cb9 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/PLC/EditePLCRequest.cs @@ -0,0 +1,59 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.PLC; + +/// +/// 修改 PLC 请求实体类 +/// +public class EditePLCRequest +{ + /// + /// PLC的编号 + /// + [JsonPropertyName("plcId")] + [DataRules] + public int? PlcId { get; set; } + + /// + /// PLC 的IP + /// + [JsonPropertyName("plcIp")] + public string? PlcIp { get; set; } + + /// + /// PLC 的名称 + /// + [JsonPropertyName("plcName")] + public string? PlcName { get; set; } + + /// + /// 机架 + /// + [JsonPropertyName("rack")] + public int? Rack { get; set; } + + /// + /// 插槽 + /// + [JsonPropertyName("slot")] + public int? Slot { get; set; } + + /// + /// PLC的类型 + /// + [JsonPropertyName("plcKind")] + public string? PlcKind { get; set; } + + /// + /// PLC的状态 + /// + [JsonPropertyName("plcStatus")] + public int? PlcStatus { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Settings/EditSettingsRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Settings/EditSettingsRequest.cs new file mode 100644 index 0000000..328148a --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Settings/EditSettingsRequest.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Settings; + +/// +/// 编辑设置项的请求类 +/// +public class EditSettingsRequest +{ + /// + /// 设置键 + /// + [JsonPropertyName("settingKey")] + public string? SettingKey { get; set; } + + /// + /// 设置名称 + /// + [JsonPropertyName("settingName")] + public string? SettingName { get; set; } + + /// + /// 设置值 + /// + [JsonPropertyName("settingValue")] + public string? SettingValue { get; set; } + + /// + /// 设置类型 + /// + [JsonPropertyName("settingType")] + public string? SettingType { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Socket/EditSocketRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Socket/EditSocketRequest.cs new file mode 100644 index 0000000..e93fc04 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Socket/EditSocketRequest.cs @@ -0,0 +1,44 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Socket; + +/// +/// 编辑 socket 的请求类 +/// +public class EditSocketRequest +{ + /// + /// socket编号,唯一 + /// + [DataRules] + [JsonPropertyName("socketNo")] + public string? SocketNo { get; set; } + + /// + /// socket的IP端口 + /// + [DataRules] + [JsonPropertyName("socketIpPort")] + public string? SocketIpPort { get; set; } + + /// + /// socket的状态 + /// + [DataRules] + [JsonPropertyName("socketStatus")] + public string? SocketStatus { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + /// + /// 是否处于编辑模式 + /// + [DataRules] + [JsonPropertyName("isEdite")] + public bool? IsEdite { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Stacker/EditStackerRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Stacker/EditStackerRequest.cs new file mode 100644 index 0000000..a4b5a3a --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Stacker/EditStackerRequest.cs @@ -0,0 +1,63 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Stacker; + + +public class EditStackerRequest +{ + + /// + /// 设备编号 + /// + [DataRules] + [JsonPropertyName("stackerId")] + public int? StackerId { get; set; } + + /// + /// 堆垛机名称 + /// + [JsonPropertyName("stackerName")] + public string? StackerName { get; set; } + + /// + /// 堆垛机状态 + /// + /// + /// 0 - 禁用 + /// 1 - 启用 + /// + [DataRules] + [JsonPropertyName("stackerStatus")] + public int? StackerStatus { get; set; } + + /// + /// 货叉状态 + /// + [JsonPropertyName("forkStatus")] + public string? ForkStatus { get; set; } + + /// + /// 控制该堆垛机的PLC + /// + [JsonPropertyName("actionPlc")] + public int? ActionPlc { get; set; } + + /// + /// 入库站台,格式 : 101-102-103 + /// + [JsonPropertyName("inStand")] + public string? InStand { get; set; } + + /// + /// 出库站台,格式 : 101-102-103 + /// + [JsonPropertyName("outStand")] + public string? OutStand { get; set; } + + /// + /// 备注 + /// + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/Stacker/GetStackerStatusResponse.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Stacker/GetStackerStatusResponse.cs new file mode 100644 index 0000000..2e36547 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/Stacker/GetStackerStatusResponse.cs @@ -0,0 +1,101 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.Stacker; + +/// +/// 查询堆垛机状态的返回实体 +/// +public class GetStackerStatusResponse +{ + + /// + /// 设备编号 + /// + [JsonPropertyName("stackerId")] + public int? StackerId { get; set; } + + /// + /// 堆垛机名称 + /// + [JsonPropertyName("stackerName")] + public string? StackerName { get; set; } + + /// + /// 堆垛机状态 + /// + /// + /// 0 - 禁用 + /// 1 - 启用 + /// + [JsonPropertyName("stackerStatus")] + public int? StackerStatus { get; set; } + + /// + /// 货叉状态 + /// + [JsonPropertyName("forkStatus")] + public string? ForkStatus { get; set; } + + /// + /// 查询结果 + /// + [JsonPropertyName("msg")] + public string? Message { get; set; } = "-"; + + /// + /// 当前运行任务号 + /// + [JsonPropertyName("plcId")] + public string? PlcId { get; set; } + + /// + /// 控制方式 + /// + [JsonPropertyName("controlModel")] + public string? ControlModel { get; set; } = "0"; + + /// + /// 设备状态 + /// + [JsonPropertyName("stackerStatusEquip")] + public string? StackerStatusEquip { get; set; } = "0"; + + /// + /// 排 + /// + [JsonPropertyName("queue")] + public int? Queue { get; set; } + + /// + /// 列 + /// + [JsonPropertyName("line")] + public int? Line { get; set; } + + /// + /// 层 + /// + [JsonPropertyName("layer")] + public int? Layer { get; set; } + + /// + /// 深 + /// + [JsonPropertyName("depth")] + public int? Depth { get; set; } + + /// + /// 条码 + /// + [JsonPropertyName("code")] + public string? Code { get; set; } + + /// + /// 报警编号 + /// + [JsonPropertyName("errCode")] + public int? ErrCode { get; set; } + + + +} diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/SystemController/GetSysMsgWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/SystemController/GetSysMsgWithPageRequest.cs new file mode 100644 index 0000000..1335e27 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/SystemController/GetSysMsgWithPageRequest.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.SystemController; + +public class GetSysMsgWithPageRequest +{ + + /// + /// 查询字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchStr { get; set; } + + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/SystemController/LogRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/SystemController/LogRequest.cs new file mode 100644 index 0000000..7891830 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/SystemController/LogRequest.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; +using DataCheck; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.SystemController; + +public class LogRequest +{ + /// + /// log类型 + /// + [JsonPropertyName("logType")] + [DataRules] + public string? LogType { get; set; } + + /// + /// log文件名 + /// + [JsonPropertyName("logFileName")] + [DataRules] + public string? LogFileName { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/GetUserWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/GetUserWithPageRequest.cs new file mode 100644 index 0000000..9c960e2 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/GetUserWithPageRequest.cs @@ -0,0 +1,45 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.User; + +public class GetUserWithPageRequest +{ + /// + /// 查询的字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchStr { get; set; } + + /// + /// 用户状态 + /// + [JsonPropertyName("userStatus")] + public List? UserStatus { get; set; } + + /// + /// 查询时间范围 + /// + [JsonPropertyName("timeRange")] + public List? TimeRange { get; set; } + + /// + /// 分页信息 + /// + [JsonPropertyName("page")] + public UserPage? Page { get; set; } +} + +public class UserPage +{ + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/LoginRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/LoginRequest.cs new file mode 100644 index 0000000..759b850 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/LoginRequest.cs @@ -0,0 +1,24 @@ +using DataCheck; +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.User; + +/// +/// 登录请求类 +/// +public class LoginRequest +{ + /// + /// 用户ID + /// + [DataRules] + [JsonPropertyName("userId")] + public string? UserId { get; set; } + + /// + /// 用户密码 + /// + [DataRules] + [JsonPropertyName("userPassword")] + public string? UserPassword { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/LoginResponse.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/LoginResponse.cs new file mode 100644 index 0000000..b800b1a --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/User/LoginResponse.cs @@ -0,0 +1,87 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.User; + +/// +/// d登录接口响应类 +/// +public class LoginResponse +{ + /// + /// 用户名称 + /// + [JsonPropertyName("userName")] + public string? UserName { get; set; } + + /// + /// 菜单 + /// + [JsonPropertyName("menu")] + public List? Menu { get; set; } + +} + +/// +/// 菜单 +/// +public class MainMenuData +{ + /// + /// 主菜单序号 + /// + [JsonPropertyName("index")] + public string? Index { get; set; } + + /// + /// 主菜单名称 + /// + [JsonPropertyName("mainMenu")] + public string? MainMenu { get; set; } + + /// + /// 主菜单图标 + /// + [JsonPropertyName("ico")] + public string? Ico { get; set; } + + /// + /// 次级菜单 + /// + [JsonPropertyName("minor")] + public List? Minor { get; set; } + + +} + +/// +/// 次级菜单 +/// +public class MinorMenuData +{ + /// + /// 次菜单序号 + /// + [JsonPropertyName("index")] + public string? Index { get; set; } + + /// + /// 次菜单名称 + /// + [JsonPropertyName("minorMenu")] + public string? MinorMenu { get; set; } + + /// + /// 次菜单图标 + /// + [JsonPropertyName("ico")] + public string? Ico { get; set; } + + /// + /// 次菜单路由 + /// + [JsonPropertyName("router")] + public string? Router { get; set; } + + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/UserGroup/AddUserGroupRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/UserGroup/AddUserGroupRequest.cs new file mode 100644 index 0000000..30286c0 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/UserGroup/AddUserGroupRequest.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.UserGroup; + +public class AddUserGroupRequest +{ + /// + /// 用户组ID + /// + [JsonPropertyName("groupId")] + public string? GroupId { get; set; } + + /// + /// 用户组名称 + /// + [JsonPropertyName("groupName")] + public string? GroupName { get; set; } + + /// + /// 用户组状态 + /// + [JsonPropertyName("groupStatus")] + public int? GroupStatus { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/UserRule/UpdateUserRuleRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/UserRule/UpdateUserRuleRequest.cs new file mode 100644 index 0000000..8fa7313 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/UserRule/UpdateUserRuleRequest.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; +using DataCheck; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.UserRule; + +public class UpdateUserRuleRequest +{ + /// + /// 用户组名称 + /// + [DataRules] + [JsonPropertyName("userGroupId")] + public string? UserGroupId { get; set; } + + /// + /// 用户权限 + /// + [JsonPropertyName("userRules")] + public List? UserRules { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/WcsTask/GetWcsTaskWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/WcsTask/GetWcsTaskWithPageRequest.cs new file mode 100644 index 0000000..2d0c7f1 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/WcsTask/GetWcsTaskWithPageRequest.cs @@ -0,0 +1,53 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.WcsTask; + +public class GetWcsTaskWithPageRequest +{ + /// + /// 查询的字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchStr { get; set; } + + /// + /// 任务类型 + /// + [JsonPropertyName("taskType")] + public List? TaskType { get; set; } + + /// + /// 任务状态 + /// + [JsonPropertyName("taskStatus")] + public List? TaskStatus { get; set; } + + /// + /// 查询时间范围 + /// + [JsonPropertyName("timeRange")] + public List? TimeRange { get; set; } + + /// + /// 分页信息 + /// + [JsonPropertyName("page")] + public WcsPage? Page { get; set; } +} + + +public class WcsPage +{ + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/WcsTask/UpdateWcsTaskStatusRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/WcsTask/UpdateWcsTaskStatusRequest.cs new file mode 100644 index 0000000..4abd914 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/WcsTask/UpdateWcsTaskStatusRequest.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; +using DataCheck; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.WcsTask; + +/// +/// 更新 wcs 任务状态请求类 +/// +public class UpdateWcsTaskStatusRequest +{ + /// + /// plc任务号 + /// + [DataRules] + [JsonPropertyName("plcId")] + public int? PlcId { get; set; } + + /// + /// 状态 + /// + [DataRules] + [JsonPropertyName("taskStatus")] + public int? TaskStatus { get; set; } + + /// + /// 优先级 + /// + [DataRules] + [JsonPropertyName("priority")] + public int? Priority { get; set; } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/WmsTask/GetWmsTaskWithPageRequest.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/WmsTask/GetWmsTaskWithPageRequest.cs new file mode 100644 index 0000000..e0ace1f --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/WmsTask/GetWmsTaskWithPageRequest.cs @@ -0,0 +1,56 @@ +using System.Text.Json.Serialization; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.WmsTask; + +/// +/// 分页查询 WMS 任务的实体 +/// +public class GetWmsTaskWithPageRequest +{ + /// + /// 查询的字符串 + /// + [JsonPropertyName("searchStr")] + public string? SearchStr { get; set; } + + /// + /// 任务类型 + /// + [JsonPropertyName("taskType")] + public List? TaskType { get; set; } + + /// + /// 任务状态 + /// + [JsonPropertyName("taskStatus")] + public List? TaskStatus { get; set; } + + /// + /// 查询时间范围 + /// + [JsonPropertyName("timeRange")] + public List? TimeRange { get; set; } + + /// + /// 分页信息 + /// + [JsonPropertyName("page")] + public WmsPage? Page { get; set; } + +} + +public class WmsPage +{ + /// + /// 每页大小 + /// + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } + + /// + /// 当前页数 + /// + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/Dto/WcsDto/WmsTask/SetWmsTask.cs b/WcsMain/ApiServe/Controllers/Dto/WcsDto/WmsTask/SetWmsTask.cs new file mode 100644 index 0000000..d32c597 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/Dto/WcsDto/WmsTask/SetWmsTask.cs @@ -0,0 +1,71 @@ +using System.Text.Json.Serialization; +using DataCheck; + +namespace WcsMain.ApiServe.Controllers.Dto.WcsDto.WmsTask; + +/// +/// 创建任务的统一类 +/// +public class SetWmsTask +{ + /// + /// 任务ID + /// + [DataRules] + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 任务类型 + /// + [DataRules] + [JsonPropertyName("taskType")] + public int? TaskType { get; set; } + + /// + /// 优先级 + /// + [JsonPropertyName("priority")] + public int? Priority { get; set; } + + /// + /// 任务起点 + /// + [JsonPropertyName("origin")] + public string? Origin { get; set; } + + /// + /// 中间点 + /// + [JsonPropertyName("midpoint")] + public string? Midpoint { get; set; } + + /// + /// 任务终点 + /// + [JsonPropertyName("destination")] + public string? Destination { get; set; } + + /// + /// 载具号 + /// + [DataRules] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 载具尺寸 + /// + [DataRules] + [JsonPropertyName("vehicleSize")] + public int? VehicleSize { get; set; } + + /// + /// 载具重量 + /// + [DataRules] + [JsonPropertyName("weight")] + public decimal? Weight { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/TestController.cs b/WcsMain/ApiServe/Controllers/TestController.cs new file mode 100644 index 0000000..ccd2a78 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/TestController.cs @@ -0,0 +1,72 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter; + +namespace WcsMain.ApiServe.Controllers; + +/// +/// 测试接口,用于测试接口是否通断 +/// +/// +/// 构造函数,注入显示类 +/// +/// +[ApiController] +[Route("api/Test")] +[ServiceFilter] +public class TestController(ILogger logger) : ControllerBase +{ + + private readonly ILogger _logger = logger; + + + /// + /// Get测试 + /// + /// + [HttpGet("GetTest")] + public string GetTest() + { + _logger.LogInformation("正常"); + return "正常"; + } + + /// + /// Get测试 + /// + /// + [HttpGet("RestartTest")] + public string RestartTest() + { + string? path = Process.GetCurrentProcess().MainModule?.FileName; + Process process = new(); + process.StartInfo.FileName = path; + process.Start(); + Process.GetCurrentProcess().Kill(); + + return "正常"; + } + + /// + /// Post测试 + /// + /// + /// + [HttpPost("PostTest")] + public string PostTest([FromBody] string body) + { + return $"正常,收到的信息为:{body}"; + } + + + /// + /// Post异常测试 + /// + /// + /// + [HttpPost("PostErrorTest")] + public string PostErrorTest([FromBody] string body) + { + throw new Exception($"异常异常:{body}"); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/ThreeDController/TaskController.cs b/WcsMain/ApiServe/Controllers/ThreeDController/TaskController.cs new file mode 100644 index 0000000..7b0fd62 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/ThreeDController/TaskController.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Service.TreeDService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.ThreeDController; + +[Route("api/threeD")] +[ApiController] +public class TaskController(TaskService taskService) : ControllerBase +{ + private readonly TaskService _taskService = taskService; + + /// + /// 查询WMS任务 + /// + /// + [HttpGet("getWmsTask")] + public WcsApiResponse> GetWmsTask() => _taskService.GetWmsTask(); + + /// + /// 查询WMS任务 + /// + /// + [HttpGet("getWmsTaskNotEnd")] + public WcsApiResponse> GetWmsTaskNoEnd() => _taskService.GetWmsTaskNoEnd(); + + /// + /// 根据 plcId 查询任务 + /// + /// + /// + [HttpGet("queryRunningTask")] + public WcsApiResponse GetWcsTask([FromQuery(Name = "plcId")] int? plcId) => _taskService.GetWcsTask(plcId); +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/ApiAcceptController.cs b/WcsMain/ApiServe/Controllers/WcsController/ApiAcceptController.cs new file mode 100644 index 0000000..9e139fd --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/ApiAcceptController.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ApiAccept; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/apiAccept")] +[ApiController] +[WcsExceptionFilter] +public class ApiAcceptController(ApiAcceptService apiAcceptService) : ControllerBase +{ + + private readonly ApiAcceptService _apiAcceptService = apiAcceptService; + + /// + /// 查询所有的WCS任务 + /// + /// + [HttpPost("getApiAcceptWithPage")] + public WcsApiResponse> GetApiAcceptWithPage([FromBody] GetApiAcceptWithPageRequest request) => _apiAcceptService.GetApiAcceptWithPage(request); +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/ApiRequestController.cs b/WcsMain/ApiServe/Controllers/WcsController/ApiRequestController.cs new file mode 100644 index 0000000..aae2360 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/ApiRequestController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ApiRequest; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/apiRequest")] +[ApiController] +[WcsExceptionFilter] +public class ApiRequestController(ApiRequestService apiRequestService) : ControllerBase +{ + + private readonly ApiRequestService _apiRequestService = apiRequestService; + + /// + /// 查询所有的WCS请求其他系统的记录 + /// + /// + [HttpGet("getApiRequest")] + public WcsApiResponse> GetApiRequest() => _apiRequestService.GetApiRequest(); + + /// + /// 查询所有的WCS任务 + /// + /// + [HttpPost("getApiRequestWithPage")] + public WcsApiResponse> GetApiRequestWithPage([FromBody] GetApiRequestWithPageRequest request) => _apiRequestService.GetApiRequestWithPage(request); + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/ConfigController.cs b/WcsMain/ApiServe/Controllers/WcsController/ConfigController.cs new file mode 100644 index 0000000..58733ec --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/ConfigController.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Config; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/config")] +[ApiController] +[WcsExceptionFilter] +public class ConfigController(ConfigService configService) : ControllerBase +{ + + private readonly ConfigService _configService = configService; + + /// + /// 查询配置项 + /// + /// + /// + [HttpGet("getConfig")] + public WcsApiResponse> GetConfig([FromQuery(Name = "configKey")] string? configKey) => _configService.GetConfig(configKey); + + /// + /// 分页请求,获取配置项 + /// + /// + /// + [HttpPost("getConfigWithPage")] + public WcsApiResponse> GetConfigWithPage([FromBody] GetConfigWithPageRequest request) => _configService.GetConfigWithPage(request); + + /// + /// 添加或者修改配置项 + /// + /// + /// + [HttpPost("editeConfig")] + public WcsApiResponse EditeConfig([FromBody] EditeConfigRequest request) => _configService.EditeConfig(request); +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/ElTagController.cs b/WcsMain/ApiServe/Controllers/WcsController/ElTagController.cs new file mode 100644 index 0000000..f8d4df1 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/ElTagController.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + + + +/// +/// 电子标签控制器的控制类 +/// +[Route("api/wcs/elTag")] +[ApiController] +[WcsExceptionFilter] +public class ElTagController(ElTagService elTagService) : ControllerBase +{ + + private readonly ElTagService _elTagService = elTagService; + + /// + /// 向标签发送显示数字 + /// + /// + /// + [HttpPost("showNum")] + public WcsApiResponse ShowNum([FromBody] ShowNumRequest request) + { + return _elTagService.ShowNum(request); + } + + + /// + /// 分页查询电子标签任务表 + /// + /// + /// + [HttpPost("queryTaskWithPage")] + public WcsApiResponse> QueryTaskWithPage([FromBody] QueryTaskRequest request) + { + return _elTagService.QueryTaskWithPage(request); + } + + /// + /// 编辑电子标签任务信息 + /// + /// + /// + [HttpPost("editTaskInfo")] + public WcsApiResponse EditTaskInfo([FromBody] EditTaskInfoRequest request) + { + return _elTagService.EditTaskInfo(request); + } + + + /// + /// 添加一个电子标签任务信息 + /// + /// + /// + [HttpPost("addTask")] + public WcsApiResponse AddTaskInfo([FromBody] AddTaskInfoRequest request) + { + return _elTagService.AddTaskInfo(request); + } + + + +} diff --git a/WcsMain/ApiServe/Controllers/WcsController/EquipmentController.cs b/WcsMain/ApiServe/Controllers/WcsController/EquipmentController.cs new file mode 100644 index 0000000..570225b --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/EquipmentController.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.Equipment; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Equipment; +using WcsMain.ApiServe.Service.WcsService; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +/// +/// 查询设备状态信息 +/// +[Route("api/wcs/equipment")] +[ApiController] +[WcsExceptionFilter] +public class EquipmentController(EquipmentService equipmentService) : ControllerBase +{ + + private readonly EquipmentService _equipmentService = equipmentService; + + + [HttpGet("queryStackerInfo")] + public WcsApiResponse> GetStackerInfo() => _equipmentService.GetStackerInfo(); + + /// + /// 查询库前输送机信息 + /// + /// + [HttpGet("queryStackerConveyInfo")] + public WcsApiResponse> GetConveyInfo() => _equipmentService.GetConveyInfo(); + + + /// + /// 复位堆垛机 + /// + /// + /// + [HttpPost("resetStacker")] + public WcsApiResponse ResetStacker([FromBody] ResetStackerRequest request) => _equipmentService.ResetStacker(request); + + /// + /// 堆垛机继续运行 + /// + /// + /// + [HttpPost("stackerContinue")] + public WcsApiResponse StackerContinue([FromBody] StackerContinueRequest request) => _equipmentService.StackerContinue(request); +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/LocationController.cs b/WcsMain/ApiServe/Controllers/WcsController/LocationController.cs new file mode 100644 index 0000000..beb8915 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/LocationController.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Location; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/location")] +[ApiController] +[WcsExceptionFilter] +public class LocationController(LocationService locationService) : ControllerBase +{ + + private readonly LocationService _locationService = locationService; + + /// + /// 查询所有的点位状态 + /// + /// + [HttpGet("getLocation")] + public WcsApiResponse> GetLocation() + { + return _locationService.GetLocation(); + } + + /// + /// 分页查询所有的点位状态 + /// + /// + [HttpPost("getLocationWithPage")] + public WcsApiResponse> GetLocationWithPage([FromBody] GetLocationWithPageRequest request) + { + return _locationService.GetLocationWithPage(request); + } + + /// + /// 更新点位信息 + /// + /// + /// + [HttpPost("updateLocation")] + public WcsApiResponse UpdateLocation([FromBody] UpdateLocationRequest request) + { + return _locationService.UpdateLocation(request); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/MenuController.cs b/WcsMain/ApiServe/Controllers/WcsController/MenuController.cs new file mode 100644 index 0000000..8610a4a --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/MenuController.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Menu; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/menu")] +[ApiController] +[WcsExceptionFilter] +public class MenuController(MenuService menuService) : ControllerBase +{ + + private readonly MenuService _menuService = menuService; + + /// + /// ҳѯ˵ + /// + /// + /// + [HttpPost("getMenuWithPage")] + public WcsApiResponse> GetMenuWithPage([FromBody] GetMenuWithPageRequest request) + { + return _menuService.GetMenuWithPage(request); + } + + /// + /// ²˵Ϣ + /// + /// + /// + [HttpPost("updateMenu")] + public WcsApiResponse UpdateMenu([FromBody] UpdateMenuRequest request) + { + return _menuService.UpdateMenu(request); + } + + /// + /// Ӳ˵Ϣ + /// + /// + /// + [HttpPost("addMenu")] + public WcsApiResponse AddMenu([FromBody] AddMenuRequest request) + { + return _menuService.AddMenu(request); + } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/PlcController.cs b/WcsMain/ApiServe/Controllers/WcsController/PlcController.cs new file mode 100644 index 0000000..f9e7c41 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/PlcController.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.PLC; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/plc")] +[ApiController] +[WcsExceptionFilter] +public class PlcController(PlcService plcService) : ControllerBase +{ + + private readonly PlcService _plcService = plcService; + + /// + /// 查询plc + /// + /// + [HttpGet("getPlc")] + public WcsApiResponse> GetPlc() + { + return _plcService.GetPlc(); + } + + /// + /// 添加或者修改配置项 + /// + /// + /// + [HttpPost("editePlc")] + public WcsApiResponse EditePlc([FromBody] EditePLCRequest request) + { + return _plcService.EditePlc(request); + } + + + /// + /// 添加或者修改配置项 + /// + /// + /// + [HttpDelete("deletePlc")] + public WcsApiResponse DeletePlc([FromQuery(Name = "plcId")] int? plcId) + { + return _plcService.DeletePlc(plcId); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/PlcDbController.cs b/WcsMain/ApiServe/Controllers/WcsController/PlcDbController.cs new file mode 100644 index 0000000..9638317 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/PlcDbController.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.DB; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/db")] +[ApiController] +[WcsExceptionFilter] +public class PlcDbController(PlcDbService plcDbService) : ControllerBase +{ + + private readonly PlcDbService _plcDbService = plcDbService; + + /// + /// 查询 db + /// + /// + [HttpGet("getDB")] + public WcsApiResponse> GetDB() + { + return _plcDbService.GetDB(); + } + + + /// + /// 添加或者修改db项 + /// + /// + /// + [HttpPost("addOrUpdate")] + public WcsApiResponse EditeDB([FromBody] EditeDBRequest request) + { + return _plcDbService.EditePlc(request); + } + + /// + /// 删除一个DB信息 + /// + /// + /// + [HttpDelete("deleteDB")] + public WcsApiResponse DeleteDb([FromQuery(Name = "dbName")] string? dbName) + { + return _plcDbService.DeleteDB(dbName); + } + + /// + /// 查询 db 同时返回 PLC名称 + /// + /// + /// + [HttpPost("getDBWithPlcName")] + public WcsApiResponse> GetDBWithPlcName(GetDBWithPlcNameRequest request) + { + return _plcDbService.GetDBWithPlcName(request); + } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/RunningInfoController.cs b/WcsMain/ApiServe/Controllers/WcsController/RunningInfoController.cs new file mode 100644 index 0000000..838ec90 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/RunningInfoController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.SystemController; +using WcsMain.ApiServe.Service.WcsService; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/runningInfo")] +[ApiController] +[WcsExceptionFilter] +public class RunningInfoController(RunningInfoService runningInfoService) : ControllerBase +{ + + + private readonly RunningInfoService _runningInfoService = runningInfoService; + + + /// + /// 获取日志的文件名称 + /// + /// + /// + [HttpGet("getLogFileName")] + public WcsApiResponse> GetLogFileName([FromQuery(Name = "logType")] string? logType) + { + return _runningInfoService.GetLogFileName(logType); + } + + + /// + /// 验证下载文件是否存在 + /// + /// + /// + [HttpPost("checkDownLoadLog")] + public WcsApiResponse CheckDownLoadLog([FromBody] LogRequest request) + { + return _runningInfoService.CheckDownLoadLog(request); + } + + + /// + /// 下载文件 + /// + /// + /// + [HttpPost("downLoadLog")] + public FileContentResult DownLoadLog([FromBody] LogRequest request) + { + return _runningInfoService.DownLoadLog(request, this); + } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/SettingController.cs b/WcsMain/ApiServe/Controllers/WcsController/SettingController.cs new file mode 100644 index 0000000..1ddffbc --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/SettingController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Settings; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("/api/wcs/setting")] +[ApiController] +public class SettingController(SettingService settingService) : ControllerBase +{ + + private readonly SettingService _settingService = settingService; + + /// + /// 查询设置项 + /// + /// + /// + [HttpGet("getSettings")] + public WcsApiResponse> GetConfig([FromQuery(Name = "settingKey")] string? settingKey) + { + return _settingService.GetConfig(settingKey); + } + + + + /// + /// 添加或者修改配置项 + /// + /// + /// + [HttpPost("editSettings")] + public WcsApiResponse EditeConfig([FromBody] EditSettingsRequest request) + { + return _settingService.EditeConfig(request); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/SocketController.cs b/WcsMain/ApiServe/Controllers/WcsController/SocketController.cs new file mode 100644 index 0000000..81c45c5 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/SocketController.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Socket; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/socket")] +[ApiController] +public class SocketController(SocketService socketService) : ControllerBase +{ + + private readonly SocketService _socketService = socketService; + + /// + /// 查询 socket 连接信息 + /// + /// + [HttpGet("getSocket")] + public WcsApiResponse> GetDB() + { + return _socketService.GetDB(); + } + + + /// + /// 编辑 socket + /// + /// + /// + [HttpPost("editSocket")] + public WcsApiResponse EditSocket([FromBody] EditSocketRequest request) + { + return _socketService.EditSocket(request); + } + + /// + /// 删除一条信息 + /// + /// + /// + [HttpDelete("deleteSocket")] + public WcsApiResponse DeleteSocket([FromQuery(Name = "socketNo")] string? socketNoStr) + { + return _socketService.DeleteSocket(socketNoStr); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/StackerController.cs b/WcsMain/ApiServe/Controllers/WcsController/StackerController.cs new file mode 100644 index 0000000..3502fa0 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/StackerController.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Stacker; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/stacker")] +[ApiController] +[WcsExceptionFilter] +public class StackerController(StackerService stackerService) : ControllerBase +{ + + private readonly StackerService _stackerService = stackerService; + + + /// + /// 查询所有的 堆垛机信息 + /// + /// + [HttpGet("getStacker")] + public WcsApiResponse> GetStacker() + { + return _stackerService.GetStacker(); + } + + /// + /// 查询所有的 堆垛机状态信息 ---- 从设备返回 + /// + /// + [HttpGet("getStackerStatus")] + public WcsApiResponse> GetStackerStatus() + { + return _stackerService.GetStackerStatus(); + } + + /// + /// 添加或者编辑堆垛机信息 + /// + /// + /// + [HttpPost("editStacker")] + public WcsApiResponse EditStacker([FromBody] EditStackerRequest request) + { + return _stackerService.EditStacker(request); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/UserController.cs b/WcsMain/ApiServe/Controllers/WcsController/UserController.cs new file mode 100644 index 0000000..b2a3a13 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/UserController.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.User; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/user")] +[ApiController] +[WcsExceptionFilter] +public class UserController(UserService userService) : ControllerBase +{ + private readonly UserService _userService = userService; + + + /// + /// 用户登录 + /// + /// + /// + /// + /// 若登录成功则返回用户名和用户菜单 + /// + [HttpPost("login")] + public WcsApiResponse Login([FromBody] LoginRequest request) + { + return _userService.Login(request); + } + + /// + /// 分页查询所有用户信息 + /// + /// + /// + [HttpPost("getUserWithPage")] + public WcsApiResponse> GetUserWithPage([FromBody] GetUserWithPageRequest request) + { + return _userService.GetUserWithPage(request); + } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/UserGroupController.cs b/WcsMain/ApiServe/Controllers/WcsController/UserGroupController.cs new file mode 100644 index 0000000..fa9fd8c --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/UserGroupController.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.UserGroup; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/userGroup")] +[ApiController] +public class UserGroupController(UserGroupService userGroupService) : ControllerBase +{ + + + private readonly UserGroupService _userGroupService = userGroupService; + + /// + /// ѯû + /// + /// + [HttpGet("getUserGroup")] + public WcsApiResponse> GetUserGroup() + { + return _userGroupService.GetUserGroup(); + } + + /// + /// û + /// + /// + /// + [HttpPost("addUserGroup")] + public WcsApiResponse AddUserGroup([FromBody] AddUserGroupRequest request) + { + return _userGroupService.AddUserGroup(request); + } + + /// + /// ɾû + /// + /// + /// + [HttpDelete("deleteUserGroup")] + public WcsApiResponse DeleteUserGroup([FromQuery] string? groupId) + { + return _userGroupService.DeleteUserGroup(groupId); + } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/UserRuleController.cs b/WcsMain/ApiServe/Controllers/WcsController/UserRuleController.cs new file mode 100644 index 0000000..f1259a0 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/UserRuleController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.UserRule; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/userRule")] +[ApiController] +[WcsExceptionFilter] +public class UserRuleController(UserRuleService userRuleService) : ControllerBase +{ + + + private readonly UserRuleService _userRuleService = userRuleService; + + /// + /// ȡûȨ + /// + /// + [HttpGet("getUserRule")] + public WcsApiResponse> GetUserRule([FromQuery] string? groupId) + { + return _userRuleService.GetUserRule(groupId); + } + + /// + /// ûȨ + /// + /// + /// + [HttpPost("updateUserRule")] + public WcsApiResponse UpdateUserRule([FromBody] UpdateUserRuleRequest request) + { + return _userRuleService.UpdateUserRule(request); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/WcsTaskController.cs b/WcsMain/ApiServe/Controllers/WcsController/WcsTaskController.cs new file mode 100644 index 0000000..25a4262 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/WcsTaskController.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.WcsTask; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/wcsTask")] +[ApiController] +[WcsExceptionFilter] +public class WcsTaskController(WcsTaskService wcsTaskService) : ControllerBase +{ + + private readonly WcsTaskService _wcsTaskService = wcsTaskService; + + + /// + /// 查询所有的正在运行的WCS任务 + /// + /// + [HttpGet("getWcsTask")] + public WcsApiResponse> GetWcsTask() + { + return _wcsTaskService.GetWcsTask(); + } + + /// + /// 根据任务号获取任务信息,包括运行表和备份表 + /// + /// + /// + [HttpGet("getWcsTaskWithTaskId")] + public WcsApiResponse> GetWcsTaskWithTaskId([FromQuery(Name = "taskId")] string taskId) + { + return _wcsTaskService.GetWcsTaskWithTaskId(taskId); + } + + /// + /// 分页查询正在运行的任务 + /// + /// + /// + [HttpPost("getWcsTaskWithPage")] + public WcsApiResponse> GetWcsTaskWithPage([FromBody] GetWcsTaskWithPageRequest request) + { + return _wcsTaskService.GetWcsTaskWithPage(request); + } + + /// + /// 更新任务信息 + /// + /// + /// + [HttpPost("updateStatus")] + public WcsApiResponse UpdateWcsTaskStatus([FromBody] UpdateWcsTaskStatusRequest request) + { + return _wcsTaskService.UpdateWcsTaskStatus(request); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WcsController/WmsTaskController.cs b/WcsMain/ApiServe/Controllers/WcsController/WmsTaskController.cs new file mode 100644 index 0000000..a2ef376 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WcsController/WmsTaskController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter.ExceptionFilter; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.WmsTask; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity.WmsTask; +using WcsMain.ApiServe.Service.WcsService; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ApiServe.Controllers.WcsController; + +[Route("api/wcs/wmsTask")] +[ApiController] +[WcsExceptionFilter] +public class WmsTaskController(WmsTaskService wmsTaskService) : ControllerBase +{ + private readonly WmsTaskService _wmsTaskService = wmsTaskService; + + /// + /// 查询WMS任务 + /// + /// + [HttpGet("getWmsTask")] + public WcsApiResponse> GetWmsTask() + { + return _wmsTaskService.GetWmsTask(); + } + + /// + /// 分页查询所有的Wms任务 + /// + /// + [HttpPost("getWmsTaskWithPage")] + public WcsApiResponse> GetWmsTaskWithPage([FromBody] GetWmsTaskWithPageRequest request) + { + return _wmsTaskService.GetWmsTaskWithPage(request); + } + + /// + /// wcs前端向Wcs发送任务 + /// + /// + /// + [HttpPost("setStackerTask")] + public WcsApiResponse GetStackerTask([FromBody] SetWmsTask request) + { + return _wmsTaskService.GetStackerTask(request); + } + + /// + /// Wms向Wcs请求修改任务状态 + /// + /// + /// + [HttpPost("updateWmsTaskStatus")] + public WcsApiResponse UpdateStackerTaskStatus([FromBody] UpdateStackerTaskStatusRequest request) + { + return _wmsTaskService.UpdateStackerTaskStatus(request); + } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Controllers/WmsController/EquipmentController.cs b/WcsMain/ApiServe/Controllers/WmsController/EquipmentController.cs new file mode 100644 index 0000000..87f5b25 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WmsController/EquipmentController.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity.Equipment; +using WcsMain.ApiServe.Service.WmsService; +using WcsMain.ApiServe.ControllerFilter; + +namespace WcsMain.ApiServe.Controllers.WmsController +{ + /// + /// 设备控制接口 ---- 供Wms调用 + /// + [Route("api/wms/equipment")] + [ApiController] + [ServiceFilter(typeof(ResponseFilterAttribute))] + [WmsApiExceptionFilter] + public class EquipmentController(EquipmentService equipmentService) : ControllerBase + { + + private readonly EquipmentService _equipmentService = equipmentService; + + /// + /// 查询站台是否允许动作 + /// + /// + /// + [HttpPost("queryStandStatus")] + public WmsApiResponse QueryStandStatus([FromBody] QueryStandStatusRequest request) + { + return _equipmentService.QueryStandStatus(request); + } + + /// + /// 通知输送机卸货完成 + /// + /// + [HttpPost("unloadSuccess")] + public WmsApiResponse UnloadSuccess() + { + return _equipmentService.UnloadSuccess(); + } + + } +} diff --git a/WcsMain/ApiServe/Controllers/WmsController/WmsTaskController.cs b/WcsMain/ApiServe/Controllers/WmsController/WmsTaskController.cs new file mode 100644 index 0000000..e2325c1 --- /dev/null +++ b/WcsMain/ApiServe/Controllers/WmsController/WmsTaskController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.ControllerFilter; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity.WmsTask; +using WcsMain.ApiServe.Service.WmsService; + +namespace WcsMain.ApiServe.Controllers.WmsController; + +/// +/// Wms任务接口 +/// +[Route("api/wms/wmsTask")] +[ApiController] +[ServiceFilter] +[WmsApiExceptionFilter] +public class WmsTaskController(WmsTaskService wmsTaskService) : ControllerBase +{ + + private readonly WmsTaskService _wmsTaskService = wmsTaskService; + + /// + /// Wms向Wcs发送任务 + /// + /// + /// + [HttpPost("setStackerTask")] + public WmsApiResponse GetStackerTask([FromBody] List request) + { + return _wmsTaskService.GetStackerTask(request); + } + + + /// + /// Wms向Wcs请求修改任务状态 + /// + /// + /// + [HttpPost("changeTaskStatus")] + public WmsApiResponse UpdateStackerTaskStatus([FromBody] UpdateStackerTaskStatusRequest request) + { + return _wmsTaskService.UpdateStackerTaskStatus(request); + } + + /// + /// WMS向WCS发送任务新终点,卸货位置有货的时候 + /// + /// + /// + [HttpPost("setStackerTaskNewDestination")] + public WmsApiResponse GetStackerTaskNewDestination([FromBody] GetStackerTaskNewDestinationRequest request) + { + return _wmsTaskService.GetStackerTaskNewDestination(request); + } + + + + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Factory/WcsApiResponseFactory.cs b/WcsMain/ApiServe/Factory/WcsApiResponseFactory.cs new file mode 100644 index 0000000..e6f6fb8 --- /dev/null +++ b/WcsMain/ApiServe/Factory/WcsApiResponseFactory.cs @@ -0,0 +1,170 @@ +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.Enum.ApiServer; + +namespace WcsMain.ApiServe.Factory; + +public static class WcsApiResponseFactory +{ + /// + /// 表示一个操作成功的响应 + /// + /// + public static WcsApiResponse Success(string msg = "操作成功") + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Msg = msg + }; + public static WcsApiResponse Success(T? data = default, string msg = "操作成功") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Msg = msg, + ReturnData = data + }; + public static WcsApiResponse Success(T1? tag = default, T2? data = default, string msg = "操作成功") where T1 : new() where T2 : class + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Msg = msg, + Tag = tag, + ReturnData = data + }; + + /// + /// 表示一个操作失败的响应 + /// + /// + /// + public static WcsApiResponse Fail(string msg = "操作失败") + => new() + { + Code = (int)ApiResponseCodeEnum.fail, + Msg = msg + }; + public static WcsApiResponse Fail(T? data = default, string msg = "操作失败") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.fail, + Msg = msg, + ReturnData = data + }; + public static WcsApiResponse Fail(T1? tag = default, T2? data = default, string msg = "操作失败") where T1 : new() where T2 : class + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Msg = msg, + Tag = tag, + ReturnData = data +}; + + + /// + /// 表示一个请求参数错误的响应 + /// + /// + /// + public static WcsApiResponse RequestErr(string msg = "请求参数错误") + => new() + { + Code = (int)ApiResponseCodeEnum.requestDataErr, + Msg = msg + }; + public static WcsApiResponse RequestErr(T? data = default, string msg = "请求参数错误") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.requestDataErr, + Msg = msg, + ReturnData = data + }; + public static WcsApiResponse RequestErr(T1? tag = default, T2? data = default, string msg = "请求参数错误") where T1 : new() where T2 : class + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Msg = msg, + Tag = tag, + ReturnData = data +}; + + /// + /// 表示一个数据重复的响应 + /// + /// + /// + public static WcsApiResponse DataRepetition(string msg = "数据重复") + => new() + { + Code = (int)ApiResponseCodeEnum.dataRepetition, + Msg = msg + }; + public static WcsApiResponse DataRepetition(T? data = default, string msg = "数据重复") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.dataRepetition, + Msg = msg, + ReturnData = data + }; + public static WcsApiResponse DataRepetition(T1? tag = default, T2? data = default, string msg = "数据重复") where T1 : new() where T2 : class + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Msg = msg, + Tag = tag, + ReturnData = data +}; + + /// + /// 表示一个服务器异常的响应 + /// + /// + /// + public static WcsApiResponse ServiceErr(string msg = "服务器异常") + => new() + { + Code = (int)ApiResponseCodeEnum.serviceErr, + Msg = msg + }; + public static WcsApiResponse ServiceErr(T? data = default, string msg = "服务器异常") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.serviceErr, + Msg = msg, + ReturnData = data + }; + public static WcsApiResponse ServiceErr(T1? tag = default, T2? data = default, string msg = "服务器异常") where T1 : new() where T2 : class + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Msg = msg, + Tag = tag, + ReturnData = data +}; + + /// + /// 表示一个数据库异常的响应 + /// + /// + /// + public static WcsApiResponse DataBaseErr(string msg = "后台数据库异常,请重试") + => new() + { + Code = (int)ApiResponseCodeEnum.dataBaseErr, + Msg = msg + }; + public static WcsApiResponse DataBaseErr(T? data = default, string msg = "后台数据库异常,请重试") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.dataBaseErr, + Msg = msg, + ReturnData = data + }; + public static WcsApiResponse DataBaseErr(T1? tag = default, T2? data = default, string msg = "后台数据库异常,请重试") where T1 : new() where T2 : class + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Msg = msg, + Tag = tag, + ReturnData = data + }; + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Factory/WmsApiResponseFactory.cs b/WcsMain/ApiServe/Factory/WmsApiResponseFactory.cs new file mode 100644 index 0000000..84329ad --- /dev/null +++ b/WcsMain/ApiServe/Factory/WmsApiResponseFactory.cs @@ -0,0 +1,123 @@ +using WcsMain.ApiServe.Controllers.Dto.WMSEntity; +using WcsMain.Enum.ApiServer; + +namespace WcsMain.ApiServe.Factory; + +public class WmsApiResponseFactory +{ + /// + /// 表示一个操作成功的响应 + /// + /// + public static WmsApiResponse Success(string msg = "操作成功") + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Message = msg + }; + public static WmsApiResponse Success(T? data = default, string msg = "操作成功") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.success, + Message = msg, + ReturnData = data + }; + + /// + /// 表示一个操作失败的响应 + /// + /// + /// + public static WmsApiResponse Fail(string msg = "操作失败") + => new() + { + Code = (int)ApiResponseCodeEnum.fail, + Message = msg + }; + public static WmsApiResponse Fail(T? data = default, string msg = "操作失败") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.fail, + Message = msg, + ReturnData = data + }; + + + /// + /// 表示一个请求参数错误的响应 + /// + /// + /// + public static WmsApiResponse RequestErr(string msg = "请求参数错误") + => new() + { + Code = (int)ApiResponseCodeEnum.requestDataErr, + Message = msg, + }; + public static WmsApiResponse RequestErr(T? data = default, string msg = "请求参数错误") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.requestDataErr, + Message = msg, + ReturnData = data + }; + + + /// + /// 表示一个数据重复的响应 + /// + /// + /// + public static WmsApiResponse DataRepetition(string msg = "数据重复") + => new() + { + Code = (int)ApiResponseCodeEnum.dataRepetition, + Message = msg, + }; + public static WmsApiResponse DataRepetition(T? data = default, string msg = "数据重复") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.dataRepetition, + Message = msg, + ReturnData = data + }; + + /// + /// 表示一个服务器异常的响应 + /// + /// + /// + public static WmsApiResponse ServiceErr(string msg = "服务器异常") + => new() + { + Code = (int)ApiResponseCodeEnum.serviceErr, + Message = msg, + }; + public static WmsApiResponse ServiceErr(T? data = default, string msg = "服务器异常") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.serviceErr, + Message = msg, + ReturnData = data + }; + + /// + /// 表示一个数据库异常的响应 + /// + /// + /// + public static WmsApiResponse DataBaseErr(string msg = "后台数据库异常,请重试") + => new() + { + Code = (int)ApiResponseCodeEnum.dataBaseErr, + Message = msg, + }; + public static WmsApiResponse DataBaseErr(T? data = default, string msg = "后台数据库异常,请重试") where T : class + => new() + { + Code = (int)ApiResponseCodeEnum.dataBaseErr, + Message = msg, + ReturnData = data + }; + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/TreeDService/TaskService.cs b/WcsMain/ApiServe/Service/TreeDService/TaskService.cs new file mode 100644 index 0000000..d9f1d34 --- /dev/null +++ b/WcsMain/ApiServe/Service/TreeDService/TaskService.cs @@ -0,0 +1,53 @@ +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.TreeDService; + +[Service] +public class TaskService(AppWmsTaskDao wmsTaskDao, AppWcsTaskDao wcsTaskDao) +{ + private readonly AppWcsTaskDao _wcsTaskDao = wcsTaskDao; + private readonly AppWmsTaskDao _wmsTaskDao = wmsTaskDao; + + /// + /// 查询WMS任务 + /// + /// + public WcsApiResponse> GetWmsTask() + { + List? wmsTasks = _wmsTaskDao.SelectToWeb(); + return WcsApiResponseFactory.Success(wmsTasks); + } + + /// + /// 查询WMS任务 + /// + /// + public WcsApiResponse> GetWmsTaskNoEnd() + { + List? wmsTasks = _wmsTaskDao.SelectWmsTaskNotEnd(); + return WcsApiResponseFactory.Success(wmsTasks); + } + + /// + /// 根据 plcId 查询任务 + /// + /// + /// + public WcsApiResponse GetWcsTask(int? plcId) + { + if (plcId == default || plcId == 0) + { + return WcsApiResponseFactory.RequestErr(default, $"无法识别查询的 plcId ,值为:{plcId}"); + } + List? queryResult = _wcsTaskDao.Select(new AppWcsTask() { PlcId = plcId }); + if (queryResult == default || queryResult.Count < 1) + { + return WcsApiResponseFactory.Fail(default, $"未找到该 plcId 对应的任务,值为:{plcId}"); + } + return WcsApiResponseFactory.Success(queryResult[0]); + } +} diff --git a/WcsMain/ApiServe/Service/WcsService/ApiAcceptService.cs b/WcsMain/ApiServe/Service/WcsService/ApiAcceptService.cs new file mode 100644 index 0000000..d989832 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/ApiAcceptService.cs @@ -0,0 +1,29 @@ +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ApiAccept; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class ApiAcceptService(AppApiAcceptDao apiAcceptDao) +{ + private readonly AppApiAcceptDao _apiAcceptDao = apiAcceptDao; + + /// + /// 分页查询API接收记录 + /// + /// + /// + public WcsApiResponse> GetApiAcceptWithPage(GetApiAcceptWithPageRequest request) + { + var (apiRequests, totalRows) = _apiAcceptDao.SelectPage(request); + if(apiRequests == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(apiRequests, totalRows.ToString()); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/ApiRequestService.cs b/WcsMain/ApiServe/Service/WcsService/ApiRequestService.cs new file mode 100644 index 0000000..adcf590 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/ApiRequestService.cs @@ -0,0 +1,43 @@ +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ApiRequest; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class ApiRequestService(AppApiRequestDao apiRequestDao) +{ + private readonly AppApiRequestDao _apiRequestDao = apiRequestDao; + + /// + /// 获取所有API请求记录 + /// + /// + public WcsApiResponse> GetApiRequest() + { + List? apiRequests = _apiRequestDao.Select(); + if(apiRequests == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(apiRequests); + } + + /// + /// 分页获取API请求记录 + /// + /// + /// + public WcsApiResponse> GetApiRequestWithPage(GetApiRequestWithPageRequest request) + { + var (apiRequests, totalRows) = _apiRequestDao.SelectPage(request); + if(apiRequests == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(apiRequests, totalRows.ToString()); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/ConfigService.cs b/WcsMain/ApiServe/Service/WcsService/ConfigService.cs new file mode 100644 index 0000000..5ada07a --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/ConfigService.cs @@ -0,0 +1,79 @@ +using DataCheck; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Config; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class ConfigService(AppConfigDao configDao) +{ + private readonly AppConfigDao _configDao = configDao; + + /// + /// 查询配置项,若查询参数为空则返回所有信息 + /// + /// + /// + public WcsApiResponse> GetConfig(string? configKey) + { + var configs = _configDao.GetAllConfig(configKey); + if(configs == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(configs); + } + + /// + /// 分页查询系统配置项 + /// + /// + /// + public WcsApiResponse> GetConfigWithPage(GetConfigWithPageRequest request) + { + var (configs, totalNumber) = _configDao.GetAllConfigWithPage(request); + if (configs == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(configs, totalNumber.ToString()); + } + + /// + /// 添加修改配置项 + /// + /// + /// + public WcsApiResponse EditeConfig(EditeConfigRequest request) + { + // 检查必填的数据 + var checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + AppConfig config = new() + { + ConfigKey = request.ConfigKey, + ConfigName = request.ConfigName, + ConfigType = request.ConfigType, + ConfigValue = request.ConfigValue, + Remark = request.Remark + }; + if (request.IsEdite) // 修改信息 + { + var result = _configDao.Update(config); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + else // 添加信息 + { + var result = _configDao.Insert(config); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/ElTagService.cs b/WcsMain/ApiServe/Service/WcsService/ElTagService.cs new file mode 100644 index 0000000..13d2992 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/ElTagService.cs @@ -0,0 +1,89 @@ +using DataCheck; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.DataService; +using WcsMain.Enum.TaskEnum; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class ElTagService(AppElTagTaskDao tagTaskDao, DataBaseData dataBaseData) +{ + private readonly DataBaseData _dataBaseData = dataBaseData; + private readonly AppElTagTaskDao _tagTaskDao = tagTaskDao; + + /// + /// 展示电子标签的数字 + /// + /// + /// + public WcsApiResponse ShowNum(ShowNumRequest request) + { + return WcsApiResponseFactory.Fail(); + } + + /// + /// 分页查询电子标签任务 + /// + /// + /// + public WcsApiResponse> QueryTaskWithPage(QueryTaskRequest request) + { + (List? elTagTasks, int totalRows) = _tagTaskDao.QueryWithPage(request); + if (elTagTasks == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(totalRows, elTagTasks); + } + + /// + /// 编辑电子标签任务信息 + /// + /// + /// + public WcsApiResponse EditTaskInfo(EditTaskInfoRequest request) + { + var updateResult = _tagTaskDao.Update(new AppElTagTask + { + TaskId = request.TaskId, + TaskStatus = request.TaskStatus, + Remark = "WCS变更信息" + }); + return updateResult > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + + /// + /// 添加电子标签任务 + /// + /// + /// + public WcsApiResponse AddTaskInfo(AddTaskInfoRequest request) + { + if (!CheckData.CheckDataRules(request)) return WcsApiResponseFactory.RequestErr(); + if(string.IsNullOrEmpty(request.TaskId)) request.TaskId = _dataBaseData.GetNewUUID(); + if(string.IsNullOrEmpty(request.TaskGroup)) request.TaskGroup = _dataBaseData.GetNewUUID2(); + var insertResult = _tagTaskDao.Insert(new AppElTagTask + { + TaskId = request.TaskId, + TaskGroup = request.TaskGroup, + Location = request.Location, + OrderId = request.OrderId, + VehicleNo = request.VehicleNo, + GoodsId = request.GoodsId, + GoodsName = request.GoodsName, + TaskStatus = (int)ElTagTaskStatusEnum.NeedLight, + NeedNum = request.NeedNum, + CreatePerson = StaticData.StaticString.WCS, + CreateTime = DateTime.Now + }); + return insertResult > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + + + } + +} diff --git a/WcsMain/ApiServe/Service/WcsService/EquipmentService.cs b/WcsMain/ApiServe/Service/WcsService/EquipmentService.cs new file mode 100644 index 0000000..973bb75 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/EquipmentService.cs @@ -0,0 +1,60 @@ +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.WcsAttribute.AutoFacAttribute; +using WcsMain.ApiServe.Controllers.Dto.Equipment; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Equipment; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class EquipmentService(AppStackerDao stackerDao) +{ + + private readonly AppStackerDao _stackerDao = stackerDao; + + /// + /// 查询所有堆垛机状态 + /// + /// + public WcsApiResponse> GetStackerInfo() + { + return WcsApiResponseFactory.Fail>(msg: "暂不支持"); + } + + + /// + /// 查询库前输送机信息 + /// + /// + public WcsApiResponse> GetConveyInfo() + { + // [TODO] + return WcsApiResponseFactory.Fail>(msg: "该功能暂不支持"); + } + + + + /// + /// 复位堆垛机 + /// + /// + /// + public WcsApiResponse ResetStacker(ResetStackerRequest request) + { + return WcsApiResponseFactory.Fail($"暂不支持"); + + } + + + /// + /// 堆垛机继续运行 + /// + /// + /// + public WcsApiResponse StackerContinue(StackerContinueRequest request) + { + return WcsApiResponseFactory.Fail($"暂不支持"); + } + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/LocationService.cs b/WcsMain/ApiServe/Service/WcsService/LocationService.cs new file mode 100644 index 0000000..81df705 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/LocationService.cs @@ -0,0 +1,72 @@ +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Location; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class LocationService(AppLocationDao locationDao) +{ + + private readonly AppLocationDao _locationDao = locationDao; + + /// + /// 查询所有的点位状态 + /// + /// + public WcsApiResponse> GetLocation() + { + List? locations = _locationDao.Select(); + if (locations == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(locations, "查询成功"); + } + + /// + /// 分页查询点位的状态 + /// + /// + /// + public WcsApiResponse> GetLocationWithPage(GetLocationWithPageRequest request) + { + (List? locations, int totalRows) = _locationDao.SelectPage(request); + if(locations == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(locations, totalRows.ToString()); + } + + /// + /// 更新点位状态 + /// + /// + /// + public WcsApiResponse UpdateLocation(UpdateLocationRequest request) + { + AppLocation updateEntity = new() + { + WcsLocation = request.WcsLocation, + WmsLocation = request.WmsLocation, + TunnelNo = request.TunnelNo, + EquipmentId = request.EquipmentId, + LocationStatus = request.LocationStatus, + Queue = request.Queue, + Line = request.Line, + Layer = request.Layer, + Depth = request.Depth, + LocationType = request.LocationType, + VehicleNo = request.VehicleNo, + ModifyTime = DateTime.Now, + Explain = request.Explain, + Remark = request.Remark + }; + var result = _locationDao.Update(updateEntity); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } +} diff --git a/WcsMain/ApiServe/Service/WcsService/MenuService.cs b/WcsMain/ApiServe/Service/WcsService/MenuService.cs new file mode 100644 index 0000000..0e716d6 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/MenuService.cs @@ -0,0 +1,77 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Menu; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; +using WcsMain.ApiServe.Factory; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class MenuService(AppMenuDao menuDao) +{ + private readonly AppMenuDao _menuDao = menuDao; + + /// + /// 分页查询菜单信息 + /// + /// + /// + public WcsApiResponse> GetMenuWithPage(GetMenuWithPageRequest request) + { + var (menus, totalRows) = _menuDao.SelectPage(request); + if (menus == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(menus, totalRows.ToString()); + } + + /// + /// 更新菜单信息 + /// + /// + /// + public WcsApiResponse UpdateMenu(UpdateMenuRequest request) + { + var menuData = new AppMenu() + { + MainMenuIndex = request.MainMenuIndex, + MainMenuName = request.MainMenuName, + MainMenuIco = request.MainMenuIco, + MinorMenuIndex = request.MinorMenuIndex, + MinorMenuName = request.MinorMenuName, + MinorMenuIco = request.MinorMenuIco, + MinorMenuRouter = request.MinorMenuRouter, + MenuStatus = request.MenuStatus, + Remark = request.Remark + }; + var result = _menuDao.Update(menuData); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + + + /// + /// 添加一个菜单信息 + /// + /// + /// + public WcsApiResponse AddMenu(AddMenuRequest request) + { + var menuData = new AppMenu() + { + MainMenuIndex = request.MainMenuIndex, + MainMenuName = request.MainMenuName, + MainMenuIco = request.MainMenuIco, + MinorMenuIndex = request.MinorMenuIndex, + MinorMenuName = request.MinorMenuName, + MinorMenuIco = request.MinorMenuIco, + MinorMenuRouter = request.MinorMenuRouter, + MenuStatus = request.MenuStatus, + Remark = request.Remark + }; + var result = _menuDao.Insert(menuData); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + +} diff --git a/WcsMain/ApiServe/Service/WcsService/PlcDbService.cs b/WcsMain/ApiServe/Service/WcsService/PlcDbService.cs new file mode 100644 index 0000000..b923db1 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/PlcDbService.cs @@ -0,0 +1,101 @@ +using DataCheck; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.DB; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class PlcDbService(AppDBDao dBDao) +{ + private readonly AppDBDao _dBDao = dBDao; + + /// + /// 查询所有的Db地址信息 + /// + /// + public WcsApiResponse> GetDB() + { + List? dbs = _dBDao.Select(); + if(dbs == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(dbs, "查询成功"); + } + + + + /// + /// 添加修改db + /// + /// + /// + public WcsApiResponse EditePlc(EditeDBRequest request) + { + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + AppDB db = new() + { + PlcId = Convert.ToInt32(request.PlcId), + DBName = request.DbName, + DBAddress = request.DbAddress, + IsSystem = request.IsSystem, + Remark = request.Remark + }; + List? dBs = _dBDao.Select(new AppDB { DBName = request.DbName}); + if(dBs == default) + { + return WcsApiResponseFactory.DataBaseErr(); + } + if (dBs.Count > 0) + { + // 修改信息 + var result = _dBDao.Update(db); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + else + { + // 添加信息 + var result = _dBDao.Insert(db); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + + } + + /// + /// 删除DB信息 + /// + /// + /// + public WcsApiResponse DeleteDB(string? dbName) + { + if (string.IsNullOrEmpty(dbName)) + { + return WcsApiResponseFactory.RequestErr(); + } + var result = _dBDao.Delete(new AppDB() { DBName = dbName }); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + + /// + /// 查询 PLCDB地址,同时返回PLC名称 + /// + /// + /// + public WcsApiResponse> GetDBWithPlcName(GetDBWithPlcNameRequest request) + { + List? dbs = _dBDao.QueryWithPlcName(new AppDB { PlcId = request.PlcId, DBName = request.DBName }); + if (dbs == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(dbs, "查询成功"); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/PlcService.cs b/WcsMain/ApiServe/Service/WcsService/PlcService.cs new file mode 100644 index 0000000..fa6ff29 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/PlcService.cs @@ -0,0 +1,84 @@ +using DataCheck; +using System.Collections.Generic; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.PLC; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class PlcService(AppPLCDao pLCDao) +{ + + private readonly AppPLCDao _plcDao = pLCDao; + + + public WcsApiResponse> GetPlc() + { + List? plcs = _plcDao.Query(); + if (plcs == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(plcs, "查询成功"); + } + + + /// + /// 添加修改plc + /// + /// + /// + public WcsApiResponse EditePlc(EditePLCRequest request) + { + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr>(); + } + AppPLC plc = new() + { + PLCId = Convert.ToInt32(request.PlcId), + PLCIp = request.PlcIp, + PLCName = request.PlcName, + PLCKind = request.PlcKind, + PLCRack = request.Rack, + PLCSlot = request.Slot, + PLCStatus = request.PlcStatus, + Remark = request.Remark + }; + /* 判断这个编号是否存在 */ + List ? appPLCs = _plcDao.Query(new AppPLC { PLCId = request.PlcId}); + if (appPLCs == default) + { + return WcsApiResponseFactory.DataBaseErr(); + } + if (appPLCs.Count > 0) + { + // 修改信息 + var result = _plcDao.Update(plc); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + else + { + // 添加信息 + var result = _plcDao.Insert(plc); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + } + + /// + /// 删除一条数据 + /// + /// + /// + public WcsApiResponse DeletePlc(int? plcId) + { + if(plcId == default) return WcsApiResponseFactory.RequestErr(); + var result = _plcDao.Delete(new AppPLC { PLCId = plcId }); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/RunningInfoService.cs b/WcsMain/ApiServe/Service/WcsService/RunningInfoService.cs new file mode 100644 index 0000000..6a2c535 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/RunningInfoService.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.SystemController; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.WcsAttribute.AutoFacAttribute; +using WcsMain.ApiServe.Factory; +using DataCheck; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class RunningInfoService +{ + + /// + /// 获取日志名称 + /// + /// + /// + public WcsApiResponse> GetLogFileName(string? logType) + { + string logAddress = AppDomain.CurrentDomain.BaseDirectory + $"Log\\{logType}\\"; + DirectoryInfo directoryInfo = new(logAddress); + bool isExist = directoryInfo.Exists; + if (!isExist) + { + return WcsApiResponseFactory.Fail>(msg: "没有相关的日志"); + } + var fileInfos = directoryInfo.GetFiles(); + List fileNames = []; + foreach (FileInfo fileInfo in fileInfos) + { + fileNames.Add(fileInfo.Name); + } + var fileNameArray = fileNames.ToArray(); + Array.Reverse(fileNameArray); + return WcsApiResponseFactory.Success(fileNameArray.ToList(), "查询成功"); + } + + /// + /// 验证下载文件是否存在 + /// + /// + /// + public WcsApiResponse CheckDownLoadLog([FromBody] LogRequest request) + { + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + string logAddress = AppDomain.CurrentDomain.BaseDirectory + $"Log\\{request.LogType}\\{request.LogFileName}"; + bool isHave = System.IO.File.Exists(logAddress); + return isHave ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.Fail("文件不存在"); + } + + /// + /// 下载文件 + /// + /// + /// + public FileContentResult DownLoadLog(LogRequest request, ControllerBase controllerBase) + { + string logAddress = AppDomain.CurrentDomain.BaseDirectory + $"Log\\{request.LogType}\\{request.LogFileName}"; + byte[] fileBytes = System.IO.File.ReadAllBytes(logAddress); + return controllerBase.File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, "log.txt"); + } + + +} diff --git a/WcsMain/ApiServe/Service/WcsService/SettingService.cs b/WcsMain/ApiServe/Service/WcsService/SettingService.cs new file mode 100644 index 0000000..de5db29 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/SettingService.cs @@ -0,0 +1,56 @@ +using DataCheck; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Settings; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class SettingService(AppSettingsDao settingsDao) +{ + + private readonly AppSettingsDao _settingsDao = settingsDao; + + /// + /// 根据键名查询设置项 + /// + /// + /// + public WcsApiResponse> GetConfig(string? settingKey) + { + List? configs = _settingsDao.Select(new AppSettings() { SettingKey = settingKey }); + if(configs == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(configs, "查询成功"); + } + + /// + /// 更新配置名称 + /// + /// + /// + public WcsApiResponse EditeConfig(EditSettingsRequest request) + { + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + int updateRows = _settingsDao.Update(new AppSettings() + { + SettingKey = request.SettingKey, + SettingName = request.SettingName, + SettingValue = request.SettingValue, + SettingType = request.SettingType, + Remark = request.Remark + }); + return updateRows > 0? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + + +} diff --git a/WcsMain/ApiServe/Service/WcsService/SocketService.cs b/WcsMain/ApiServe/Service/WcsService/SocketService.cs new file mode 100644 index 0000000..45ff99c --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/SocketService.cs @@ -0,0 +1,107 @@ +using WcsMain.ExtendMethod; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Socket; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; +using WcsMain.ApiServe.Factory; +using DataCheck; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class SocketService(AppTcpDao socketDao) +{ + + private readonly AppTcpDao _socketDao = socketDao; + + /// + /// 查询 socket 连接信息 + /// + /// + public WcsApiResponse> GetDB() + { + List? sockets = _socketDao.Query(); + if (sockets == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(sockets, "查询成功"); + } + + /// + /// 编辑 或者 新增一条记录 + /// + /// + /// + public WcsApiResponse EditSocket(EditSocketRequest request) + { + bool checkRequest = CheckData.CheckDataRules(request); + if (!checkRequest || request.IsEdite == default) + { + return WcsApiResponseFactory.RequestErr(); + } + if (!request.SocketNo.IsNumber() || request.IsEdite == default) + { + return WcsApiResponseFactory.RequestErr(); + } + int socketNo = Convert.ToInt32(request.SocketNo); + if ((bool)request.IsEdite!) + { + // 更新现有记录 + int socketStatus = request.SocketStatus.IsNumber() ? Convert.ToInt32(request.SocketStatus) : 0; + /* 插入新记录 */ + AppTcp socket = new() + { + TcpId = socketNo, + TcpIp = request.SocketIpPort, + TcpStatus = socketStatus, + Remark = request.Remark, + }; + int updateRows = _socketDao.Update(socket); + return updateRows > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + else + { + // 新增一条记录 + /* 校验这个编号是否已经存在 */ + List? checks = _socketDao.Query(new AppTcp() { TcpId = socketNo }); + if (checks == default) + { + return WcsApiResponseFactory.DataBaseErr(); + } + if (checks.Count > 0) + { + return WcsApiResponseFactory.DataRepetition("SocketNo重复,已经存在一个相同的编号"); + } + int socketStatus = request.SocketStatus.IsNumber() ? Convert.ToInt32(request.SocketStatus) : 0; + /* 插入新记录 */ + AppTcp socket = new() + { + TcpId = socketNo, + TcpIp = request.SocketIpPort, + TcpStatus = socketStatus, + Remark = request.Remark, + }; + int insertRows = _socketDao.Insert(socket); + return insertRows > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + } + + + /// + /// 删除一条记录 + /// + /// + /// + public WcsApiResponse DeleteSocket(string? socketNoStr) + { + if (!socketNoStr.IsNumber()) + { + return WcsApiResponseFactory.RequestErr(); + } + int socketNo = Convert.ToInt32(socketNoStr); + int deleteRows = _socketDao.Delete(new AppTcp() { TcpId = socketNo }); + return deleteRows > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/StackerService.cs b/WcsMain/ApiServe/Service/WcsService/StackerService.cs new file mode 100644 index 0000000..bce8688 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/StackerService.cs @@ -0,0 +1,129 @@ +using DataCheck; +using System.Collections.Generic; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Stacker; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.EquipOperation.Stacker; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class StackerService(AppStackerDao stackerDao, StackerOperation stackerOperation) +{ + + private readonly AppStackerDao _stackerDao = stackerDao; + private readonly StackerOperation _stackerOperation = stackerOperation; + + /// + /// 查询所有的 堆垛机信息 + /// + /// + public WcsApiResponse> GetStacker() + { + List? stackers = _stackerDao.Select(new AppStacker()); + if(stackers == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(stackers, "查询成功"); + } + + /// + /// 查询所有的 堆垛机状态信息 ---- 从设备返回 + /// + /// + public WcsApiResponse> GetStackerStatus() + { + List? stackers = _stackerDao.Select(new AppStacker()); + if (stackers == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + List getStackerStatusResponses = []; + foreach(AppStacker stacker in stackers) + { + GetStackerStatusResponse stackerStatusResponse = new() + { + StackerId = stacker.StackerId, + StackerName = stacker.StackerName, + StackerStatus = stacker.StackerStatus, + ForkStatus = stacker.ForkStatus, + }; + /* 获取堆垛机状态 */ + var (errMsg, stackerInfo) = _stackerOperation.GetStackerInfo((int)stacker.StackerId!); + if(string.IsNullOrEmpty(errMsg) && stackerInfo != default) + { + stackerStatusResponse.Message = "查询成功"; + stackerStatusResponse.PlcId = stackerInfo.PlcId.ToString(); + stackerStatusResponse.ControlModel = stackerInfo.ControlModel.ToString(); + stackerStatusResponse.StackerStatusEquip = stackerInfo.StackerStatus.ToString(); + stackerStatusResponse.Queue = stackerInfo.Row; + stackerStatusResponse.Line = stackerInfo.Line; + stackerStatusResponse.Layer = stackerInfo.Layer; + stackerStatusResponse.Depth = stackerInfo.Depth; + stackerStatusResponse.Code = stackerInfo.Code.ToString(); + stackerStatusResponse.ErrCode = stackerInfo.ErrCode; + } + else + { + stackerStatusResponse.Message = errMsg; + } + getStackerStatusResponses.Add(stackerStatusResponse); + } + return WcsApiResponseFactory.Success(getStackerStatusResponses, "查询成功"); + } + + /// + /// 添加或者编辑堆垛机信息 + /// + /// + /// + public WcsApiResponse EditStacker(EditStackerRequest request) + { + if(!CheckData.CheckDataRules(request)) + { + return WcsApiResponseFactory.RequestErr(); + } + /* 查询是否存在这个编号的堆垛机,若存在则更新数据,若不存在则添加 */ + List? appStackers = _stackerDao.Select(new AppStacker { StackerId = request.StackerId }); + if (appStackers == default) + { + return WcsApiResponseFactory.DataBaseErr(); + } + if (appStackers.Count == 0) // 没有数据,新增一条新的 + { + var insertResult = _stackerDao.Insert(new AppStacker + { + StackerId = request.StackerId, + StackerName = request.StackerName, + StackerStatus = request.StackerStatus, + ForkStatus = request.ForkStatus, + ActionPlc = request.ActionPlc, + InStand = request.InStand, + OutStand = request.OutStand, + Remark = request.Remark, + }); + return insertResult > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + else // 存在,更新数据 + { + var updateResult = _stackerDao.Update(new AppStacker + { + StackerId = request.StackerId, + StackerName = request.StackerName, + StackerStatus = request.StackerStatus, + ForkStatus = request.ForkStatus, + ActionPlc = request.ActionPlc, + InStand = request.InStand, + OutStand = request.OutStand, + Remark = request.Remark, + }); + return updateResult > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + } + + +} diff --git a/WcsMain/ApiServe/Service/WcsService/UserGroupService.cs b/WcsMain/ApiServe/Service/WcsService/UserGroupService.cs new file mode 100644 index 0000000..025ba47 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/UserGroupService.cs @@ -0,0 +1,95 @@ +using DataCheck; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.UserGroup; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class UserGroupService(AppUserGroupDao userGroupDao) +{ + private readonly AppUserGroupDao _userGroupDao = userGroupDao; + + /// + /// 查询用户组 + /// + /// + public WcsApiResponse> GetUserGroup() + { + var userGroup = _userGroupDao.Query(); + if(userGroup == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(userGroup); + } + + + /// + /// 添加用户组的请求 + /// + /// + /// + public WcsApiResponse AddUserGroup(AddUserGroupRequest request) + { + // 校验请求参数 + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + // 检查该用户组是否存在 + List? checkUserGroup = _userGroupDao.Query(new AppUserGroup { GroupId = request.GroupId }); + if (checkUserGroup == default) + { + return WcsApiResponseFactory.DataBaseErr(); + } + if (checkUserGroup.Count > 0) // 用户组已经存在 + { + return WcsApiResponseFactory.DataRepetition("该用户组已经存在"); + } + // 插入用户 + var userGroup = new AppUserGroup() + { + GroupId = request.GroupId, + GroupName = request.GroupName, + GroupStatus = request.GroupStatus + }; + var result = _userGroupDao.Insert(userGroup); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + + + /// + /// 删除指定的用户组 + /// + /// + /// + public WcsApiResponse DeleteUserGroup(string? groupId) + { + // 校验请求参数 + if (string.IsNullOrEmpty(groupId)) + { + return WcsApiResponseFactory.RequestErr(); + } + // 检查该用户组是否存在 + List? checkUserGroup = _userGroupDao.Query(new AppUserGroup { GroupId = groupId }); + if (checkUserGroup == default) + { + return WcsApiResponseFactory.DataBaseErr(); + } + if (checkUserGroup.Count < 0) // 用户组不存在 + { + return WcsApiResponseFactory.DataRepetition("该用户组不存在"); + } + // 删除用户组 + var result = _userGroupDao.Delete(new AppUserGroup { GroupId = groupId }); + return result > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + + + + } +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/UserRuleService.cs b/WcsMain/ApiServe/Service/WcsService/UserRuleService.cs new file mode 100644 index 0000000..3d52dc4 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/UserRuleService.cs @@ -0,0 +1,46 @@ +using DataCheck; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.UserRule; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class UserRuleService(AppUserRuleDao userRuleDao) +{ + private readonly AppUserRuleDao _userRuleDao = userRuleDao; + + /// + /// 获取用户组权限 + /// + /// + public WcsApiResponse> GetUserRule(string? groupId) + { + var userRule = _userRuleDao.Query(new AppUserRule() { GroupId = groupId }); + if(userRule == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(userRule, "查询成功"); + } + + /// + /// 更新用户组权限 + /// + /// + /// + public WcsApiResponse UpdateUserRule(UpdateUserRuleRequest request) + { + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + bool result = _userRuleDao.UpdateUserRule(request); + return result ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.Fail(); + } + +} diff --git a/WcsMain/ApiServe/Service/WcsService/UserService.cs b/WcsMain/ApiServe/Service/WcsService/UserService.cs new file mode 100644 index 0000000..80f9043 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/UserService.cs @@ -0,0 +1,107 @@ +using DataCheck; +using EncryptTool; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.User; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class UserService(AppUserDao appUserDao, AppMenuDao menuDao) +{ + private readonly AppUserDao _userDao = appUserDao; + private readonly AppMenuDao _menuDao = menuDao; + + /// + /// 登录请求的具体实现 + /// + /// + /// + public WcsApiResponse Login(LoginRequest request) + { + // 数据校验 + var checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + string encryptPwd = Md5Encrypt.EncryptPassword(request.UserPassword); + /* 判断用户账户ID和密码是否正确 */ + List? loginUser = _userDao.Query(new AppUser { UserId = request.UserId, UserPassword = encryptPwd }); + if (loginUser == default) + { + return WcsApiResponseFactory.DataBaseErr(); + } + if (loginUser.Count < 1) + { + return WcsApiResponseFactory.RequestErr(msg: "用户不存在或者密码不正确"); + } + /* 查找该用户的菜单 */ // ---- 目前无权限控制,默认返回所有菜单 + var loginResponse = GetUserMenu(loginUser[0]); + return WcsApiResponseFactory.Success(loginResponse, "登录成功"); + } + + /// + /// 查找用户的菜单 + /// + /// + /// + private LoginResponse? GetUserMenu(AppUser user) + { + var menus = _menuDao.GetMenuWithGroupId(user.UserGroup!); + if (menus == default || menus.Count < 1) + { + return default; + } + LoginResponse loginResponse = new() { UserName = user.UserName, Menu = [] }; + // ---- 找出主菜单 + List<(string Index, string MainMenuName, string ico)> mainMenuInfo = []; + foreach (var menu in menus) + { + if (mainMenuInfo.Exists(e => e.MainMenuName == menu.MainMenuName)) continue; + (string Index, string MainMenuName, string ico) newMenu = (menu.MainMenuIndex!, menu.MainMenuName!, menu.MainMenuIco!); + mainMenuInfo.Add(newMenu); + loginResponse.Menu.Add(new MainMenuData + { + Index = menu.MainMenuIndex, + MainMenu = menu.MainMenuName, + Ico = menu.MainMenuIco, + }); + } + loginResponse.Menu = [.. loginResponse.Menu.OrderBy(o => o.Index)]; + // ---- 找出次级菜单 + foreach (var mainMenu in loginResponse.Menu) + { + var findResult = menus.FindAll(f => f.MainMenuName == mainMenu.MainMenu); + mainMenu.Minor = []; + foreach (var minorMenu in findResult) + { + mainMenu.Minor.Add(new MinorMenuData + { + Index = minorMenu.MinorMenuIndex, + MinorMenu = minorMenu.MinorMenuName, + Router = minorMenu.MinorMenuRouter, + Ico = minorMenu.MinorMenuIco, + }); + } + mainMenu.Minor = [.. mainMenu.Minor.OrderBy(o => o.Index)]; + } + return loginResponse; + } + + /// + /// 分页查询所有用户 + /// + /// + /// + public WcsApiResponse> GetUserWithPage(GetUserWithPageRequest request) + { + var (users, total) = _userDao.QueryWithPage(request); + return WcsApiResponseFactory.Success(users, total.ToString()); + } + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WcsService/WcsTaskService.cs b/WcsMain/ApiServe/Service/WcsService/WcsTaskService.cs new file mode 100644 index 0000000..7ba2505 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/WcsTaskService.cs @@ -0,0 +1,94 @@ +using DataCheck; +using Microsoft.AspNetCore.Mvc; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.WcsTask; +using WcsMain.ApiServe.Factory; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + + +[Service] +public class WcsTaskService(AppWcsTaskDao wcsTaskDao) +{ + private readonly AppWcsTaskDao _wcsTaskDao = wcsTaskDao; + + /// + /// 查询所有的Wcs任务列表 + /// + /// + public WcsApiResponse> GetWcsTask() + { + List? wcsTasks = _wcsTaskDao.Select(); + if(wcsTasks == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(wcsTasks, "查询成功"); + } + + + /// + /// 根据任务号获取任务信息,包括运行表和备份表 + /// + /// + /// + public WcsApiResponse> GetWcsTaskWithTaskId(string taskId) + { + List? wcsTasks = _wcsTaskDao.GetAllTasksWithBakData(taskId); + if (wcsTasks == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(wcsTasks, "查询成功"); + } + + /// + /// 分页查询正在运行的任务 + /// + /// + /// + public WcsApiResponse> GetWcsTaskWithPage([FromBody] GetWcsTaskWithPageRequest request) + { + (List? wcsTasks, int totalRows) = _wcsTaskDao.SelectPage(request); + if (wcsTasks == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(wcsTasks, totalRows.ToString()); + } + + /// + /// 更新任务信息 + /// + /// + /// + public WcsApiResponse UpdateWcsTaskStatus(UpdateWcsTaskStatusRequest request) + { + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + var taskData = _wcsTaskDao.Select(new AppWcsTask() { PlcId = request.PlcId }); + if (taskData == default) + { + return WcsApiResponseFactory.DataBaseErr(); + } + if (taskData.Count < 1) + { + return WcsApiResponseFactory.Fail("找不到该任务,可能该任务不存在或者已经完结"); + } + int updateRows = _wcsTaskDao.Update(new AppWcsTask() + { + PlcId = request.PlcId, + TaskStatus = request.TaskStatus, + Priority = request.Priority, + Remark = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss},手动修改信息" + }); + return updateRows > 0 ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.DataBaseErr(); + } + +} diff --git a/WcsMain/ApiServe/Service/WcsService/WmsTaskService.cs b/WcsMain/ApiServe/Service/WcsService/WmsTaskService.cs new file mode 100644 index 0000000..7536880 --- /dev/null +++ b/WcsMain/ApiServe/Service/WcsService/WmsTaskService.cs @@ -0,0 +1,137 @@ +using DataCheck; +using WcsMain.ApiServe.Controllers.Dto; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.WmsTask; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity.WmsTask; +using WcsMain.ApiServe.Factory; +using WcsMain.Business.CommonAction; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.DataService; +using WcsMain.Enum.TaskEnum; +using WcsMain.StaticData; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WcsService; + +[Service] +public class WmsTaskService(WmsTaskAction wmsTaskAction, AppWmsTaskDao wmsTaskDao, DataBaseData dataBaseData) +{ + private readonly DataBaseData _dataBaseData = dataBaseData; + private readonly WmsTaskAction _wmsTaskAction = wmsTaskAction; + private readonly AppWmsTaskDao _wmsTaskDao = wmsTaskDao; + + + /// + /// 查询Wms 任务表 + /// + /// + public WcsApiResponse> GetWmsTask() + { + List? wmsTasks = _wmsTaskDao.SelectToWeb(); + if(wmsTasks == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(wmsTasks, "查询成功"); + } + + + /// + /// 分页查询 Wms 任务表 + /// + /// + /// + public WcsApiResponse> GetWmsTaskWithPage(GetWmsTaskWithPageRequest request) + { + (List? wmsTasks, int totalRows) = _wmsTaskDao.SelectPage(request); + if(wmsTasks == default) + { + return WcsApiResponseFactory.DataBaseErr>(); + } + return WcsApiResponseFactory.Success(totalRows, wmsTasks); + } + + /// + /// Wcs 前端发来的任务 + /// + /// + /// + public WcsApiResponse GetStackerTask(SetWmsTask request) + { + request.TaskId = _dataBaseData.GetNewUUID(); + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + /* 插入库存信息 */ // ---- 库存由WMS管理,数据库表也无需操作 + AppWmsTask wmsTask = new() + { + TaskId = request.TaskId, + TaskType = request.TaskType, + TaskStatus = (int)WmsTaskStatusEnum.create, + Priority = request.Priority ?? 0, + Origin = request.Origin, + MidPoint = request.Midpoint, + Destination = request.Destination, + VehicleNo = request.VehicleNo, + VehicleSize = request.VehicleSize ?? -1, + Weight = request.Weight ?? -1, + CreateTime = DateTime.Now, + ModifyTime = DateTime.Now, + CreatePerson = StaticString.WCS, + }; + /* 插入任务数据 */ + //int insertRows = _wmsTaskDao.Insert(wmsTasks.ToArray()); // ---- 直接插入任务数据 + int insertRows = _wmsTaskDao.InsertTaskAndMarkErr(wmsTask); // ---- 先清除这个料箱之前未作的任务,然后插入新数据 + if (insertRows > 0) + { + return WcsApiResponseFactory.Success($"任务创建成功,任务号:{request.TaskId}"); + } + return WcsApiResponseFactory.Fail("数据插入失败,请稍后重试或者联系我们"); + } + + /// + /// 变更任务状态 + /// + /// + /// + public WcsApiResponse UpdateStackerTaskStatus(UpdateStackerTaskStatusRequest request) + { + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) + { + return WcsApiResponseFactory.RequestErr(); + } + /* 校验任务是否存在 */ + var tasks = _wmsTaskDao.Select(new AppWmsTask { TaskId = request.TaskId }); + if (tasks == default) + { + // 查询失败 + return WcsApiResponseFactory.DataBaseErr(); + } + if (tasks.Count == 0) + { + // 任务不存在 + return WcsApiResponseFactory.Fail($"任务不存在,任务号:{request.TaskId}"); + } + int? status = request.TaskStatus; + switch (status) + { + case 0: + string resetErrText = _wmsTaskAction.ResetWmsTaskStatus(request.TaskId, request.Destination, StaticString.WCS); // 重置任务 + return string.IsNullOrEmpty(resetErrText) ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.Fail(resetErrText); + case 3: + string completeErrText = _wmsTaskAction.CompleteWmsTaskStatus(request.TaskId, StaticString.WCS); // 完成任务 + return string.IsNullOrEmpty(completeErrText) ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.Fail(completeErrText); + case 999: + string deleteErrText = _wmsTaskAction.DeleteWmsTaskStatus(request.TaskId, StaticString.WCS); // 删除任务 + return string.IsNullOrEmpty(deleteErrText) ? WcsApiResponseFactory.Success() : WcsApiResponseFactory.Fail(deleteErrText); + default: + return WcsApiResponseFactory.Fail($"任务状态不支持:任务状态:{request.TaskStatus}"); + } + } + + + +} \ No newline at end of file diff --git a/WcsMain/ApiServe/Service/WmsService/EquipmentService.cs b/WcsMain/ApiServe/Service/WmsService/EquipmentService.cs new file mode 100644 index 0000000..e7055c4 --- /dev/null +++ b/WcsMain/ApiServe/Service/WmsService/EquipmentService.cs @@ -0,0 +1,37 @@ +using WcsMain.ApiServe.Controllers.Dto.WMSEntity.Equipment; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity; +using WcsMain.ApiServe.Factory; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WmsService; + + +[Service] +public class EquipmentService +{ + + /// + /// 查询站台是否允许动作 + /// + /// + /// + public WmsApiResponse QueryStandStatus(QueryStandStatusRequest request) + { + if (request == null) + { + return WmsApiResponseFactory.RequestErr(msg: "无法解析请求值"); + } + return WmsApiResponseFactory.RequestErr(msg: "暂不支持"); + + } + + /// + /// 通知输送机卸货完成 + /// + /// + public WmsApiResponse UnloadSuccess() + { + return WmsApiResponseFactory.Fail("暂不支持"); + } + +} diff --git a/WcsMain/ApiServe/Service/WmsService/WmsTaskService.cs b/WcsMain/ApiServe/Service/WmsService/WmsTaskService.cs new file mode 100644 index 0000000..789ed35 --- /dev/null +++ b/WcsMain/ApiServe/Service/WmsService/WmsTaskService.cs @@ -0,0 +1,151 @@ +using DataCheck; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity; +using WcsMain.ApiServe.Controllers.Dto.WMSEntity.WmsTask; +using WcsMain.ApiServe.Factory; +using WcsMain.Business.CommonAction; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.TaskEnum; +using WcsMain.ExtendMethod; +using WcsMain.StaticData; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ApiServe.Service.WmsService; + +/// +/// Wms任务接口逻辑 +/// +[Service] +public class WmsTaskService(WmsTaskAction wmsTaskAction, AppWmsTaskDao wmsTaskDao, AppWcsTaskDao wcsTaskDao) +{ + private readonly AppWmsTaskDao _wmsTaskDao = wmsTaskDao; + private readonly WmsTaskAction _wmsTaskAction = wmsTaskAction; + private readonly AppWcsTaskDao _wcsTaskDao = wcsTaskDao; + + /// + /// 接收WMS任务数据处理 + /// + /// + /// + /// + public WmsApiResponse GetStackerTask(List request) + { + if (request.Count < 1) return WmsApiResponseFactory.RequestErr("请求的任务数量为 0"); + /* 插入库存信息 */ // ---- 库存由WMS管理,数据库表也无需操作 + List wmsTasks = []; + foreach (var taskData in request) + { + /* 检验传入的数据格式 */ + bool checkData = CheckData.CheckDataRules(taskData); + if (!checkData) return WmsApiResponseFactory.RequestErr("请求的任务数据部分存在格式问题,请检查数据格式"); + /* 检验起点和终点是否正常 */ + var existOringin = CommonData.AppLocations.ExistWmsLocation(taskData.Origin); + var existDestination = CommonData.AppLocations.ExistWmsLocation(taskData.Destination); + if(!existOringin || !existDestination) return WmsApiResponseFactory.RequestErr($"任务号:{taskData.TaskId} 的起点或者终点不正确"); + /* 构造任务 */ + wmsTasks.Add(new AppWmsTask() + { + TaskId = taskData.TaskId, + TaskType = taskData.TaskType, + TaskStatus = (int)WmsTaskStatusEnum.create, + Priority = taskData.Priority ?? -1, + Origin = taskData.Origin, + MidPoint = taskData.Midpoint, + Destination = taskData.Destination, + VehicleNo = taskData.VehicleNo, + VehicleSize = taskData.VehicleSize ?? -1, + Weight = taskData.Weight ?? -1, + CreateTime = DateTime.Now, + ModifyTime = DateTime.Now, + CreatePerson = StaticString.WMS, + }); + } + List taskIds = []; // 存放任务号 + foreach (var checkEntity in wmsTasks) + { + /* 检查任务号是否重复 */ + List? checkSame = _wmsTaskDao.Select(new AppWmsTask() { TaskId = checkEntity.TaskId }); + if (checkSame == default) return WmsApiResponseFactory.DataBaseErr(); + if (checkSame.Count > 0 || taskIds.Exists(e => e == checkEntity.TaskId)) + { + return WmsApiResponseFactory.RequestErr($"任务:{checkEntity.TaskId} 已存在,请勿重复"); + } + taskIds.Add(checkEntity.TaskId); + } + /* 插入任务数据 */ + //int insertRows = _wmsTaskDao.Insert(wmsTasks.ToArray()); // ---- 直接插入任务数据 + int insertRows = _wmsTaskDao.InsertTaskAndMarkErr([.. wmsTasks]); // ---- 先清除这个料箱之前未作的任务,然后插入新数据 + if (insertRows > 0) return WmsApiResponseFactory.Success($"任务创建成功,任务号:{string.Join(',', taskIds)}"); + return WmsApiResponseFactory.Fail("数据插入失败,请稍后重试或者联系我们"); + } + + + + /// + /// 更新任务状态 + /// + /// + /// + public WmsApiResponse UpdateStackerTaskStatus(UpdateStackerTaskStatusRequest request) + { + bool checkData = CheckData.CheckDataRules(request); + if (!checkData) return WmsApiResponseFactory.RequestErr(); + /* 校验任务是否存在 */ + var tasks = _wmsTaskDao.Select(new AppWmsTask { TaskId = request.TaskId }); + if (tasks == default) return WmsApiResponseFactory.DataBaseErr(); // 数据库连接失败 + if (tasks.Count == 0) return WmsApiResponseFactory.RequestErr($"任务:{request.TaskId} 不存在"); + int? status = request.TaskStatus; + switch(status) + { + case 0: + string resetErrText = _wmsTaskAction.ResetWmsTaskStatus(request.TaskId, request.Destination, StaticString.WMS); // 重置任务 + return string.IsNullOrEmpty(resetErrText) ? WmsApiResponseFactory.Success() : WmsApiResponseFactory.Fail(resetErrText); + case 2: + string completeErrText = _wmsTaskAction.CompleteWmsTaskStatus(request.TaskId, StaticString.WMS); // 完成任务 + return string.IsNullOrEmpty(completeErrText) ? WmsApiResponseFactory.Success() : WmsApiResponseFactory.Fail(completeErrText); + case 1: + string deleteErrText = _wmsTaskAction.DeleteWmsTaskStatus(request.TaskId, StaticString.WMS); // 删除任务 + return string.IsNullOrEmpty(deleteErrText) ? WmsApiResponseFactory.Success() : WmsApiResponseFactory.Fail(deleteErrText); + default: + return WmsApiResponseFactory.RequestErr($"不支持的任务状态:{request.TaskStatus}"); + } + } + + /// + /// WMS向WCS发送任务新终点,卸货位置有货的时候 + /// + /// + /// + public WmsApiResponse GetStackerTaskNewDestination(GetStackerTaskNewDestinationRequest request) + { + if(!CheckData.CheckDataRules(request)) + return WmsApiResponseFactory.RequestErr(); + /* 查找该任务有没有无法卸货 */ + List? wcsTasks = _wcsTaskDao.Select(new AppWcsTask { TaskId = request.TaskId, TaskStatus = (int)WcsTaskStatusEnum.doubleIn }); + if(wcsTasks == default) + return WmsApiResponseFactory.DataBaseErr(); + if(wcsTasks.Count() < 1) + return WmsApiResponseFactory.Fail($"任务号:{request.TaskId} 没有无法卸货的情况"); + wcsTasks = [.. wcsTasks.OrderByDescending(x => x.CompleteTime)]; + var wcsTask = wcsTasks[0]; // 获取最新的一条任务 + /* 如果是最后一条任务则更新WMS任务终点 */ + if(wcsTask.IsLastTask()) + { + _wmsTaskDao.Update(new AppWmsTask { TaskId = request.TaskId, Destination = request.Destination, TaskMsg = "WMS修改终点" }); + //return WmsApiResponseFactory.Success(); + return WmsApiResponseFactory.Fail($"任务号:{request.TaskId} 不是最后一条任务,无法更新终点"); + } + _wmsTaskDao.Update(new AppWmsTask { TaskId = request.TaskId, Destination = request.Destination, TaskMsg = "WMS修改终点" }); + + return WmsApiResponseFactory.Success(); // 返回成功响应 + + + + } + + + + +} \ No newline at end of file diff --git a/WcsMain/AppEntity/LED/LEDData.cs b/WcsMain/AppEntity/LED/LEDData.cs new file mode 100644 index 0000000..596af51 --- /dev/null +++ b/WcsMain/AppEntity/LED/LEDData.cs @@ -0,0 +1,86 @@ +namespace WcsMain.AppEntity.LED; + +/// +/// 需要展示在LED显示器上的内容 +/// +public class LEDData +{ + /// + /// 展示的模式,用于区分各种样式 + /// + public LEDShowModel? ShowModel { get; set; } + + + + /// + /// 出入库模式 + /// + public string? TaskModel { get; set; } + + /// + /// 载具号 + /// + public string? VehicleNo { get; set; } + + /// + /// plc任务号 + /// + public int? PlcId { get; set; } + + /// + /// 任务起点 + /// + public string? Origin { get; set; } + + /// + /// 任务终点 + /// + public string? Destination { get; set; } + + + + public static bool operator ==(LEDData? left, LEDData? right) + { + if (left == default && right == default) { return true; }; + if (left == default && right != default) { return false; }; + if (left != default && right == default) { return false; }; + return left!.Equals(right); + } + + public static bool operator !=(LEDData? left, LEDData? right) + { + if (left == default && right == default) { return false; }; + if (left == default && right != default) { return true; }; + if (left != default && right == default) { return true; }; + return !left!.Equals(right); + } + + public override bool Equals(object? obj) + { + if (obj == default) { return false; } + if (obj is LEDData ledData) + { + if (ShowModel == LEDShowModel.stackerTask) + { + if (TaskModel == ledData.TaskModel + && VehicleNo == ledData.VehicleNo + && PlcId == ledData.PlcId + && Origin == ledData.Origin + && Destination == ledData.Destination) + { return true; } // 上面这些值都相等旧返回 true + } + } + return false; + } + + public override int GetHashCode() + { + return GetType().GetHashCode(); + } +} + + +public enum LEDShowModel +{ + stackerTask = 1, // 显示堆垛机任务 +} \ No newline at end of file diff --git a/WcsMain/AppEntity/SystemData/AppConfigEntity.cs b/WcsMain/AppEntity/SystemData/AppConfigEntity.cs new file mode 100644 index 0000000..0a7a4d0 --- /dev/null +++ b/WcsMain/AppEntity/SystemData/AppConfigEntity.cs @@ -0,0 +1,117 @@ +using WcsMain.WcsAttribute.AppConfig; + +namespace WcsMain.AppEntity.SystemData; + +/* + * ConfigKey 对应表内的 config_key + * 若此处填写没有的 ConfigKey,则该变量会保持默认值;若不填写 ConfigKey 则会读取该属性的名称 + */ + +/// +/// 数据库配置表的映射 +/// +public class AppConfigEntity +{ + /******************************* 系统基本信息 *******************************/ + + /// + /// Wcs 系统在系统中的编号 + /// + [ConfigKey("WcsId")] + public string? WcsId { get; set; } + + + /***************************** API信息 *************************************/ + + /// + /// Wms 的根地址 + /// + [ConfigKey("WmsBaseApiAddress")] + public string? WmsBaseAddressApiAddress { get; set; } + + /// + /// 请求入库的地址 + /// + [ConfigKey("ApplyEnterApiAddress")] + public string? ApplyEnterApiAddress { get; set;} + + /// + /// 发送 Wms 任务状态 + /// + [ConfigKey("SendWmsTaskStatusApiAddress")] + public string? SendWmsTaskStatusApiAddress { get; set; } + + /// + /// 上报载具到达 + /// + [ConfigKey("VehicleArriveApiAddress")] + public string? VehicleArriveApiAddress { get; set; } + + + + + /****************************** 功能配置 ******************************/ + + /// + /// 表示是否开启定时器 + /// + /// + /// 0 - 关闭 + /// 1 - 开启 + /// + [ConfigKey("UseCirculation")] + public string? UseCirculation { get; set; } + + /// + /// 表示是否开启连接PLC + /// + /// + /// 0 - 关闭 + /// 1 - 开启 + /// + [ConfigKey("UseConnectPlc")] + public string? UseConnectPlc { get; set; } + + + /// + /// 表示是否开启 Socket 连接 + /// + /// + /// 0 - 关闭 + /// 1 - 开启 + /// + [ConfigKey("UseSocket")] + public string? UseSocket { get; set; } + + /// + /// 表示是否开启 Opr 连接 电子标签 + /// + /// + /// 0 - 关闭 + /// 1 - 开启 + /// + [ConfigKey("UseOpr")] + public string? UseOpr { get; set; } + + + /// + /// 表示过账确认的方法 + /// + /// + /// 0 - 直接写过账区 + /// 1 - 写过账反馈区 + /// + [ConfigKey("CheckAccountMethod")] + public string? CheckAccountMethod { get; set; } + + + /// + /// 是否是扫码入库 + /// + /// + /// 0 - 不是扫码入库 + /// 1 - 扫码入库 + /// + [ConfigKey("ExcuteStackerInTaskWithScan")] + public string? ExcuteStackerInTaskWithScan { get; set; } +} diff --git a/WcsMain/AppEntity/SystemData/AppSettingJsonEntity.cs b/WcsMain/AppEntity/SystemData/AppSettingJsonEntity.cs new file mode 100644 index 0000000..9e21809 --- /dev/null +++ b/WcsMain/AppEntity/SystemData/AppSettingJsonEntity.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; + +namespace WcsMain.AppEntity.SystemData; + +/// +/// 应用程序内的设置 JSON 的设置映射 +/// +public class AppSettingJsonEntity +{ + /// + /// Mysql 数据库连接字符串 + /// + public string? DBMysql { get; set; } + + /// + /// Mssql 数据库连接字符串 + /// + public string? DBMssql { get; set; } + + /// + /// mssql 本地测试 + /// + public string? DBMssqlLocal { get; set; } + + /// + /// mysql 本地数据库 + /// + public string? DBMysqlLocal { get; set; } + + /// + /// 系统配置项 + /// + [NotNull] + public ApplicationConfig? ApplicationConfig { get; set; } + + /// + /// 系统启动的发布地址 + /// + public string[]? UseUrls { get; set; } +} + + +public class ApplicationConfig +{ + /// + /// 是否仅以 api 形式运行,此状态下不会运行 Wcs 相关内容 + /// + public bool? ApiOnly { get; set; } + + /// + /// 程序的语言 + /// + public string? Language { get; set; } = "zh-CN"; +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/CommonCirculation/ConnectPlcWithCirculation.cs b/WcsMain/Business/CirculationTask/CommonCirculation/ConnectPlcWithCirculation.cs new file mode 100644 index 0000000..55fcac6 --- /dev/null +++ b/WcsMain/Business/CirculationTask/CommonCirculation/ConnectPlcWithCirculation.cs @@ -0,0 +1,35 @@ +using CirculateTool; +using WcsMain.Common; +using WcsMain.EquipOperation; + +namespace WcsMain.Business.CirculationTask.CommonCirculation; + +/// +/// 定时器检测 PLC 是否连接,若没有连接则重新连接 +/// +[Circulation] +public class ConnectPlcWithCirculation(ConnectPLCs connectPLCs) +{ + private readonly ConnectPLCs _connectPLCs = connectPLCs; + + /// + /// 当plc断开时重新连接 PLC + /// + /// + [Circulation("连接PLC", 5000)] + public bool ReconnectPlc() + { + if (CommonData.IsConnectPlc == false) + { + // 如果处于未连接的状态则需要连接 + /* 连接 PLC */ + bool isContinue = _connectPLCs.ConnectPlc(); + if (!isContinue) + { + return false; // 没有需要连接PLC可能,不需要接着继续检测了 + } + } + return true; + } + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/CommonCirculation/DataClear.cs b/WcsMain/Business/CirculationTask/CommonCirculation/DataClear.cs new file mode 100644 index 0000000..00de5d1 --- /dev/null +++ b/WcsMain/Business/CirculationTask/CommonCirculation/DataClear.cs @@ -0,0 +1,53 @@ +using CirculateTool; +using WcsMain.Business.CommonAction; + +namespace WcsMain.Business.CirculationTask.CommonCirculation; + +/// +/// 数据清理 +/// +[Circulation("数据清理")] +public class DataClear(ClearData clearData) +{ + private readonly ClearData _clearData = clearData; + + /// + /// 定时清理无用数据, ---- 每隔10分钟清理一次 + /// + /// + [Circulation("数据清理", 1000 * 60 * 30)] + public bool ClearDataCirculate() + { + try + { + // 清理接口接收记录表 -- 保留多少天 + int apiAcceptCount = _clearData.ClearApiAcceptData(30); + ConsoleLog.Tip(apiAcceptCount > 0, $"[数据清理]接口接收记录:{apiAcceptCount}"); + + // 清理日志文件 -- 清理多少天之前 + int logCount = _clearData.ClearLogFile(60); + ConsoleLog.Tip(logCount > 0, $"[数据清理]日志文件:{logCount}"); + + // 清理接口请求记录表 -- 保留多少天 + int apiRequestCount = _clearData.ClearApiRequestData(30); + ConsoleLog.Tip(apiRequestCount > 0, $"[数据清理]接口请求记录:{apiRequestCount}"); + + // 清理WCS任务备份表 -- 保留多少天 + int apiWcsTaskCount = _clearData.ClearWcsTaskData(30); + ConsoleLog.Tip(apiWcsTaskCount > 0, $"[数据清理]WCS任务备份记录:{apiWcsTaskCount}"); + + // 清理WMS任务表 -- 保留多少天 + int apiWmsTaskCount = _clearData.ClearWmsTaskData(30); + ConsoleLog.Tip(apiWmsTaskCount > 0, $"[数据清理]WMS任务记录:{apiWmsTaskCount}"); + } + catch (Exception ex) + { + ConsoleLog.Error($"[数据清理]线程发生异常,异常信息:{ex}"); + } + + + + return true; + } + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/CommonCirculation/HeartBeat.cs b/WcsMain/Business/CirculationTask/CommonCirculation/HeartBeat.cs new file mode 100644 index 0000000..467d5ee --- /dev/null +++ b/WcsMain/Business/CirculationTask/CommonCirculation/HeartBeat.cs @@ -0,0 +1,18 @@ +using CirculateTool; +using WcsMain.EquipOperation.Convey; + +namespace WcsMain.Business.CirculationTask.CommonCirculation; + +[Circulation] +public class HeartBeat(ConveyOperation conveyOperation) +{ + private readonly ConveyOperation _conveyOperation = conveyOperation; + + [Circulation("输送机心跳", 1000)] + public bool ConveyHeartBeat() + { + _conveyOperation.WriteHeartBeat(); + return true; + } + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/CommonCirculation/LedShow.cs b/WcsMain/Business/CirculationTask/CommonCirculation/LedShow.cs new file mode 100644 index 0000000..eea80eb --- /dev/null +++ b/WcsMain/Business/CirculationTask/CommonCirculation/LedShow.cs @@ -0,0 +1,38 @@ +using WcsMain.Common; + +namespace WcsMain.Business.CirculationTask.CommonCirculation; + +public class LedShow +{ + private static LedShow? _instance; + public static LedShow Instance() => _instance ??= new LedShow(); + + + /// + /// 显示默认值 + /// + public void ShowDefault() + { + CommonTool.LedUsing.ShowDefaultMsg("192.168.103.16", "入库站台"); + CommonTool.LedUsing.ShowDefaultMsg("192.168.103.17", "出库站台"); + } + + + /// + /// 显示入库显示屏 + /// + /// + public DateTime ShowTaskInMessage(string message) + { + CommonTool.LedUsing.ShowDefaultMsg("192.168.103.16", message); + return DateTime.Now; + } + + public DateTime ShowTaskOutMessage(string message) + { + CommonTool.LedUsing.ShowDefaultMsg("192.168.103.17", message); + return DateTime.Now; + } + + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/ElTag/LightElTag.cs b/WcsMain/Business/CirculationTask/ElTag/LightElTag.cs new file mode 100644 index 0000000..4eba076 --- /dev/null +++ b/WcsMain/Business/CirculationTask/ElTag/LightElTag.cs @@ -0,0 +1,97 @@ +using CirculateTool; +using LedSimple; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.ElTag.Atop; +using WcsMain.ElTag.Atop.AtopEnum; +using WcsMain.ElTag.Atop.Entity; +using WcsMain.Enum.TaskEnum; +using WcsMain.EquipOperation.ElTag; + +namespace WcsMain.Business.CirculationTask.ElTag; + + +//[Circulation] +public class LightElTag(AppElTagTaskDao tagTaskDao, AtopOperation atopOperation, AppElTagBaseDao elTagBaseDao) +{ + + private readonly AppElTagTaskDao _tagTaskDao = tagTaskDao; + private readonly AtopOperation _atopOperation = atopOperation; + private readonly AppElTagBaseDao _elTagBaseDao = elTagBaseDao; + + /// + /// 点亮电子标签 + /// + /// + [Circulation("点亮电子标签")] + public bool Light() + { + List? tasks = _tagTaskDao.Query(new AppElTagTask { TaskStatus = (int)ElTagTaskStatusEnum.NeedLight }); + if (tasks == default) + { + ConsoleLog.Exception("【异常】查询 电子标签 任务失败,数据库连接异常"); + Thread.Sleep(5000); + return true; + } + tasks.ForEach(LightTask); + return true; + } + + /// + /// 执行点亮标签任务 + /// + /// + public void LightTask(AppElTagTask tagTask) + { + if (CommonData.AppElTags == default || CommonData.AppElTags.Count < 1) return; + var tagInfo = CommonData.AppElTags.Find(f => f.Location == tagTask.Location); + if(tagInfo == default) + { + ConsoleLog.Warning($"【警告】电子标签:{tagTask.Location} 不存在,无法点亮"); + _tagTaskDao.Update(new AppElTagTask + { + TaskId = tagTask.TaskId, + TaskStatus = (int)ElTagTaskStatusEnum.Error, + ConfirmTime = DateTime.Now, + Remark = "点位不存在" + }); + return; + } + /* 点亮标签 */ + LedColor lEDColor = new Func(() => + { + switch (tagTask.TaskType) + { + case (int)ElTagTaskTypeEnum.PICK: + return LedColor.Green; + case (int)ElTagTaskTypeEnum.STOCK: + return LedColor.Blue; + default: + return LedColor.Red; + }; + }).Invoke(); + var resultException = _atopOperation.ShowMsg(tagInfo.ControllerDisplayName, tagInfo.TagId, tagTask.NeedNum, lEDColor); + if(resultException == default ) + { + ConsoleLog.Success($"点亮电子标签成功,点位:{tagTask.Location},数据:{tagTask.NeedNum},载具号:{tagTask.VehicleNo},标签号:{tagInfo.TagId}"); + _tagTaskDao.Update(new AppElTagTask + { + TaskId = tagTask.TaskId, + TaskStatus = (int)ElTagTaskStatusEnum.Lighting, + LightTime = DateTime.Now, + }); // 更新任务状态 + _elTagBaseDao.Update(new AppElTagBase { Location = tagTask.Location, TaskId = tagTask.TaskId, LastLightTime = DateTime.Now }); // 更新点位状态,绑定任务 + return; + } + ConsoleLog.Warning($"【警告】点亮电子标签失败,点位:{tagTask.Location},数据:{tagTask.NeedNum},载具号:{tagTask.VehicleNo},标签号:{tagInfo.TagId},异常信息:{resultException.Message}"); + } + + + + public void LightPickTask() + { + + } + +} diff --git a/WcsMain/Business/CirculationTask/ElTag/OffElTag.cs b/WcsMain/Business/CirculationTask/ElTag/OffElTag.cs new file mode 100644 index 0000000..33512c1 --- /dev/null +++ b/WcsMain/Business/CirculationTask/ElTag/OffElTag.cs @@ -0,0 +1,91 @@ +using CirculateTool; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.ElTag.Atop.Entity; +using WcsMain.Enum.TaskEnum; +using WcsMain.EquipOperation.ElTag; + +namespace WcsMain.Business.CirculationTask.ElTag; + +/************************* + * 当某个任务组的全部任务都确认之后熄灭所有电子标签 + * + * + * *******************/ + + +/// +/// 熄灭电子标签 +/// +//[Circulation] +public class OffElTag(AppElTagTaskDao tagTaskDao, AtopOperation atopOperation) +{ + private readonly AppElTagTaskDao _tagTaskDao = tagTaskDao; + private readonly AtopOperation _atopOperation = atopOperation; + + + + /// + /// 检验任务组内任务是否全部确认,若全部确认则熄灭所有标签 + /// + /// + [Circulation("检验任务组内任务是否全部确认", 1000)] + public bool CheckTaskIsConfirmAll() + { + /* 查询已经确认的任务的任务组 */ + List? tasks = _tagTaskDao.QueryRunningTask(); + if (tasks == default) + { + ConsoleLog.Exception("【异常】查询 电子标签 任务失败,数据库连接异常"); + Thread.Sleep(5000); + return true; + } + CheckTaskIsConfirmAllMethod(tasks); + return true; + } + + /// + /// 检验任务组内任务是否全部确认,若全部确认则熄灭所有标签 + /// + /// + public void CheckTaskIsConfirmAllMethod(List tasks) + { + if (tasks.Count == 0) return; + List taskGroups = []; + foreach (var task in tasks) + { + if (taskGroups.Contains(task.TaskGroup)) continue; + taskGroups.Add(task.TaskGroup); + } + List offTasks = []; // 需要熄灭的任务 + foreach (var taskGroup in taskGroups) + { + /* 检查其是否还有未确认的标签,若没有未确认的则关闭这些标签 */ + var groupTasks = tasks.FindAll(f => f.TaskGroup == taskGroup); + bool isHaveLightingTask = groupTasks.Exists(e => e.TaskStatus == (int)ElTagTaskStatusEnum.Lighting); + if (isHaveLightingTask) continue; + // 没有点亮的标签了 + ConsoleLog.Info($"电子标签任务组:{taskGroup} 全部确认,熄灭标签"); + foreach (var groupTask in groupTasks) + { + var tagInfo = CommonData.AppElTags.Find(f => f.Location == groupTask.Location); + if (tagInfo == default) continue; + var sendResult = CommonTool.OprTcpClient.Send(TagSendInfo.TurnOffTag(Convert.ToByte(tagInfo.TagId)), tagInfo.ControllerDisplayName!); + if (!sendResult.Success) continue; // 没有发送电子标签成功 + offTasks.Add(new AppElTagTask { TaskId = groupTask.TaskId, TaskStatus = (int)ElTagTaskStatusEnum.Off, OffTime = DateTime.Now }); + } + } + if (offTasks.Count < 1) return; + _tagTaskDao.Update([.. offTasks]); // 更新状态为熄灭 + } + + + + + + + + + +} diff --git a/WcsMain/Business/CirculationTask/ScanMethod.cs b/WcsMain/Business/CirculationTask/ScanMethod.cs new file mode 100644 index 0000000..44f9999 --- /dev/null +++ b/WcsMain/Business/CirculationTask/ScanMethod.cs @@ -0,0 +1,75 @@ +using WcsMain.DataService; + +namespace WcsMain.Business.CirculationTask; + +/// +/// 读取 PLC 的扫码 +/// +//[Circulation] +public class ScanMethod(DataBaseData dataBaseData) +{ + private readonly DataBaseData _dataBaseData = dataBaseData; + + + //private static string[]? _scanId; + //private static ScanCodeAction? scanCodeAction; + + ///// + ///// + ///// + ///// + ////[Circulation("读取扫码信息", 100)] + //public bool ReadScanCode() + //{ + // if (_scanId == default) + // { + // GetScanNo(); + // return true; + // } + // foreach (string scanId in _scanId) + // { + // Thread.Sleep(10); + // (string? errText, short scanOk, string code) = ConveyOperation.Instance().ReadScanInfo(scanId); + // if (!string.IsNullOrEmpty(errText)) + // { + // ConsoleLog.Error($"在扫码:{scanId} 处读取信息失败,信息:{errText}"); + // Thread.Sleep(10000); + // continue; + // } + // if (scanOk != 1) { continue; } + // ConveyOperation.Instance().ClearScanStatus(scanId); + // Task.Factory.StartNew(() => + // { + // ConsoleLog.Info($"扫码器:{scanId} 收到数据:{code}"); + // if (string.IsNullOrEmpty(code)) { return; }// 没有条码,不处理 + // scanCodeAction ??= new ScanCodeAction(); + // Type type = scanCodeAction.GetType(); + // var methods = type.GetMethods(); + // if (methods.Length < 1) { return; } // 类里面没有方法 + // foreach (var method in methods) + // { + // string? methodName = DataBaseData.Instance().GetConfig($"ScanMethod{scanId}"); + // if (string.IsNullOrEmpty(methodName)) { return; } + // if (method.Name == methodName) + // { + // method.Invoke(scanCodeAction, [new ScanCodeClass() { Code = code, StrScanID = scanId }]); + // return; + // } + // } + // ConsoleLog.Error($"扫码器编号:{scanId} 未找到方法,请配置 config 参数。"); + // }); + // } + // return true; + //} + + + + //private void GetScanNo() + //{ + // string? scans = DataBaseData.Instance().GetConfig("scanNo"); + // if (string.IsNullOrEmpty(scans)) { return; } + // _scanId = scans.Split(','); + //} + + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/Stacker/CheckAccount.cs b/WcsMain/Business/CirculationTask/Stacker/CheckAccount.cs new file mode 100644 index 0000000..452e1f1 --- /dev/null +++ b/WcsMain/Business/CirculationTask/Stacker/CheckAccount.cs @@ -0,0 +1,159 @@ +using CirculateTool; +using WcsMain.Business.CommonAction; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum; +using WcsMain.EquipOperation.Entity; +using WcsMain.EquipOperation.Stacker; +using WcsMain.ExtendMethod; + +namespace WcsMain.Business.CirculationTask.Stacker; + +/// +/// 过账,PLC任务回告 +/// +[Circulation()] +public class CheckAccount(StackerOperation stackerOperation, AppWcsTaskDao wcsTaskDao, WCSTaskExecuteEvent wcsTaskEvent) +{ + private readonly WCSTaskExecuteEvent _wcsTaskEvent = wcsTaskEvent; + private readonly AppWcsTaskDao _wcsTaskDao = wcsTaskDao; + private readonly StackerOperation _stackerOperation = stackerOperation; + + /// + /// PLC过账 + /// + /// + [Circulation("监控PLC地址,过账", 1000)] + public bool CheckAccountTask() + { + var openStackers = CommonData.AppStackers.Open(); // 只获取开放的堆垛机 + List? taskFeedBackEntities = _stackerOperation.GetTaskFeedBackData(20, openStackers); + if (taskFeedBackEntities == default || taskFeedBackEntities.Count < 1) + { + return true; + } + foreach (TaskFeedBackEntity taskFeedBackEntity in taskFeedBackEntities) + { + if (taskFeedBackEntity.PlcId == 0) { continue; } + List? wcsTasks = _wcsTaskDao.Select(new AppWcsTask() { PlcId = taskFeedBackEntity.PlcId }); + if (wcsTasks == default) + { + ConsoleLog.Error($"【异常】堆垛机过账查询任务数据失败,和数据库服务器连接中断"); + Thread.Sleep(2000); + continue; // 网络连接中断 + } + if (wcsTasks.Count < 1) + { + ConsoleLog.Warning($"【提示】堆垛机过账区获取任务ID:{taskFeedBackEntity.PlcId},无关联任务"); + _stackerOperation.ClearFeedBackData(taskFeedBackEntity); // 清除过账 + continue; // 任务无数据 + } + var wcsTask = wcsTasks[0]; + switch (taskFeedBackEntity.FeedBackType) + { + case (int)TaskFeedBackTypeEnum.cancel: // 任务取消 + CancelTask(taskFeedBackEntity, wcsTask); + break; + case (int)TaskFeedBackTypeEnum.complete: // 任务完成 + CompleteTask(taskFeedBackEntity, wcsTask); + break; + case (int)TaskFeedBackTypeEnum.doubleIn: // 重复入库 + DoubleInFeedBackType(taskFeedBackEntity, wcsTask); + break; + case (int)TaskFeedBackTypeEnum.emptyOut: // 空出库 + EmptyOutFeedBackType(taskFeedBackEntity, wcsTask); + break; + default: + OtherTaskFeedBackType(taskFeedBackEntity); // 其他任务类型 + break; + } + } + return true; + } + + /// + /// 过账任务取消 + /// + /// + /// + private void CancelTask(TaskFeedBackEntity taskFeedBackEntity, AppWcsTask wcsTask) + { + ConsoleLog.Warning($"【提示】过账区反馈:PlcId:{taskFeedBackEntity.PlcId} 已经被取消,后续任务也一并取消,任务号:{wcsTask.TaskId}"); + string? cleanAccountErr = _stackerOperation.ClearFeedBackData(taskFeedBackEntity); // 清除过账 + if (!string.IsNullOrEmpty(cleanAccountErr)) + { + ConsoleLog.Warning($"【警告】取消任务清除过账区发生异常,信息:{cleanAccountErr}"); + } + /* 执行 WMS 任务异常动作 */ + _wcsTaskEvent.ErrTaskEvent(wcsTask, "任务被PLC取消"); + + } + + /// + /// 过账任务完成 + /// + /// + /// + private void CompleteTask(TaskFeedBackEntity taskFeedBackEntity, AppWcsTask wcsTask) + { + ConsoleLog.Tip($"【提示】过账区获取任务ID:{taskFeedBackEntity.PlcId},任务已经完成,任务号:{wcsTask.TaskId}"); + var cleanAccountErr = _stackerOperation.ClearFeedBackData(taskFeedBackEntity); // 清除过账 + if (!string.IsNullOrEmpty(cleanAccountErr)) + { + ConsoleLog.Warning($"【警告】完成任务清除过账区发生异常,信息:{cleanAccountErr}"); + } + /* 执行 WMS 任务完成动作 */ + _wcsTaskEvent.CompleteTaskEvent(wcsTask, "PLC上报完成"); + } + + + + /// + /// 过账卸货位置有货 + /// + /// + /// + private void DoubleInFeedBackType(TaskFeedBackEntity taskFeedBackEntity, AppWcsTask wcsTask) + { + ConsoleLog.Warning($"[提示]过账区获取任务ID:{taskFeedBackEntity.PlcId},卸货位置有货,任务号:{wcsTask.TaskId}"); + string? cleanAccountErr = _stackerOperation.ResetFeedBackData(taskFeedBackEntity); // 清除过账 + if (!string.IsNullOrEmpty(cleanAccountErr)) + { + ConsoleLog.Warning($"[警告]取消任务清除过账区发生异常,信息:{cleanAccountErr}"); + } + /* 执行 WMS 任务异常动作 */ + _wcsTaskEvent.DoubleInTaskEvent(wcsTask, "卸货位置有货"); + } + + /// + /// 过账取货位置无货 + /// + /// + /// + private void EmptyOutFeedBackType(TaskFeedBackEntity taskFeedBackEntity, AppWcsTask wcsTask) + { + ConsoleLog.Warning($"[警告]过账区获取任务ID:{taskFeedBackEntity.PlcId},取货位置无货,任务号:{wcsTask.TaskId}"); + string? cleanAccountErr = _stackerOperation.ResetFeedBackData(taskFeedBackEntity); // 清除过账 + if (!string.IsNullOrEmpty(cleanAccountErr)) + { + ConsoleLog.Warning($"[警告]取消任务清除过账区发生异常,信息:{cleanAccountErr}"); + } + /* 执行 WMS 任务异常动作 */ + _wcsTaskEvent.EmptyOutTaskEvent(wcsTask, "取货位置无货"); + } + + + + /// + /// 其他过账类型 + /// + /// + private void OtherTaskFeedBackType(TaskFeedBackEntity taskFeedBackEntity) + { + ConsoleLog.Warning($"[警告]过账区获取任务ID:{taskFeedBackEntity.PlcId},无法识别的过账类型:{taskFeedBackEntity.FeedBackType}"); + _stackerOperation.ClearFeedBackData(taskFeedBackEntity); // 清除过账 + } + + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/Stacker/ExeTaskDoubleFork.cs b/WcsMain/Business/CirculationTask/Stacker/ExeTaskDoubleFork.cs new file mode 100644 index 0000000..72a9302 --- /dev/null +++ b/WcsMain/Business/CirculationTask/Stacker/ExeTaskDoubleFork.cs @@ -0,0 +1,469 @@ +using CirculateTool; +using WcsMain.Business.CommonAction; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.MixDao; +using WcsMain.DataBase.TableEntity; +using WcsMain.DataService; +using WcsMain.Enum.Stacker; +using WcsMain.Enum.TaskEnum; +using WcsMain.EquipOperation.Convey; +using WcsMain.EquipOperation.Entity.Stacker; +using WcsMain.EquipOperation.Stacker; +using WcsMain.ExtendMethod; +using static Dm.net.buffer.ByteArrayBuffer; + +namespace WcsMain.Business.CirculationTask.Stacker; + + +/// +/// 双货叉单深位执行堆垛机任务 ---- 卡特模式 +/// +//[Circulation("双货叉单深位执行堆垛机任务")] +public class ExeTaskDoubleFork( + StackerOperation stackerOperation, AppWcsTaskDao wcsTaskDao, WCSTaskExecuteEvent wcsTaskEvent, + ConveyOperation conveyOperation, DataBaseData dataBaseData) +{ + private readonly WCSTaskExecuteEvent _wcsTaskEvent = wcsTaskEvent; + private readonly AppWcsTaskDao _wcsTaskDao = wcsTaskDao; + private readonly StackerOperation _stackerOperation = stackerOperation; + private readonly ConveyOperation _conveyOperation = conveyOperation; + private readonly DataBaseData _dataBaseData = dataBaseData; + + /// + /// 执行堆垛机任务 + /// + /// + [Circulation("执行堆垛机任务")] + public bool ExecuteStackerTask() + { + List tasks = []; + foreach (var stacker in CommonData.AppStackers.Open()) + { + tasks.Add(Task.Factory.StartNew(() => + { + var stackerUseStatus = _stackerOperation.StackerCanUse(stacker.StackerId, out int plcId, out int spare1); + if (stackerUseStatus == StackerUseStatusEnum.Free) + { + /* 空闲时正常执行任务 */ + // 移库 + bool exeMoveTask = ExecuteMoveTask(stacker.StackerId); + if (exeMoveTask) return; + // 出库 + bool exeOutTask = ExecuteOutTask(stacker.StackerId); + if (exeOutTask) return; + // 入库 + bool exeInTask = ExecuteInTask(stacker.StackerId); + if (exeInTask) return; + // 拣选 + //bool exePickTask = ExecutePickTask(stacker.StackerId); + //if (exePickTask) return; + } + if (stackerUseStatus == StackerUseStatusEnum.waitTask) + { + /* 重复入库时执行任务 */ + bool exeDoubleInTask = ExecuteDoubleInTask(stacker.StackerId, plcId, 1); + if (exeDoubleInTask) return; + exeDoubleInTask = ExecuteDoubleInTask(stacker.StackerId, spare1, 2); + if (exeDoubleInTask) return; + } + })); + } + Task.WaitAll([.. tasks]); + return true; + } + + + /// + /// 执行堆垛机入库任务,若执行了任务则返回 true + /// + /// + /// + private bool ExecuteInTask(int? stackerId) + { + if (stackerId == default) return false; + /* 检查入库站台是否允许取货 */ + bool allowGetGoods = _conveyOperation.AllGetVehicle(stackerId.ToString()); + if(!allowGetGoods) return false; // 入库站台不允许取货 + /* 读取入库站台的条码 */ + var codes = _conveyOperation.ReadConveyCode(stackerId.ToString()); + if(codes == default || codes.Count != 2) return false; + /* 构造任务数据 */ + bool isWriteTask = false; // 指示是否写入任务 + for (int i = 0; i < 2; i++) + { + /* 校验货叉开启 */ + string forkStatus = CommonData.AppStackers.First(f => f.StackerId == stackerId).ForkStatus ?? ""; + if (forkStatus.Length <= i || forkStatus[i] != '1') continue; // 获取数据失败或者货叉未开启 + var code = codes[i]; + if (string.IsNullOrEmpty(code)) continue; // 检查条码是否为空 + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,入库位置:{i + 1} 返回:{code}"); + if (code == "NoRead") // 检查条码是否为 NoRead + { + /* 检查卸货站台是否允许卸货 */ + bool allowSetGoods = _conveyOperation.AllSetVehicle(stackerId.ToString()); + if (!allowSetGoods) continue; // 出库站台不允许取货 + /* 生成一个直接卸货出去的任务 */ + int? plcId = _dataBaseData.GetNewPlcTaskId(); + if(plcId == default || plcId == 0) continue; + StackerPlcTask errTask = StackerPlcTask.DefaultErrTask((int)plcId!, (int)stackerId!, i + 1); + string WriteTaskErrText = _stackerOperation.WriteTask(errTask, i + 1); + if(string.IsNullOrEmpty(WriteTaskErrText)) // 写入成功 + { + ConsoleLog.Success($"{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code},写入任务成功,{errTask}"); + isWriteTask = true; + } + else + { + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code},写入任务失败,{errTask},异常信息:{WriteTaskErrText},"); + } + continue; + } + var wcsTasks = _wcsTaskDao.Select(new AppWcsTask { VehicleNo = code, TaskStatus = (int)WcsTaskStatusEnum.create }); + if(wcsTasks == default) // 查询任务失败 + { + ConsoleLog.Exception($"【警告】{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code} 查找任务失败,和数据库服务器连接中断"); + continue; + } + if(wcsTasks.Count < 1) // 没有任务 + { + /* 检查卸货站台是否允许卸货 */ + bool allowSetGoods = _conveyOperation.AllSetVehicle(stackerId.ToString()); + if (!allowSetGoods) continue; // 出库站台不允许取货 + /* 生成一个直接卸货出去的任务 */ + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code} 没有找到对应任务"); + int? plcId = _dataBaseData.GetNewPlcTaskId(); + if (plcId == default || plcId == 0) continue; + StackerPlcTask errTask = StackerPlcTask.DefaultErrTask((int)plcId!, (int)stackerId!, i + 1, code.ToPlcVehicleNo()); + string WriteTaskErrText = _stackerOperation.WriteTask(errTask, i + 1); + if (string.IsNullOrEmpty(WriteTaskErrText)) // 写入成功 + { + ConsoleLog.Success($"{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code},写入任务成功,{errTask}"); + isWriteTask = true; + } + else + { + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code},写入任务失败,{errTask},异常信息:{WriteTaskErrText},"); + } + continue; + } + var wcsTask = wcsTasks[0]; // 找出第一个任务 + /* 校验终点是否正确 */ + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Destination); + if(destinationLocationInfo == default) // 任务终点错误,理论上此处不应该出现异常 + { + /* 检查卸货站台是否允许卸货 */ + bool allowSetGoods = _conveyOperation.AllSetVehicle(stackerId.ToString()); + if (!allowSetGoods) continue; // 出库站台不允许取货 + /* 生成一个直接卸货出去的任务 */ + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code} 无法识别的入库库位"); + int? plcId = _dataBaseData.GetNewPlcTaskId(); + if (plcId == default || plcId == 0) continue; + StackerPlcTask errTask = StackerPlcTask.DefaultErrTask((int)plcId!, (int)stackerId!, i + 1, code.ToPlcVehicleNo()); + string WriteTaskErrText = _stackerOperation.WriteTask(errTask, i + 1); + if (string.IsNullOrEmpty(WriteTaskErrText)) // 写入成功 + { + ConsoleLog.Success($"{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code},写入任务成功,{errTask}"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, "入库库位不正确"); + isWriteTask = true; + } + else + { + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code},写入任务失败,{errTask},异常信息:{WriteTaskErrText},"); + } + continue; + } + /* 判断冲突货位是否有任务 */ + var runningWcsTasks = _wcsTaskDao.QueryTaskWithWcsLocation(destinationLocationInfo.InterveneLocation); + if (runningWcsTasks == default) continue; // 查询失败 + if(runningWcsTasks.Count > 0) continue; // 存在冲突点位,等待 + /* 下发任务 */ + StackerPlcTask stackerTask = wcsTask.ToStackerInTask((int)stackerId!, i + 1, destinationLocationInfo); + string WriteStackerTaskErrText = _stackerOperation.WriteTask(stackerTask, i + 1); + if (string.IsNullOrEmpty(WriteStackerTaskErrText)) // 写入成功 + { + ConsoleLog.Success($"{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code},写入任务成功,{stackerTask}"); + _wcsTaskEvent.StartTaskEvent(wcsTask); // 任务开始 + isWriteTask = true; + } + else + { + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,入库位置:{i + 1} 条码:{code},写入任务失败,{stackerTask},异常信息:{WriteStackerTaskErrText},"); + } + } + if (isWriteTask) + { + string confirmResult = _stackerOperation.WriteTaskConfirm(stackerId); // 写入任务确认 + ConsoleLog.Info($"堆垛机入库任务写任务确认;信息:{(string.IsNullOrEmpty(confirmResult) ? "成功" : confirmResult)}"); + Thread.Sleep(1000); + } + return isWriteTask; + } + + /// + /// 执行堆垛机出库任务,若执行了任务则返回 true + /// + /// + /// + public bool ExecuteOutTask(int? stackerId) + { + if (stackerId == default) return false; + /* 检查出库站台是否可以卸货 */ + bool allowSetGoods = _conveyOperation.AllSetVehicle(stackerId.ToString()); + if (!allowSetGoods) return false; // 出库站台不允许取货 + var wcsTasks = _wcsTaskDao.SelectOutTaskWithStacker((int)stackerId!); + if (wcsTasks == default) + { + ConsoleLog.Exception(string.Format("【异常】{0} 号堆垛机出库任务查询失败,与数据库连接异常", stackerId)); + Thread.Sleep(5000); + return false; + } + if (wcsTasks.Count == 0) return false; + bool isWriteTask = false; // 指示是否写入任务 + int writeTaskCount = 0; // 表示执行了多少条任务 + for (int i = 0; i < wcsTasks.Count; i++) + { + if (writeTaskCount >= 2) break; // 最多执行两条 + /* 校验货叉开启 */ + string forkStatus = CommonData.AppStackers.First(f => f.StackerId == stackerId).ForkStatus ?? ""; + if (forkStatus.Length <= i || forkStatus[i] != '1') + { + writeTaskCount ++; + continue; // 获取数据失败或者货叉未开启 + } + var wcsTask = wcsTasks[i]; + /* 校验起点是否正确 */ + var originLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Origin); + if (originLocationInfo == default) // 任务起点错误,理论上此处不应该出现异常 + { + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,出库库位:{wcsTask.Origin} 条码:{wcsTask.VehicleNo} 无法识别的出库库位"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, "出库的库位不正确"); + continue; + } + /* 判断冲突货位是否有任务 */ + var runningWcsTasks = _wcsTaskDao.QueryTaskWithWcsLocation(originLocationInfo.InterveneLocation); + if (runningWcsTasks == default) continue; // 查询失败 + if (runningWcsTasks.Count > 0) continue; // 存在冲突点位,等待 + /* 下发任务 */ + StackerPlcTask stackerTask = wcsTask.ToStackerOutTask((int)stackerId!, i + 1, originLocationInfo); + string WriteStackerTaskErrText = _stackerOperation.WriteTask(stackerTask, i + 1); + if (string.IsNullOrEmpty(WriteStackerTaskErrText)) // 写入成功 + { + ConsoleLog.Success($"{stackerId} 号堆垛机,出库位置:{i + 1} 条码:{wcsTask.VehicleNo},写入任务成功,{stackerTask}"); + _wcsTaskEvent.StartTaskEvent(wcsTask); // 任务开始 + writeTaskCount++; + isWriteTask = true; + } + else + { + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,出库位置:{i + 1} 条码:{wcsTask.VehicleNo},写入任务失败,{stackerTask},异常信息:{WriteStackerTaskErrText},"); + } + } + if (isWriteTask) + { + string confirmResult = _stackerOperation.WriteTaskConfirm(stackerId); // 写入任务确认 + ConsoleLog.Info($"堆垛机出库任务写任务确认;信息:{(string.IsNullOrEmpty(confirmResult) ? "成功" : confirmResult)}"); + Thread.Sleep(1000); + } + return isWriteTask; + } + + /// + /// 执行堆垛机拣选任务,若执行了任务则返回 true + /// + /// + /// + public bool ExecutePickTask(int? stackerId) + { + if (stackerId == default) return false; + var wcsTasks = _wcsTaskDao.SelectPickOutTaskWithStacker((int)stackerId!); + if (wcsTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机拣选任务查询失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (wcsTasks.Count == 0) return false; + var wcsTask = wcsTasks[0]; // 取第一条任务 + /* 校验出库站台是否可以卸货 */ + // TODO + /* 检验并返回起点终点信息 */ + var originLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Origin); + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Destination); + if (destinationLocationInfo == default || originLocationInfo == default) // 起点终点错误,直接标记错误 + { + // 已经在接口内做了检查,按道理此处不会出错。直接返回异常,任务直接取消 + ConsoleLog.Warning($"【警告】任务号:{wcsTask.TaskId},Plc任务号:{wcsTask.PlcId} 无法执行,起点或者终点存在异常"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"起点或者终点存在异常"); + return false; + } + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = wcsTask.PlcId, + TaskType = Convert.ToInt16(wcsTask.TaskType), + GetStand = 0, + InTunnelId = Convert.ToInt16(stackerId), + OutTunnelId = Convert.ToInt16(stackerId), + SetStand = 0, + GetQueue = Convert.ToInt16(originLocationInfo.Queue), + GetLine = Convert.ToInt16(originLocationInfo.Line), + GetLayer = Convert.ToInt16(originLocationInfo.Layer), + GetDeep = Convert.ToInt16(originLocationInfo.Depth), + SetQueue = Convert.ToInt16(destinationLocationInfo.Queue), + SetLine = Convert.ToInt16(destinationLocationInfo.Line), + SetLayer = Convert.ToInt16(destinationLocationInfo.Layer), + SetDeep = Convert.ToInt16(destinationLocationInfo.Depth), + Size = Convert.ToInt16(wcsTask.VehicleSize), + Weight = Convert.ToInt16(wcsTask.Weight), + Code = wcsTask.PlcVehicleNo ?? 0, + }; + var writeStackerTaskErrText = _stackerOperation.WriteTask(stackerTask); + if (string.IsNullOrEmpty(writeStackerTaskErrText)) + { + ConsoleLog.Success($"堆垛机:{stackerId} 写入拣选任务成功,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination}"); + _wcsTaskEvent.StartTaskEvent(wcsTask); + return true; + } + else + { + ConsoleLog.Warning($"【警告】堆垛机:{stackerId} 写入拣选任务失败,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination},异常信息:{writeStackerTaskErrText}"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"写入任务失败,异常信息:{writeStackerTaskErrText}"); + } + return false; + + } + + /// + /// 执行堆垛机移库任务,若执行了任务则返回 true + /// + /// + /// + public bool ExecuteMoveTask(int? stackerId) + { + if (stackerId == default) return false; + var wcsTasks = _wcsTaskDao.SelectMoveTaskWithStacker((int)stackerId!); + if (wcsTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机移库任务查询失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (wcsTasks.Count == 0) return false; + bool isWriteTask = false; // 指示是否写入任务 + int writeTaskCount = 0; // 表示执行了多少条任务 + for (int i = 0; i < wcsTasks.Count; i++) + { + if (writeTaskCount >= 2) break; // 最多执行两条 + /* 校验货叉开启 */ + string forkStatus = CommonData.AppStackers.First(f => f.StackerId == stackerId).ForkStatus ?? ""; + if (forkStatus.Length <= i || forkStatus[i] != '1') + { + writeTaskCount++; + continue; // 获取数据失败或者货叉未开启 + } + var wcsTask = wcsTasks[i]; + /* 校验起点是否正确 */ + var originLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Origin); + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Origin); + if (originLocationInfo == default || destinationLocationInfo == default) // 任务起点终点错误,理论上此处不应该出现异常 + { + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,移库库位:{wcsTask.Origin} 条码:{wcsTask.VehicleNo} 无法识别的库位"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, "移库的库位不正确"); + continue; + } + /* 判断冲突货位是否有任务,起点终点都要判断 */ + var runningWcsTasks = _wcsTaskDao.QueryTaskWithWcsLocation(destinationLocationInfo.InterveneLocation, originLocationInfo.InterveneLocation); + if (runningWcsTasks == default) continue; // 查询失败 + if (runningWcsTasks.Count > 0) continue; // 存在冲突点位,等待 + /* 下发任务 */ + StackerPlcTask stackerTask = wcsTask.ToStackerMoveTask((int)stackerId!, originLocationInfo, destinationLocationInfo); + string WriteStackerTaskErrText = _stackerOperation.WriteTask(stackerTask, i + 1); + if (string.IsNullOrEmpty(WriteStackerTaskErrText)) // 写入成功 + { + ConsoleLog.Success($"{stackerId} 号堆垛机,移库位置:{i + 1} 条码:{wcsTask.VehicleNo},写入任务成功,{stackerTask}"); + _wcsTaskEvent.StartTaskEvent(wcsTask); // 任务开始 + writeTaskCount++; + isWriteTask = true; + } + else + { + ConsoleLog.Warning($"【警告】{stackerId} 号堆垛机,移库位置:{i + 1} 条码:{wcsTask.VehicleNo},写入任务失败,{stackerTask},异常信息:{WriteStackerTaskErrText},"); + } + } + if (isWriteTask) + { + string confirmResult = _stackerOperation.WriteTaskConfirm(stackerId); // 写入任务确认 + ConsoleLog.Info($"堆垛机移库任务写任务确认;信息:{(string.IsNullOrEmpty(confirmResult) ? "成功" : confirmResult)}"); + Thread.Sleep(1000); + } + return isWriteTask; + + } + + /// + /// 执行重复入库新任务,若执行了返回true + /// + /// + /// + /// 货叉编号 + /// + public bool ExecuteDoubleInTask(int? stackerId, int plcId, int forkId) + { + if (stackerId == default || plcId == 0) return false; + /* 查找这个PlcId对应的wms任务 */ + List? doubleInTasks = _wcsTaskDao.Select(new AppWcsTask { PlcId = plcId }); + if (doubleInTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机重复入库任务检验失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (doubleInTasks.Count == 0) return false; // 找不到对应的PlcId的任务 + var doubleTask = doubleInTasks[0]; + /* 判断这个任务是否出现卸货位置有货 */ + if(doubleTask.TaskStatus != (int)WcsTaskStatusEnum.doubleIn) { return false; } // 没有重复入库不执行下面方法 + /* 查找这个任务的新任务 */ + List? newTasks = _wcsTaskDao.Select(new AppWcsTask { TaskId = doubleTask.TaskId, TaskType = (int)WcsTaskTypeEnum.newTaskForDoubleIn, TaskStatus = (int)WcsTaskStatusEnum.create }); + if (newTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机重复入库任务新任务查询失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (newTasks.Count == 0) return false; + var newTask = newTasks[0]; + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(newTask.Destination); + if (destinationLocationInfo == default) // 终点错误,直接标记错误 + { + // 已经在接口内做了检查,按道理此处不会出错。直接返回异常,任务直接取消 + ConsoleLog.Warning($"【警告】任务号:{newTask.TaskId},Plc任务号:{newTask.PlcId} 无法执行,新终点存在异常"); + _wcsTaskEvent.ErrTaskEvent(newTask, $"新终点存在异常"); + return false; + } + /* 判断冲突货位是否有任务 */ + var runningWcsTasks = _wcsTaskDao.QueryTaskWithWcsLocation(destinationLocationInfo.InterveneLocation); + if (runningWcsTasks == default) return false; // 查询失败 + if (runningWcsTasks.Count > 0) return false; // 存在冲突点位,等待 + /* 下发任务 */ + StackerPlcTask stackerTask = newTask.ToStackerInTask((int)stackerId!, forkId, destinationLocationInfo); + var writeStackerTaskErrText = _stackerOperation.WriteTask(stackerTask); + if (string.IsNullOrEmpty(writeStackerTaskErrText)) + { + ConsoleLog.Success($"堆垛机:{stackerId} 写入重复入库新任务成功,箱号:{newTask.VehicleNo},PlcId:{newTask.PlcId};新目的地:{newTask.Destination}"); + _wcsTaskEvent.StartTaskEvent(newTask); + return true; + } + else + { + ConsoleLog.Warning($"【警告】堆垛机:{stackerId} 写入重复入库新任务失败,箱号:{newTask.VehicleNo},PlcId:{newTask.PlcId};新目的地:{newTask.Destination},异常信息:{writeStackerTaskErrText}"); + _wcsTaskEvent.ErrTaskEvent(newTask, $"写入任务失败,异常信息:{writeStackerTaskErrText}"); + } + return false; + } + + + + + +} diff --git a/WcsMain/Business/CirculationTask/Stacker/ExecuteWcsTask.cs b/WcsMain/Business/CirculationTask/Stacker/ExecuteWcsTask.cs new file mode 100644 index 0000000..78342f2 --- /dev/null +++ b/WcsMain/Business/CirculationTask/Stacker/ExecuteWcsTask.cs @@ -0,0 +1,410 @@ +using CirculateTool; +using WcsMain.Business.CommonAction; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.Stacker; +using WcsMain.Enum.TaskEnum; +using WcsMain.EquipOperation.Convey; +using WcsMain.EquipOperation.Entity; +using WcsMain.EquipOperation.Entity.Stacker; +using WcsMain.EquipOperation.Entity.StackerConvey; +using WcsMain.EquipOperation.Stacker; +using WcsMain.EquipOperation.StackerConvey; +using WcsMain.ExtendMethod; + +namespace WcsMain.Business.CirculationTask.Stacker; + +/// +/// 执行堆垛机任务类 +/// +[Circulation(tags: ["stacker"])] +public class ExecuteWcsTask(StackerOperation stackerOperation, AppWcsTaskDao wcsTaskDao, WCSTaskExecuteEvent wcsTaskEvent, StackerConveyOperation stackerConveyOperation) +{ + private readonly WCSTaskExecuteEvent _wcsTaskEvent = wcsTaskEvent; + private readonly AppWcsTaskDao _wcsTaskDao = wcsTaskDao; + private readonly StackerOperation _stackerOperation = stackerOperation; + private readonly StackerConveyOperation _stackerConveyOperation = stackerConveyOperation; + + /// + /// 执行堆垛机任务 + /// + /// + [Circulation("执行堆垛机任务")] + public bool ExecuteStackerTask() + { + foreach (var stacker in CommonData.AppStackers.Open()) + { + + var stackerUseStatus = _stackerOperation.StackerCanUse(stacker.StackerId, out int _, out int _); + if (stackerUseStatus == StackerUseStatusEnum.Free) + { + /* 空闲时正常执行任务 */ + // 入库 + if (!CommonData.AppConfig.ExcuteStackerInTaskWithScan.ToBool()) // 不是扫码入库 + { + bool exeInTask = ExecuteInTask(stacker.StackerId); + if (exeInTask) continue; + } + // 出库 + bool exeOutTask = ExecuteOutTask(stacker.StackerId); + if (exeOutTask) continue; + // 拣选 + //bool exePickTask = ExecutePickTask(stacker.StackerId); + //if (exePickTask) continue; + // 移库 + bool exeMoveTask = ExecuteMoveTask(stacker.StackerId); + if (exeMoveTask) continue; + } + //if (stackerUseStatus == StackerUseStatusEnum.waitTask) + //{ + // /* 重复入库时执行任务 */ + // bool exeDoubleInTask = ExecuteDoubleInTask(stacker.StackerId, plcId); + // if (exeDoubleInTask) continue; + //} + } + return true; + } + + + /// + /// 执行堆垛机入库任务,若执行了任务则返回 true + /// + /// + /// + private bool ExecuteInTask(int? stackerId) + { + if (stackerId == default) return false; + var wcsTasks = _wcsTaskDao.SelectInTaskWithStacker((int)stackerId!); // 此处方法内已经判断状态 + if (wcsTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号输送机入库任务查询失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (wcsTasks.Count == 0) return false; + var wcsTask = wcsTasks[0]; // 取第一条任务 + /* 校验入库站台是否可以取货 */ + (bool isSuccess, uint value) = _stackerConveyOperation.ReadSenserStatus(wcsTask.Origin); + if (!isSuccess) return false; //站台不允许取货 + + // TODO + /* 检验并返回起点终点信息 */ + + var originLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Origin); + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Destination); + if (destinationLocationInfo == default || originLocationInfo == default) // 起点终点错误,直接标记错误 + { + // 已经在接口内做了检查,按道理此处不会出错。直接返回异常,任务直接取消 + ConsoleLog.Warning($"【警告】任务号:{wcsTask.TaskId},Plc任务号:{wcsTask.PlcId} 无法执行,起点或者终点存在异常"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"起点或者终点存在异常"); + return false; + } + StackerConveyPlcTask conveyTask = new() + { + //StackerId = stackerId, + StackerConveyId = wcsTask.Origin, + PlcId = wcsTask.PlcId, + TaskType = Convert.ToInt16(wcsTask.TaskType), + GetStand = wcsTask.Origin == "111" ? (short)111 : (short)106, + InTunnelId = 3, + OutTunnelId = 3, + SetStand = 0, + GetQueue = 1, + GetLine = 1, + GetLayer = 1, + GetDeep = 1, + SetQueue = Convert.ToInt16(destinationLocationInfo.Queue), + SetLine = Convert.ToInt16(destinationLocationInfo.Line), + SetLayer = Convert.ToInt16(destinationLocationInfo.Layer), + SetDeep = Convert.ToInt16(destinationLocationInfo.Depth), + Size = Convert.ToInt16(wcsTask.VehicleSize), + Weight = Convert.ToInt16(wcsTask.Weight), + Code = wcsTask.PlcVehicleNo ?? 0, + }; + // 调用输送机操作类的方法,将任务写入输送机,获取写入任务的错误信息 + var writeStackerTaskErrText = _stackerConveyOperation.WriteTask(conveyTask); + if (string.IsNullOrEmpty(writeStackerTaskErrText)) + { + ConsoleLog.Success($"输送机:{stackerId} 写入入库任务成功,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination}"); + _wcsTaskEvent.StartTaskEvent(wcsTask); + return true; + } + else + { + ConsoleLog.Warning($"【警告】输送机:{stackerId} 写入入库任务失败,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination},异常信息:{writeStackerTaskErrText}"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"写入任务失败,异常信息:{writeStackerTaskErrText}"); + } + return false; + } + + /// + /// 执行堆垛机出库任务,若执行了任务则返回 true + /// + /// + /// + public bool ExecuteOutTask(int? stackerId) + { + if (stackerId == default) return false; + var wcsTasks = _wcsTaskDao.SelectOutTaskWithStacker((int)stackerId!); + if (wcsTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机出库任务查询失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (wcsTasks.Count == 0) return false; + var wcsTask = wcsTasks[0]; // 取第一条任务 + /* 校验出库站台是否可以卸货 */ + // TODO + /* 检验并返回起点终点信息 */ + var originLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Origin); + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Destination); + if (destinationLocationInfo == default || originLocationInfo == default) // 起点终点错误,直接标记错误 + { + // 已经在接口内做了检查,按道理此处不会出错。直接返回异常,任务直接取消 + ConsoleLog.Warning($"【警告】任务号:{wcsTask.TaskId},Plc任务号:{wcsTask.PlcId} 无法执行,起点或者终点存在异常"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"起点或者终点存在异常"); + return false; + } + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = wcsTask.PlcId, + TaskType = Convert.ToInt16(wcsTask.TaskType), + GetStand = 0, + InTunnelId = 3, + OutTunnelId = 3, + SetStand = Convert.ToInt16(wcsTask.Destination), + GetQueue = Convert.ToInt16(originLocationInfo.Queue), + GetLine = Convert.ToInt16(originLocationInfo.Line), + GetLayer = Convert.ToInt16(originLocationInfo.Layer), + GetDeep = Convert.ToInt16(originLocationInfo.Depth), + SetQueue = 2, + SetLine = 1, + SetLayer = 1, + SetDeep = 1, + Size = Convert.ToInt16(wcsTask.VehicleSize), + Weight = Convert.ToInt16(wcsTask.Weight), + Code = wcsTask.PlcVehicleNo ?? 0, + }; + var writeStackerTaskErrText = _stackerOperation.WriteTask(stackerTask); + if (string.IsNullOrEmpty(writeStackerTaskErrText)) + { + ConsoleLog.Success($"堆垛机:{stackerId} 写入出库任务成功,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination}"); + _wcsTaskEvent.StartTaskEvent(wcsTask); + return true; + } + else + { + ConsoleLog.Warning($"【警告】堆垛机:{stackerId} 写入出库任务失败,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination},异常信息:{writeStackerTaskErrText}"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"写入任务失败,异常信息:{writeStackerTaskErrText}"); + } + return false; + + } + + /// + /// 执行堆垛机拣选任务,若执行了任务则返回 true + /// + /// + /// + public bool ExecutePickTask(int? stackerId) + { + if (stackerId == default) return false; + var wcsTasks = _wcsTaskDao.SelectPickOutTaskWithStacker((int)stackerId!); + if (wcsTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机拣选任务查询失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (wcsTasks.Count == 0) return false; + var wcsTask = wcsTasks[0]; // 取第一条任务 + /* 校验出库站台是否可以卸货 */ + // TODO + /* 检验并返回起点终点信息 */ + var originLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Origin); + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Destination); + if (destinationLocationInfo == default || originLocationInfo == default) // 起点终点错误,直接标记错误 + { + // 已经在接口内做了检查,按道理此处不会出错。直接返回异常,任务直接取消 + ConsoleLog.Warning($"【警告】任务号:{wcsTask.TaskId},Plc任务号:{wcsTask.PlcId} 无法执行,起点或者终点存在异常"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"起点或者终点存在异常"); + return false; + } + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = wcsTask.PlcId, + TaskType = Convert.ToInt16(wcsTask.TaskType), + GetStand = 0, + InTunnelId = 3, + OutTunnelId = 3, + SetStand = 0, + GetQueue = Convert.ToInt16(originLocationInfo.Queue), + GetLine = Convert.ToInt16(originLocationInfo.Line), + GetLayer = Convert.ToInt16(originLocationInfo.Layer), + GetDeep = Convert.ToInt16(originLocationInfo.Depth), + SetQueue = Convert.ToInt16(destinationLocationInfo.Queue), + SetLine = Convert.ToInt16(destinationLocationInfo.Line), + SetLayer = Convert.ToInt16(destinationLocationInfo.Layer), + SetDeep = Convert.ToInt16(destinationLocationInfo.Depth), + Size = Convert.ToInt16(wcsTask.VehicleSize), + Weight = Convert.ToInt16(wcsTask.Weight), + Code = wcsTask.PlcVehicleNo ?? 0, + }; + var writeStackerTaskErrText = _stackerOperation.WriteTask(stackerTask); + if (string.IsNullOrEmpty(writeStackerTaskErrText)) + { + ConsoleLog.Success($"堆垛机:{stackerId} 写入拣选任务成功,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination}"); + _wcsTaskEvent.StartTaskEvent(wcsTask); + return true; + } + else + { + ConsoleLog.Warning($"【警告】堆垛机:{stackerId} 写入拣选任务失败,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination},异常信息:{writeStackerTaskErrText}"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"写入任务失败,异常信息:{writeStackerTaskErrText}"); + } + return false; + + } + + /// + /// 执行堆垛机移库任务,若执行了任务则返回 true + /// + /// + /// + public bool ExecuteMoveTask(int? stackerId) + { + if (stackerId == default) return false; + var wcsTasks = _wcsTaskDao.SelectMoveTaskWithStacker((int)stackerId!); + if (wcsTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机移库任务查询失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (wcsTasks.Count == 0) return false; + var wcsTask = wcsTasks[0]; // 取第一条任务 + /* 检验并返回起点终点信息 */ + var originLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Origin); + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(wcsTask.Destination); + if (destinationLocationInfo == default || originLocationInfo == default) // 起点终点错误,直接标记错误 + { + // 已经在接口内做了检查,按道理此处不会出错。直接返回异常,任务直接取消 + ConsoleLog.Warning($"【警告】任务号:{wcsTask.TaskId},Plc任务号:{wcsTask.PlcId} 无法执行,起点或者终点存在异常"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"起点或者终点存在异常"); + return false; + } + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = wcsTask.PlcId, + TaskType = Convert.ToInt16(wcsTask.TaskType), + GetStand = 0, + InTunnelId = 3, + OutTunnelId = 3, + SetStand = 0, + GetQueue = Convert.ToInt16(originLocationInfo.Queue), + GetLine = Convert.ToInt16(originLocationInfo.Line), + GetLayer = Convert.ToInt16(originLocationInfo.Layer), + GetDeep = Convert.ToInt16(originLocationInfo.Depth), + SetQueue = Convert.ToInt16(destinationLocationInfo.Queue), + SetLine = Convert.ToInt16(destinationLocationInfo.Line), + SetLayer = Convert.ToInt16(destinationLocationInfo.Layer), + SetDeep = Convert.ToInt16(destinationLocationInfo.Depth), + Size = Convert.ToInt16(wcsTask.VehicleSize), + Weight = Convert.ToInt16(wcsTask.Weight), + Code = wcsTask.PlcVehicleNo ?? 0, + }; + var writeStackerTaskErrText = _stackerOperation.WriteTask(stackerTask); + if (string.IsNullOrEmpty(writeStackerTaskErrText)) + { + ConsoleLog.Success($"堆垛机:{stackerId} 写入移库任务成功,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination}"); + _wcsTaskEvent.StartTaskEvent(wcsTask); + return true; + } + else + { + ConsoleLog.Warning($"【警告】堆垛机:{stackerId} 写入移库任务失败,箱号:{wcsTask.VehicleNo},PlcId:{wcsTask.PlcId};{wcsTask.Origin} => {wcsTask.Destination},异常信息:{writeStackerTaskErrText}"); + _wcsTaskEvent.ErrTaskEvent(wcsTask, $"写入任务失败,异常信息:{writeStackerTaskErrText}"); + } + return false; + + } + + /// + /// 执行重复入库新任务,若执行了返回true + /// + /// + /// + /// + public bool ExecuteDoubleInTask(int? stackerId, int plcId) + { + if (stackerId == default || plcId == 0) return false; + /* 查找这个PlcId对应的wms任务 */ + List? doubleInTasks = _wcsTaskDao.Select(new AppWcsTask { PlcId = plcId }); + if (doubleInTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机重复入库任务检验失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (doubleInTasks.Count == 0) return false; // 找不到对应的PlcId的任务 + var doubleTask = doubleInTasks[0]; + /* 查找这个任务的新任务 */ + List? newTasks = _wcsTaskDao.Select(new AppWcsTask { TaskId = doubleTask.TaskId, TaskType = (int)TaskTypeEnum.newTaskForDoubleIn, TaskStatus = (int)WmsTaskStatusEnum.create }); + if (newTasks == default) + { + ConsoleLog.Error($"【异常】{stackerId} 号堆垛机重复入库任务新任务查询失败,与数据库连接异常"); + Thread.Sleep(5000); + return false; + } + if (newTasks.Count == 0) return false; + var newTask = newTasks[0]; + var destinationLocationInfo = CommonData.AppLocations.DetailWithWcsLocation(newTask.Destination); + if (destinationLocationInfo == default) // 终点错误,直接标记错误 + { + // 已经在接口内做了检查,按道理此处不会出错。直接返回异常,任务直接取消 + ConsoleLog.Warning($"【警告】任务号:{newTask.TaskId},Plc任务号:{newTask.PlcId} 无法执行,新终点存在异常"); + _wcsTaskEvent.ErrTaskEvent(newTask, $"新终点存在异常"); + return false; + } + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = newTask.PlcId, + TaskType = Convert.ToInt16(TaskTypeEnum.inTask), + GetStand = 0, + InTunnelId = Convert.ToInt16(stackerId), + OutTunnelId = Convert.ToInt16(stackerId), + SetStand = 0, + GetQueue = 0, + GetLine = 0, + GetLayer = 0, + GetDeep = 0, + SetQueue = Convert.ToInt16(destinationLocationInfo.Queue), + SetLine = Convert.ToInt16(destinationLocationInfo.Line), + SetLayer = Convert.ToInt16(destinationLocationInfo.Layer), + SetDeep = Convert.ToInt16(destinationLocationInfo.Depth), + Size = Convert.ToInt16(newTask.VehicleSize), + Weight = Convert.ToInt16(newTask.Weight), + Code = newTask.PlcVehicleNo ?? 0, + }; + var writeStackerTaskErrText = _stackerOperation.WriteTask(stackerTask); + if (string.IsNullOrEmpty(writeStackerTaskErrText)) + { + ConsoleLog.Success($"堆垛机:{stackerId} 写入重复入库新任务成功,箱号:{newTask.VehicleNo},PlcId:{newTask.PlcId};新目的地:{newTask.Destination}"); + _wcsTaskEvent.StartTaskEvent(newTask); + return true; + } + else + { + ConsoleLog.Warning($"【警告】堆垛机:{stackerId} 写入重复入库新任务失败,箱号:{newTask.VehicleNo},PlcId:{newTask.PlcId};新目的地:{newTask.Destination},异常信息:{writeStackerTaskErrText}"); + _wcsTaskEvent.ErrTaskEvent(newTask, $"写入任务失败,异常信息:{writeStackerTaskErrText}"); + } + return false; + } + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/StackerConvey/CheckAccount.cs b/WcsMain/Business/CirculationTask/StackerConvey/CheckAccount.cs new file mode 100644 index 0000000..184b8d3 --- /dev/null +++ b/WcsMain/Business/CirculationTask/StackerConvey/CheckAccount.cs @@ -0,0 +1,108 @@ +using CirculateTool; +using WcsMain.Business.CommonAction; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum; +using WcsMain.EquipOperation.Entity; +using WcsMain.EquipOperation.StackerConvey; + +namespace WcsMain.Business.CirculationTask.StackerConvey; + +/// +/// 过账,PLC任务回告 ---- 库前输送线 +/// +//[Circulation] +public class CheckAccount(AppWcsTaskDao wcsTaskDao, StackerConveyOperation stackerConveyOperation, WCSTaskExecuteEvent wcsTaskEvent) +{ + private readonly WCSTaskExecuteEvent _wcsTaskEvent = wcsTaskEvent; + private readonly StackerConveyOperation _stackerConveyOperation = stackerConveyOperation; + private readonly AppWcsTaskDao _wcsTaskDao = wcsTaskDao; + + /// + /// PLC过账 + /// + /// + [Circulation("监控PLC地址,过账", 1000)] + public bool CheckAccountTask() + { + List? taskFeedBackEntities = _stackerConveyOperation.GetConveyTaskFeedBackData(20); + if (taskFeedBackEntities == default || taskFeedBackEntities.Count < 1) {return true; } + foreach (TaskFeedBackEntity taskFeedBackEntity in taskFeedBackEntities) + { + if (taskFeedBackEntity.PlcId == 0) { continue; } + List? wcsTasks = _wcsTaskDao.Select(new AppWcsTask() { PlcId = taskFeedBackEntity.PlcId }); + if (wcsTasks == default) + { + ConsoleLog.Error($"【异常】库前输送过账查询任务数据失败,和数据库服务器连接中断"); + Thread.Sleep(2000); + continue; // 网络连接中断 + } + if (wcsTasks.Count < 1) + { + ConsoleLog.Warning($"【提示】库前输送过账区获取任务ID:{taskFeedBackEntity.PlcId},无关联任务"); + _stackerConveyOperation.ClearFeedBackData(taskFeedBackEntity); // 清除过账 + continue; // 任务无数据 + } + var wcsTask = wcsTasks[0]; + switch (taskFeedBackEntity.FeedBackType) + { + case (int)TaskFeedBackTypeEnum.cancel: // 任务取消 + CancelTask(taskFeedBackEntity, wcsTask); + break; + case (int)TaskFeedBackTypeEnum.complete: // 任务完成 + CompleteTask(taskFeedBackEntity, wcsTask); + break; + default: + OtherTaskFeedBackType(taskFeedBackEntity); // 其他任务类型, + break; + } + } + return true; + } + + /// + /// 过账任务取消 + /// + /// + private void CancelTask(TaskFeedBackEntity taskFeedBackEntity, AppWcsTask wcsTask) + { + ConsoleLog.Warning($"【提示】库前输送过账区获取任务ID:{taskFeedBackEntity.PlcId},任务已经被取消,后续任务也一并取消,任务号:{wcsTask.TaskId}"); + string? cleanAccountErr = _stackerConveyOperation.ResetFeedBackData(taskFeedBackEntity); // 清除过账 + if (!string.IsNullOrEmpty(cleanAccountErr)) + { + ConsoleLog.Warning($"[警告]取消任务清除过账区发生异常,信息:{cleanAccountErr}"); + } + /* 执行 WMS 任务异常动作 */ + _wcsTaskEvent.ErrTaskEvent(wcsTask, "任务被PLC取消"); + + } + + /// + /// 过账任务完成 + /// + /// + private void CompleteTask(TaskFeedBackEntity taskFeedBackEntity, AppWcsTask wcsTask) + { + ConsoleLog.Tip($"[提示]过账区获取任务ID:{taskFeedBackEntity.PlcId},任务已经完成,任务号:{wcsTask.TaskId}"); + string? cleanAccountErr = _stackerConveyOperation.ResetFeedBackData(taskFeedBackEntity); // 清除过账 + if (!string.IsNullOrEmpty(cleanAccountErr)) + { + ConsoleLog.Warning($"[警告]完成任务清除过账区发生异常,信息:{cleanAccountErr}"); + } + /* 执行 WMS 任务完成动作 */ + _wcsTaskEvent.CompleteTaskEvent(wcsTask, "PLC上报完成"); + } + + + + /// + /// 其他过账类型 + /// + /// + private void OtherTaskFeedBackType(TaskFeedBackEntity taskFeedBackEntity) + { + ConsoleLog.Warning($"[警告]过账区获取任务ID:{taskFeedBackEntity.PlcId},无法识别的过账类型:{taskFeedBackEntity.FeedBackType}"); + _stackerConveyOperation.ResetFeedBackData(taskFeedBackEntity); // 清除过账 + } + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/StackerConvey/ExecuteScanMethod.cs b/WcsMain/Business/CirculationTask/StackerConvey/ExecuteScanMethod.cs new file mode 100644 index 0000000..a6b78ea --- /dev/null +++ b/WcsMain/Business/CirculationTask/StackerConvey/ExecuteScanMethod.cs @@ -0,0 +1,32 @@ +using CirculateTool; + +namespace WcsMain.Business.CirculationTask.StackerConvey; + +//[Circulation("执行输送机扫码方法")] +public class ExecuteScanMethod +{ + + #region 扫码入库 + + + /// + /// 扫码入库 + /// + /// + [Circulation("扫码入库", 1000)] + public bool ScanTaskIn() + { + //var code = StackerConveyOperation.Instance().ReadConveyCode("R1"); + + return true; + } + + + + #endregion + + + + + +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/TaskData/ResolveWmsTask.cs b/WcsMain/Business/CirculationTask/TaskData/ResolveWmsTask.cs new file mode 100644 index 0000000..82e4062 --- /dev/null +++ b/WcsMain/Business/CirculationTask/TaskData/ResolveWmsTask.cs @@ -0,0 +1,278 @@ +using CirculateTool; +using WcsMain.ExtendMethod; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.DataService; +using WcsMain.Enum.TaskEnum; +using WcsMain.Business.CommonAction; +using WcsMain.Common; +using WcsMain.StaticData; +using System.Linq.Expressions; +using WcsMain.DataBase.MixDao; + +namespace WcsMain.Business.CirculationTask.TaskData; + +/// +/// WMS任务解析类 +/// +[Circulation()] +public class ResolveWmsTask(AppWmsTaskDao wmsTaskDao, SendWmsTaskStatus sendWmsTaskStatus, DataBaseData dataBaseData, TaskDao taskDao) +{ + private readonly DataBaseData _dataBaseData = dataBaseData; + private readonly SendWmsTaskStatus _sendWmsTaskStatus = sendWmsTaskStatus; + private readonly AppWmsTaskDao _wmsTaskDao = wmsTaskDao; + private readonly TaskDao _taskDao = taskDao; + + /// + /// 解析WMS任务 + /// + /// + [Circulation("解析WMS任务,转换为WCS任务")] + public bool ResolveTask() + { + List? needResolveTask = _wmsTaskDao.Select(new AppWmsTask() { TaskStatus = (int)WmsTaskStatusEnum.create }); + if (needResolveTask == default) + { + ConsoleLog.Error("【异常】解析Wms任务时拉取任务列表失败,与数据库连接中断"); + Thread.Sleep(5000); + return true; + } + needResolveTask.ForEach(ResolveTask); + return true; + } + + + /// + /// 解析任务 + /// + /// + private void ResolveTask(AppWmsTask wmsTask) + { + /* 校验起点终点是否在点位表 */ + bool checkResult = CommonData.AppLocations.ExistWmsLocation(wmsTask.Origin, wmsTask.Destination); + if (!checkResult) + { + _wmsTaskDao.Update(new() + { + TaskId = wmsTask.TaskId, + TaskStatus = (int)WmsTaskStatusEnum.err, + TaskMsg = "不受支持的起点或终点", + EndTime = DateTime.Now + }); + if (wmsTask.CreatePerson == StaticString.WMS) + { + _sendWmsTaskStatus.SendTaskErr(wmsTask.TaskId, "不受支持的起点或终点"); + } + return; + } + /* 解析任务转换为WCS任务,一般根据项目,此处自定义规则 */ + TransToWcsTask(wmsTask); + } + + + + /// + /// 处理WMS任务,插入WCS任务 + /// + /// + /// + private void TransToWcsTask(AppWmsTask task) + { + switch (task.TaskType) + { + case (int)WmsTaskTypeEnum.inTask:// 入库 + TransInTaskToWcsTask(task); + break; + case (int)WmsTaskTypeEnum.outTask:// 出库 + TransOutTaskToWcsTask(task); + break; + case (int)WmsTaskTypeEnum.moveTask:// 移库 + TransMoveTaskToWcsTask(task); + break; + case (int)WmsTaskTypeEnum.pick:// 拣选 + TransPickTaskToWcsTask(task); + break; + default: // 其他任务,未识别的任务类型,直接报错 + TransOtherTaskToWcsTask(task); + break; + }; + } + + /// + /// 转换入库任务 + /// + /// + /// + private void TransInTaskToWcsTask(AppWmsTask wmsTask) + { + AppLocation? wcsOrigin = CommonData.AppLocations.DetailWithWmsLocation(wmsTask.Origin); + AppLocation? wcsDestination = CommonData.AppLocations.DetailWithWmsLocation(wmsTask.Destination); + if (wcsOrigin == default || wcsDestination == default) { return; } + int? plcId = _dataBaseData.GetNewPlcTaskId(); + if (plcId == default) { return; } + AppWcsTask wcsTask = new() + { + PlcId = plcId, + TaskId = wmsTask.TaskId, + TaskType = (int)WcsTaskTypeEnum.inTask, + TaskStatus = (int)WcsTaskStatusEnum.create, + Origin = wcsOrigin!.WcsLocation, + TaskCategory = 1, + TaskSort = 1, + Destination = wcsDestination.WcsLocation, + VehicleNo = wmsTask.VehicleNo, + PlcVehicleNo = wmsTask.VehicleNo.ToPlcVehicleNo(), + Priority = wmsTask.Priority, + VehicleSize = wmsTask.VehicleSize, + Weight = wmsTask.Weight, + WmsTime = wmsTask.CreateTime, + CreatePerson = wmsTask.CreatePerson, + CreateTime = DateTime.Now, + }; + /* 插入新任务同时更新 wms 任务为排队中 */ + string? errMessage = _taskDao.CreateWcsTask(wcsTask); + if (string.IsNullOrWhiteSpace(errMessage)) + { + ConsoleLog.Info($"【提示】任务:{wmsTask.TaskId} 已经进入队列"); + return; + } + ConsoleLog.Warning($"【警告】任务:{wmsTask.TaskId} 无法处理,异常信息:{errMessage}"); + return; + } + + /// + /// 转换出库任务 + /// + /// + /// + private void TransOutTaskToWcsTask(AppWmsTask wmsTask) + { + AppLocation? wcsOrigin = CommonData.AppLocations.DetailWithWmsLocation(wmsTask.Origin); + AppLocation? wcsDestination = CommonData.AppLocations.DetailWithWmsLocation(wmsTask.Destination); + if (wcsOrigin == default || wcsDestination == default) { return; } + int? plcId = _dataBaseData.GetNewPlcTaskId(); + if (plcId == default) { return; } + AppWcsTask wcsTask = new() + { + PlcId = plcId, + TaskId = wmsTask.TaskId, + TaskType = (int)WcsTaskTypeEnum.outTask, + TaskCategory = 1, + TaskSort = 1, + TaskStatus = (int)WcsTaskStatusEnum.create, + Origin = wcsOrigin.WcsLocation, + Destination = wcsDestination.WcsLocation, + VehicleNo = wmsTask.VehicleNo, + PlcVehicleNo = wmsTask.VehicleNo.ToPlcVehicleNo(), + Priority = wmsTask.Priority, + VehicleSize = wmsTask.VehicleSize, + Weight = wmsTask.Weight, + WmsTime = wmsTask.CreateTime, + CreatePerson = wmsTask.CreatePerson, + CreateTime = DateTime.Now, + }; + /* 插入新任务同时更新 wms 任务为排队中 */ + string? errMessage = _taskDao.CreateWcsTask(wcsTask); + if (string.IsNullOrWhiteSpace(errMessage)) + { + ConsoleLog.Info($"【提示】任务:{wmsTask.TaskId} 已经进入队列"); + return; + } + ConsoleLog.Warning($"【警告】任务:{wmsTask.TaskId} 无法处理,异常信息:{errMessage}"); + return; + } + + /// + /// 转换拣选任务 + /// + /// + /// + private void TransPickTaskToWcsTask(AppWmsTask wmsTask) + { + AppLocation? wcsOrigin = CommonData.AppLocations.DetailWithWmsLocation(wmsTask.Origin); + AppLocation? wcsDestination = CommonData.AppLocations.DetailWithWmsLocation(wmsTask.Destination); + if (wcsOrigin == default || wcsDestination == default) { return; } + int? plcId = _dataBaseData.GetNewPlcTaskId(); + if (plcId == default) { return; } + AppWcsTask wcsTask = new() + { + PlcId = plcId, + TaskId = wmsTask.TaskId, + TaskType = (int)WcsTaskTypeEnum.pick, + TaskStatus = (int)WcsTaskStatusEnum.create, + Origin = wcsOrigin.WcsLocation, + TaskCategory = 1, + TaskSort = 1, + Destination = wcsDestination.WcsLocation, + VehicleNo = wmsTask.VehicleNo, + PlcVehicleNo = wmsTask.VehicleNo.ToPlcVehicleNo(), + Priority = wmsTask.Priority, + VehicleSize = wmsTask.VehicleSize, + Weight = wmsTask.Weight, + WmsTime = wmsTask.CreateTime, + CreatePerson = wmsTask.CreatePerson, + CreateTime = DateTime.Now, + }; + /* 插入新任务同时更新 wms 任务为排队中 */ + string? errMessage = _taskDao.CreateWcsTask(wcsTask); + if (string.IsNullOrWhiteSpace(errMessage)) + { + ConsoleLog.Info($"【提示】任务:{wmsTask.TaskId} 已经进入队列"); + return; + } + ConsoleLog.Warning($"【警告】任务:{wmsTask.TaskId} 无法处理,异常信息:{errMessage}"); + return; + } + + /// + /// 转换移库任务 + /// + /// + /// + private void TransMoveTaskToWcsTask(AppWmsTask wmsTask) + { + AppLocation? wcsOrigin = CommonData.AppLocations.DetailWithWmsLocation(wmsTask.Origin); + AppLocation? wcsDestination = CommonData.AppLocations.DetailWithWmsLocation(wmsTask.Destination); + if (wcsOrigin == default || wcsDestination == default) { return; } + int? plcId = _dataBaseData.GetNewPlcTaskId(); + if (plcId == default) { return; } + AppWcsTask wcsTask = new() + { + PlcId = plcId, + TaskId = wmsTask.TaskId, + TaskType = (int)WcsTaskTypeEnum.moveTask, + TaskSort = 1, + TaskStatus = (int)WcsTaskStatusEnum.create, + Origin = wcsOrigin.WcsLocation, + Destination = wcsDestination.WcsLocation, + VehicleNo = wmsTask.VehicleNo, + TaskCategory = 1, + PlcVehicleNo = wmsTask.VehicleNo.ToPlcVehicleNo(), + Priority = wmsTask.Priority, + VehicleSize = wmsTask.VehicleSize, + Weight = wmsTask.Weight, + WmsTime = wmsTask.CreateTime, + CreatePerson = wmsTask.CreatePerson, + CreateTime = DateTime.Now, + }; + /* 插入新任务同时更新 wms 任务为排队中 */ + string? errMessage = _taskDao.CreateWcsTask(wcsTask); + if (string.IsNullOrWhiteSpace(errMessage)) + { + ConsoleLog.Info($"【提示】任务:{wmsTask.TaskId} 已经进入队列"); + return; + } + ConsoleLog.Warning($"【警告】任务:{wmsTask.TaskId} 无法处理,异常信息:{errMessage}"); + return; + } + + /// + /// 转换其他任务 + /// + /// + /// + private List? TransOtherTaskToWcsTask(AppWmsTask wmsTask) + { + return default; + } +} \ No newline at end of file diff --git a/WcsMain/Business/CirculationTask/TestCirculation.cs b/WcsMain/Business/CirculationTask/TestCirculation.cs new file mode 100644 index 0000000..9f08ce4 --- /dev/null +++ b/WcsMain/Business/CirculationTask/TestCirculation.cs @@ -0,0 +1,17 @@ +namespace WcsMain.Business.CirculationTask; + +/// +/// 测试类 +/// +//[Circulation] +public class TestCirculation +{ + + //[Circulation("测试websocket发送", 1000)] + public bool SendWebSocket() + { + //WebSocketOperation.Instance().Send(DateTime.Now.ToString("F")); + return true; + } + +} \ No newline at end of file diff --git a/WcsMain/Business/CommonAction/ClearData.cs b/WcsMain/Business/CommonAction/ClearData.cs new file mode 100644 index 0000000..3728b9e --- /dev/null +++ b/WcsMain/Business/CommonAction/ClearData.cs @@ -0,0 +1,104 @@ +using WcsMain.ExtendMethod; +using WcsMain.DataBase.Dao; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Business.CommonAction; + +/// +/// 清理数据类 +/// +[Component] +public class ClearData(AppWmsTaskDao wmsTaskDao, AppWcsTaskDao wcsTaskDao, AppApiRequestDao apiRequestDao, AppApiAcceptDao apiAcceptDao) +{ + + private readonly AppWmsTaskDao _wmsTaskDao = wmsTaskDao; + private readonly AppWcsTaskDao _wcsTaskDao = wcsTaskDao; + private readonly AppApiRequestDao _apiRequestDao = apiRequestDao; + private readonly AppApiAcceptDao _apiAcceptDao = apiAcceptDao; + + + + /* 定时清除 WMS 任务表 */ + /// + /// 清理超过一定时间的 WMS 任务表数据 + /// + /// + public int ClearWmsTaskData(int days) + { + return _wmsTaskDao.ClearData(days); + + } + + + /* 定时清除 WCS 任务备份表 */ + /// + /// 清理超过一定天数的 WCS 任务备份表数据 + /// + /// + public int ClearWcsTaskData(int days) + { + return _wcsTaskDao.ClearData(days); + } + + /* 定时清除 日志文件 */ + /// + /// 清理日志文件 + /// + /// + public int ClearLogFile(int days) + { + int clearCount = 0; + string LogAddress = AppDomain.CurrentDomain.BaseDirectory + "\\Log"; + DirectoryInfo di = new(LogAddress); + if (!di.Exists) return clearCount; + var directories = di.GetDirectories(); + if (directories.Length <= 0) return clearCount; + foreach (DirectoryInfo directory in directories) + { + var files = directory.GetFiles(); + if (files.Length <= 0) continue; + foreach (FileInfo fileInfo in files) + { + try + { + string fileName = fileInfo.Name; + DateTime? time = fileName.ToDateTime(); + if (time == default) continue; + TimeSpan? ts = (DateTime.Now - time!); + if (ts?.Days > days) // --------------------------------- 日志保存的天数 + { + fileInfo.Delete(); + clearCount++; + } + } + catch { } // 无论任何情况都放弃执行 + } + } + return clearCount; + } + + /* 定时清除接口请求表 */ + /// + /// 清理接口发送信息表 + /// + /// + public int ClearApiRequestData(int days) + { + return _apiRequestDao.ClearData(days); + } + + + + /* 定时清除接口接收表 */ + /// + /// 清理接口接收信息表 + /// + /// + public int ClearApiAcceptData(int days) + { + return _apiAcceptDao.ClearData(days); + } + + + +} \ No newline at end of file diff --git a/WcsMain/Business/CommonAction/CusBindingVehicle.cs b/WcsMain/Business/CommonAction/CusBindingVehicle.cs new file mode 100644 index 0000000..f68d299 --- /dev/null +++ b/WcsMain/Business/CommonAction/CusBindingVehicle.cs @@ -0,0 +1,34 @@ +namespace WcsMain.Business.CommonAction; + +/// +/// 绑定任务号和箱子 +/// +//[Component] +public class CusBindingVehicle +{ + + ///// + ///// 绑定任务号和载具号 + ///// + ///// + ///// + //public void VehicleBinding(string? vehicleNo, int plcId) + //{ + // if (string.IsNullOrEmpty(vehicleNo)) + // { + // return; + // } + // // 清理旧数据 + // AppVehicleBindingDao.Instance().DeleteWithVehicleNo(vehicleNo); + // // 绑定新数据 + // AppVehicleBindingDao.Instance().Insert(new AppVehicleBinding() + // { + // vehicleNo = vehicleNo, + // PlcId = plcId, + // BindingTime = DateTime.Now + // }); + + //} + + +} \ No newline at end of file diff --git a/WcsMain/Business/CommonAction/LEDUsing.cs b/WcsMain/Business/CommonAction/LEDUsing.cs new file mode 100644 index 0000000..3f13d8c --- /dev/null +++ b/WcsMain/Business/CommonAction/LEDUsing.cs @@ -0,0 +1,152 @@ +using LedSimple; + +namespace WcsMain.Business.CommonAction; + +public class LedUsing(int ledWidth, int ledHeight, int colorType, int nAlignment = 2, int isVCenter = 1) +{ + private readonly int _ledWidth = ledWidth;//屏的宽度 + private readonly int _ledHeight = ledHeight;//屏的高度 + private readonly int _colorType = colorType;//屏的色彩——1.单色 2.双基色 3.七彩 4.全彩 + private readonly int _nAlignment = nAlignment;//水平对齐样式,0.左对齐 1.右对齐 2.水平居中 (注意:只对字符串和txt文件有效) + private readonly int _isVCenter = isVCenter; //是否垂直居中 0.置顶(默认) 1.垂直居中 + + /// + /// 红色宋体字 + /// + private Leddll.FONTPROP _fontRed = new() { FontName = "宋体", FontSize = 9, FontColor = (int)LEDColor.Red, FontBold = 0 }; + + /// + /// 红色宋体字 + /// + private Leddll.FONTPROP _fontRedBig = new() { FontName = "宋体", FontSize = 12, FontColor = (int)LEDColor.Red, FontBold = 0 }; + /// + /// 红色宋体字 + /// + private Leddll.FONTPROP _fontGreen = new() { FontName = "宋体", FontSize = 9, FontColor = (int)LEDColor.Green, FontBold = 0 }; + /// + /// 立即显示的节目效果 + /// + private Leddll.PLAYPROP _playProp = new() { InStyle = 0 }; + + + /// + /// 发送锁 + /// + private static readonly object SendLock = new(); + + + + /// + /// 展示默认的显示内容 ---- 江西隆成立库 256*96 + /// + /// + /// + public void ShowDefaultMsg(string ip, string msg) + { + try + { + Leddll.COMMUNICATIONINFO communicationInfo = GetCommunicationInfo(ip); + Leddll.DIGITALCLOCKAREAINFO date = new() + { + DateFormat = 0, + TimeFormat = 0, + IsShowDay = 1, + IsShowHour = 1, + IsShowMonth = 1, + IsShowSecond = 1, + IsShowYear = 1, + IsShowMinute = 1, + }; + lock (SendLock) + { + Leddll.LV_AdjustTime(ref communicationInfo); // 校时 + + int nResult; + //LedDll.LV_InitLed(3, 0); 当Led上显示的文字区域的颜色与下发的不一致, 请的确认Led 屏的RGB顺序,并调用此接口 + nint hProgram;//节目句柄 + hProgram = Leddll.LV_CreateProgramEx(_ledWidth, _ledHeight, _colorType, 0, 0);//注意此处屏宽高及颜色参数必需与设置屏参的屏宽高及颜色一致,否则发送时会提示错误 + //此处可自行判断有未创建成功,hProgram返回NULL失败,非NULL成功,一般不会失败 + //int nProgramId = 0; + nResult = Leddll.LV_AddProgram(hProgram, 1, 0, 1);//添加一个节目,参数说明见函数声明注示 + + /* 添加内容 */ + // ---- 公司名称 + Leddll.AREARECT areaHeader = new() // 添加区域 + { + left = 0, + top = 10, + width = 256, + height = 20 + }; //区域坐标属性结构体变量 + int addAreaHeaderResult = Leddll.LV_AddImageTextArea(hProgram, 1, 1, ref areaHeader, 0); + if (addAreaHeaderResult == 0) + { + // 添加到显示 + nResult = Leddll.LV_AddMultiLineTextToImageTextArea(hProgram, 1, 1, Leddll.ADDTYPE_STRING, "江西隆成医药", + ref _fontRedBig, ref _playProp, 2, 1); + } + // ---- 控制方式,联机状态 + Leddll.AREARECT areaMain = new() // 添加区域 + { + left = 0, + top = 35, + width = 256, + height = 20 + }; //区域坐标属性结构体变量 + if (msg.Length > 25) + { + nResult = Leddll.LV_QuickAddSingleLineTextArea(hProgram, 1, 2, ref areaMain, 0, msg + " ", + ref _fontRed, 10); + } + else + { + int addAreaMain = Leddll.LV_AddImageTextArea(hProgram, 1, 2, ref areaMain, 1); + if (addAreaMain == 0) + { + // 添加到显示 + nResult = Leddll.LV_AddMultiLineTextToImageTextArea(hProgram, 1, 2, Leddll.ADDTYPE_STRING, msg, + ref _fontRed, ref _playProp, 2, 1); + } + } + // ---- 时间 + Leddll.AREARECT areaTime = new() // 添加区域 + { + left = 0, + top = 60, + width = 256, + height = 20 + }; //区域坐标属性结构体变量 + int addAreaTime = Leddll.LV_AddDigitalClockArea(hProgram, 1, 3, ref areaTime, ref date); + /* 发送 LED */ + nResult = Leddll.LV_Send(ref communicationInfo, hProgram); //发送,见函数声明注示 + Thread.Sleep(100); + Leddll.LV_DeleteProgram(hProgram); //删除节目内存对象,详见函数声明注示 + } + } + catch (Exception ex) + { + _ = ex; + } + } + + private Leddll.COMMUNICATIONINFO GetCommunicationInfo(string ip) + { + Leddll.COMMUNICATIONINFO CommunicationInfo = new() + { + //TCP通讯******************************************************************************** + SendType = 0,//设为固定IP通讯模式,即TCP通讯 + IpStr = ip,//给IpStr赋值LED控制卡的IP + LedNumber = 1,//LED屏号为1,注意socket通讯和232通讯不识别屏号,默认赋1就行了,485必需根据屏的实际屏号进行赋值 + //广播通讯******************************************************************************** + //CommunicationInfo.SendType=1;//设为单机直连,即广播通讯无需设LED控制器的IP地址 + //串口通讯******************************************************************************** + //CommunicationInfo.SendType=2;//串口通讯 + //CommunicationInfo.Commport=1;//串口的编号,如设备管理器里显示为 COM3 则此处赋值 3 + //CommunicationInfo.Baud=9600;//波特率 + //CommunicationInfo.LedNumber=1; + };//定义一通讯参数结构体变量用于对设定的LED通讯,具体对此结构体元素赋值说明见COMMUNICATIONINFO结构体定义部份注示 + return CommunicationInfo; + } + + +} \ No newline at end of file diff --git a/WcsMain/Business/CommonAction/ScanCodeAction.cs b/WcsMain/Business/CommonAction/ScanCodeAction.cs new file mode 100644 index 0000000..845b4b6 --- /dev/null +++ b/WcsMain/Business/CommonAction/ScanCodeAction.cs @@ -0,0 +1,29 @@ +using SocketTool.Entity; + +namespace WcsMain.Business.CommonAction; +/* + * 扫码器方法类: + * 使用方法:系统自动调用,需要在 config 表内配置参数 + * 配置的参数名称为:ScanMethod{ScanID},ScanID 为 string 格式,参数对应的值为方法名称, + * 方法必须为 public 格式,参数为 ScanCodeClass + */ + +/// +/// 扫码器处理事件 +/// +public class ScanCodeAction +{ + + /// + /// 测试 + /// + /// + public void PickScanTest(ScanCodeClass codeEntity) + { + + } + + + + +} \ No newline at end of file diff --git a/WcsMain/Business/CommonAction/SendWmsTaskStatus.cs b/WcsMain/Business/CommonAction/SendWmsTaskStatus.cs new file mode 100644 index 0000000..a2fe8ac --- /dev/null +++ b/WcsMain/Business/CommonAction/SendWmsTaskStatus.cs @@ -0,0 +1,311 @@ +using WcsMain.ApiClient.DataEntity.WmsEntity; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum; +using WcsMain.Plugins; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Business.CommonAction; + +/// +/// 用于发送WMS任务状态的类 +/// +[Component] +public class SendWmsTaskStatus(AppWmsTaskDao wmsTaskDao, WmsWebApiPost wmsWebApiPost) +{ + private readonly WmsWebApiPost _wmsWebApiPost = wmsWebApiPost; + private readonly AppWmsTaskDao _wmsTaskDao = wmsTaskDao; + + /// + /// 发送 WMS 任务异常 + /// + /// + /// + /// + public void SendTaskErr(string? taskId, string? errMsg, ushort count = 5) + { + Task.Factory.StartNew(() => { + AppWmsTask? wmsTask = default; + for (int i = 0; i < count; i++) + { + List? tasks = _wmsTaskDao.Select(new AppWmsTask { TaskId = taskId }); + if (tasks == default) + { + ConsoleLog.Error($"【异常】发送WMS任务状态时查询数据库失败,和数据库连接中断,任务号:{taskId}"); + Thread.Sleep(100); + continue; + } + if (tasks.Count == 0) + { + ConsoleLog.Warning($"【警告】发送WMS任务状态时查询数据库异常,找不到任务,任务号:{taskId}"); + return; + } + wmsTask = tasks[0]; + } + if (wmsTask == default) { return; } + SendWmsTaskStatusRequest request = new() + { + TaskId = wmsTask.TaskId, + TaskStatus = (int)SendWmsTaskStatusEnum.err, + Destination = wmsTask.Destination, + VehicleNo = wmsTask.VehicleNo, + Message = errMsg + }; + for (int i = 1; i <= count; i++) + { + var response = _wmsWebApiPost.HttpPost(request, CommonData.AppConfig.SendWmsTaskStatusApiAddress ?? ""); + if (!response.IsSend) + { + ConsoleLog.Error($"【异常】发送WMS[任务异常]失败,发送次数:{i},任务号:{wmsTask.TaskId},参考信息:{response.RequestException?.Message}"); + continue; + } + var responseObj = response.ResponseEntity; + if (responseObj == default) + { + ConsoleLog.Error($"【异常】发送WMS[任务异常]失败,解析WMS返回数据失败,发送次数:{i},任务号:{wmsTask.TaskId}"); + return; + } + if (responseObj.Code == 0) + { + ConsoleLog.Tip($"发送WMS[任务异常]成功任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},{wmsTask.Origin} => {wmsTask.Destination}"); + return; + } + ConsoleLog.Error($"【异常】发送WMS[任务异常]失败,WMS返回异常,任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},参考信息:{responseObj.Message}"); + } + }); + } + + /// + /// 发送 WMS 取货位置无货 + /// + /// + /// + public void SendTaskEmptyOut(string? taskId, ushort count = 5) + { + Task.Factory.StartNew(() => { + AppWmsTask? wmsTask = default; + for (int i = 0; i < count; i++) + { + List? tasks = _wmsTaskDao.Select(new AppWmsTask { TaskId = taskId }); + if (tasks == default) + { + ConsoleLog.Error($"【异常】发送WMS任务状态时查询数据库失败,和数据库连接中断,任务号:{taskId}"); + Thread.Sleep(100); + continue; + } + if (tasks.Count == 0) + { + ConsoleLog.Warning($"【警告】发送WMS任务状态时查询数据库异常,找不到任务,任务号:{taskId}"); + return; + } + wmsTask = tasks[0]; + } + if (wmsTask == default) { return; } + SendWmsTaskStatusRequest request = new() + { + TaskId = wmsTask.TaskId, + TaskStatus = (int)SendWmsTaskStatusEnum.emptyOut, + Destination = wmsTask.Destination, + VehicleNo = wmsTask.VehicleNo, + Message = "取货位置无货" + }; + for (int i = 1; i <= count; i++) + { + var response = _wmsWebApiPost.HttpPost(request, CommonData.AppConfig.SendWmsTaskStatusApiAddress ?? ""); + if (!response.IsSend) + { + ConsoleLog.Error($"【异常】发送WMS[取货位置无货]失败,发送次数:{i},任务号:{wmsTask.TaskId},参考信息:{response.RequestException?.Message}"); + continue; + } + var responseObj = response.ResponseEntity; + if (responseObj == default) + { + ConsoleLog.Error($"【异常】发送WMS[取货位置无货]失败,解析WMS返回数据失败,发送次数:{i},任务号:{wmsTask.TaskId}"); + return; + } + if (responseObj.Code == 0) + { + ConsoleLog.Tip($"发送WMS[取货位置无货]成功任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},{wmsTask.Origin} => {wmsTask.Destination}"); + return; + } + ConsoleLog.Error($"【异常】发送WMS[取货位置无货]失败,WMS返回异常,任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},参考信息:{responseObj.Message}"); + } + }); + } + + /// + /// 发送 WMS 卸货位置有货 + /// + /// + /// + public void SendTaskDoubleIn(string? taskId, ushort count = 5) + { + Task.Factory.StartNew(() => { + AppWmsTask? wmsTask = default; + for (int i = 0; i < count; i++) + { + List? tasks = _wmsTaskDao.Select(new AppWmsTask { TaskId = taskId }); + if (tasks == default) + { + ConsoleLog.Error($"【异常】发送WMS任务状态时查询数据库失败,和数据库连接中断,任务号:{taskId}"); + Thread.Sleep(100); + continue; + } + if (tasks.Count == 0) + { + ConsoleLog.Warning($"【警告】发送WMS任务状态时查询数据库异常,找不到任务,任务号:{taskId}"); + return; + } + wmsTask = tasks[0]; + } + if (wmsTask == default) { return; } + SendWmsTaskStatusRequest request = new() + { + TaskId = wmsTask.TaskId, + TaskStatus = (int)SendWmsTaskStatusEnum.doubleIn, + Destination = wmsTask.Destination, + VehicleNo = wmsTask.VehicleNo, + Message = "卸货位置有货" + }; + for (int i = 1; i <= count; i++) + { + var response = _wmsWebApiPost.HttpPost(request, CommonData.AppConfig.SendWmsTaskStatusApiAddress ?? ""); + if (!response.IsSend) + { + ConsoleLog.Error($"【异常】发送WMS[卸货位置有货]失败,发送次数:{i},任务号:{wmsTask.TaskId},参考信息:{response.RequestException?.Message}"); + continue; + } + var responseObj = response.ResponseEntity; + if (responseObj == default) + { + ConsoleLog.Error($"【异常】发送WMS[卸货位置有货]失败,解析WMS返回数据失败,发送次数:{i},任务号:{wmsTask.TaskId}"); + return; + } + if (responseObj.Code == 0) + { + ConsoleLog.Tip($"发送WMS[卸货位置有货]成功任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},{wmsTask.Origin} => {wmsTask.Destination}"); + return; + } + ConsoleLog.Error($"【异常】发送WMS[卸货位置有货]失败,WMS返回异常,任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},参考信息:{responseObj.Message}"); + } + }); + } + + + /// + /// 向WMS发送任务开始执行 + /// + /// + /// + public void SendTaskStart(string? taskId, ushort count = 5) + { + Task.Factory.StartNew(() => { + AppWmsTask? wmsTask = default; + for (int i = 0; i < count; i++) + { + List? tasks = _wmsTaskDao.Select(new AppWmsTask { TaskId = taskId }); + if (tasks == default) + { + ConsoleLog.Error($"【异常】发送WMS任务状态时查询数据库失败,和数据库连接中断,任务号:{taskId}"); + Thread.Sleep(100); + continue; + } + if (tasks.Count == 0) + { + ConsoleLog.Warning($"【警告】发送WMS任务状态时查询数据库异常,找不到任务,任务号:{taskId}"); + return; + } + wmsTask = tasks[0]; + } + if (wmsTask == default) { return; } + SendWmsTaskStatusRequest request = new() + { + TaskId = wmsTask.TaskId, + TaskStatus = (int)SendWmsTaskStatusEnum.start, + Destination = wmsTask.Destination, + VehicleNo = wmsTask.VehicleNo, + Message = "开始执行" + }; + for (int i = 1; i <= count; i++) + { + var response = _wmsWebApiPost.HttpPost(request, CommonData.AppConfig.SendWmsTaskStatusApiAddress ?? ""); + if (!response.IsSend) + { + ConsoleLog.Error($"【异常】发送WMS[开始执行]失败,发送次数:{i},任务号:{wmsTask.TaskId},参考信息:{response.RequestException?.Message}"); + continue; + } + var responseObj = response.ResponseEntity; + if (responseObj == default) + { + ConsoleLog.Error($"【异常】发送WMS[开始执行]失败,解析WMS返回数据失败,发送次数:{i},任务号:{wmsTask.TaskId}"); + return; + } + if (responseObj.Code == 0) + { + ConsoleLog.Tip($"发送WMS[开始执行]成功任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},{wmsTask.Origin} => {wmsTask.Destination}"); + return; + } + ConsoleLog.Error($"【异常】发送WMS[开始执行]失败,WMS返回异常,任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},参考信息:{responseObj.Message}"); + } + }); + } + + /// + /// 向WMS发送任务完成 + /// + /// + /// + public void SendTaskComplete(string? taskId, ushort count = 5) + { + Task.Factory.StartNew(() => { + AppWmsTask? wmsTask = default; + for (int i = 0; i < count; i++) + { + List? tasks = _wmsTaskDao.Select(new AppWmsTask { TaskId = taskId }); + if (tasks == default) + { + ConsoleLog.Error($"【异常】发送WMS任务状态时查询数据库失败,和数据库连接中断,任务号:{taskId}"); + Thread.Sleep(100); + continue; + } + if (tasks.Count == 0) + { + ConsoleLog.Warning($"【警告】发送WMS任务状态时查询数据库异常,找不到任务,任务号:{taskId}"); + return; + } + wmsTask = tasks[0]; + } + if (wmsTask == default) { return; } + SendWmsTaskStatusRequest request = new() + { + TaskId = wmsTask.TaskId, + TaskStatus = (int)SendWmsTaskStatusEnum.complete, + Destination = wmsTask.Destination, + VehicleNo = wmsTask.VehicleNo, + Message = "任务完成" + }; + for (int i = 1; i <= count; i++) + { + var response = _wmsWebApiPost.HttpPost(request, CommonData.AppConfig.SendWmsTaskStatusApiAddress ?? ""); + if (!response.IsSend) + { + ConsoleLog.Error($"【异常】发送WMS[任务完成]失败,发送次数:{i},任务号:{wmsTask.TaskId},参考信息:{response.RequestException?.Message}"); + continue; + } + var responseObj = response.ResponseEntity; + if (responseObj == default) + { + ConsoleLog.Error($"【异常】发送WMS[任务完成]失败,解析WMS返回数据失败,发送次数:{i},任务号:{wmsTask.TaskId}"); + return; + } + if (responseObj.Code == 0) + { + ConsoleLog.Tip($"发送WMS[任务完成]成功任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},{wmsTask.Origin} => {wmsTask.Destination}"); + return; + } + ConsoleLog.Error($"【异常】发送WMS[任务完成]失败,WMS返回异常,任务号:{wmsTask.TaskId},载具号:{wmsTask.VehicleNo},参考信息:{responseObj.Message}"); + } + }); + } +} \ No newline at end of file diff --git a/WcsMain/Business/CommonAction/WCSTaskExecuteEvent.cs b/WcsMain/Business/CommonAction/WCSTaskExecuteEvent.cs new file mode 100644 index 0000000..c7d5f8d --- /dev/null +++ b/WcsMain/Business/CommonAction/WCSTaskExecuteEvent.cs @@ -0,0 +1,147 @@ +using WcsMain.ExtendMethod; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.DataBase.MixDao; +using WcsMain.Enum.TaskEnum; +using WcsMain.WcsAttribute.AutoFacAttribute; +using WcsMain.StaticData; + +namespace WcsMain.Business.CommonAction; + +/// +/// WCS 任务 触发的事件 +/// +[Component] +public class WCSTaskExecuteEvent(TaskDao taskDao, SendWmsTaskStatus sendWmsTaskStatus, AppLocationDao locationDao) +{ + private readonly AppLocationDao _locationDao = locationDao; + private readonly SendWmsTaskStatus _sendWmsTaskStatus = sendWmsTaskStatus; + private readonly TaskDao _taskDao = taskDao; + + /// + /// 任务执行异常触发事件 + /// + /// + /// + public void ErrTaskEvent(AppWcsTask task, string errText) + { + string errMsg = _taskDao.TaskErrAndCancelOtherTask(task, errText); + if (!string.IsNullOrEmpty(errMsg)) + { + ConsoleLog.Error($"【异常】任务异常更新数据库失败,任务信息:{task},参考信息:{errMsg}"); + } + if (task.CreatePerson == StaticString.WMS) + { + _sendWmsTaskStatus.SendTaskErr(task.TaskId, errText); + } + } + + + /// + /// 任务执行异常触发事件 --- 取货位置无货 + /// + /// + /// + public void EmptyOutTaskEvent(AppWcsTask task, string errText) + { + string errMsg = _taskDao.TaskErr(task, errText); + if (!string.IsNullOrEmpty(errMsg)) + { + ConsoleLog.Error($"【异常】任务异常更新数据库失败,任务信息:{task},参考信息:{errMsg}"); + } + if (task.CreatePerson == StaticString.WMS) + { + _sendWmsTaskStatus.SendTaskEmptyOut(task.TaskId); + } + } + + /// + /// 任务执行异常触发事件 --- 卸货位置有货 + /// + /// + /// + public void DoubleInTaskEvent(AppWcsTask task, string errText) + { + string errMsg = _taskDao.TaskErr(task, errText); + if (!string.IsNullOrEmpty(errMsg)) + { + ConsoleLog.Error($"【异常】卸货位置有货更新数据库失败,任务信息:{task},参考信息:{errMsg}"); + } + if (task.CreatePerson == StaticString.WMS) + { + _sendWmsTaskStatus.SendTaskDoubleIn(task.TaskId); + } + } + + + + + /// + /// WCS任务开始时触发 + /// + /// + public void StartTaskEvent(AppWcsTask task) + { + string errMsg = _taskDao.StartTask(task); + if (!string.IsNullOrEmpty(errMsg)) + { + ConsoleLog.Error($"【异常】任务开始更新数据库失败,任务信息:{task},参考信息:{errMsg}"); + } + if (task.IsFirstTask() && task.CreatePerson == StaticString.WMS) + { + _sendWmsTaskStatus.SendTaskStart(task.TaskId); + } + } + + + + #region WCS 任务完成时触发 + + /// + /// WCS任务完成时触发 + /// + /// + /// + public void CompleteTaskEvent(AppWcsTask task, string msg) + { + UpdateLocationInfo(task); // 更新库位的相关信息 + HandleTaskData(task, msg); // 处理任务数据 + } + + private void HandleTaskData(AppWcsTask task, string msg) + { + string errMsg = _taskDao.ComlpeteTask(task, msg); + if(!string.IsNullOrEmpty(errMsg)) + { + ConsoleLog.Error($"【异常】PlcId:{task.PlcId} 任务完成失败,异常信息:{errMsg}"); + } + } + + + /// + /// 更新库位的相关信息, + /// + /// + /// + /// 特别注意:随缘更新,具体占用情况必须以WMS为准 + /// + private void UpdateLocationInfo(AppWcsTask task) + { + switch (task.TaskType) + { + case (int)TaskTypeEnum.inTask: // 入库任务 + _locationDao.Update(AppLocation.CreateUsedInstance(task.Destination, task.VehicleNo)); + break; + case (int)TaskTypeEnum.outTask: // 出库任务 + _locationDao.Update(AppLocation.CreateEmptyInstance(task.Origin)); + break; + case (int)TaskTypeEnum.moveTask: // 移库任务 + _locationDao.Update(AppLocation.CreateUsedInstance(task.Destination, task.VehicleNo), AppLocation.CreateEmptyInstance(task.Origin)); + break; + } + } + #endregion + + + +} \ No newline at end of file diff --git a/WcsMain/Business/CommonAction/WMSApiResponseAction.cs b/WcsMain/Business/CommonAction/WMSApiResponseAction.cs new file mode 100644 index 0000000..bb823f6 --- /dev/null +++ b/WcsMain/Business/CommonAction/WMSApiResponseAction.cs @@ -0,0 +1,52 @@ +using ApiTool.Dto; +using LogTool; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.DataService; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Business.CommonAction; + +/// +/// WMS 接口响应触发事件 +/// +[Component] +public class WMSApiResponseAction(DataBaseData dataBaseData, AppApiRequestDao apiRequestDao) +{ + + private readonly DataBaseData _dataBaseData = dataBaseData; + private readonly AppApiRequestDao _apiRequestDao = apiRequestDao; + + /// + /// 当请求其他系统时触发本方法 + /// + /// + public void WMSApiResponse(ApiResponseInfo responseInfo) + { + try + { + // 写日志 + WcsLog.Instance().WriteApiRequestLog(responseInfo.ToString()); + // 存库 + AppApiRequest insertData = new() + { + RequestId = _dataBaseData.GetNewUUID(), + RequestUrl = responseInfo.RequestUrl, + RequestMethod = responseInfo.RequestMethod, + IsSuccess = responseInfo.IsSend ? 1 : 0, + RequestMsg = responseInfo.RequestMsg, + ResponseMsg = responseInfo.ResponseMsg, + RequestTime = responseInfo.RequestTime, + ResponseTime = responseInfo.ResponseTime, + UseTime = responseInfo.UseTime, + ErrMsg = responseInfo.RequestException?.Message, + }; + _apiRequestDao.Insert(insertData); + } + catch (Exception ex) + { + ConsoleLog.Error($"【异常】写接口日志发生异常,信息:{ex}"); + } + } + +} \ No newline at end of file diff --git a/WcsMain/Business/CommonAction/WmsTaskAction.cs b/WcsMain/Business/CommonAction/WmsTaskAction.cs new file mode 100644 index 0000000..72b7e39 --- /dev/null +++ b/WcsMain/Business/CommonAction/WmsTaskAction.cs @@ -0,0 +1,73 @@ +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.StaticData; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Business.CommonAction; + +[Component] +public class WmsTaskAction(AppWmsTaskDao wmsTaskDao, SendWmsTaskStatus sendWmsTaskStatus) +{ + private readonly SendWmsTaskStatus _sendWmsTaskStatus = sendWmsTaskStatus; + private readonly AppWmsTaskDao _wmsTaskDao = wmsTaskDao; + + /// + /// 重置WMS任务 + /// + /// + /// + /// + /// + public string ResetWmsTaskStatus(string? taskId, string? newDestination, string systemName) + { + string resetErrMessage = _wmsTaskDao.ResetTaskWithTaskId(taskId, newDestination, $"任务被{systemName}重置"); + if (string.IsNullOrEmpty(resetErrMessage)) return string.Empty; // 重置成功 + // 重置失败,返回错误信息 + return $"重置状态失败:任务号:{taskId},参考异常信息:{resetErrMessage}"; + } + + /// + /// 完成WMS任务 + /// + /// + /// + /// + public string CompleteWmsTaskStatus(string? taskId, string sysName) + { + List? wmsTasks = _wmsTaskDao.Select(new AppWmsTask() { TaskId = taskId }); + if (wmsTasks != default && wmsTasks.Count > 0) + { + AppWmsTask wmsTask = wmsTasks[0]; + if (sysName != StaticString.WMS && wmsTask.CreatePerson == StaticString.WMS) + { + _sendWmsTaskStatus.SendTaskComplete(taskId); + } + } + string resetErrMessage = _wmsTaskDao.CompleteTaskWithTaskId(taskId, $"【{sysName}】完成"); + if (string.IsNullOrEmpty(resetErrMessage)) return string.Empty; // 完成成功 + return $"完成任务失败。任务号:{taskId},参考异常信息:{resetErrMessage}"; + } + + /// + /// 删除WMS任务 + /// + /// + /// + /// + public string DeleteWmsTaskStatus(string? taskId, string sysName) + { + List? wmsTasks = _wmsTaskDao.Select(new AppWmsTask() { TaskId = taskId }); + if (wmsTasks != default && wmsTasks.Count > 0) + { + AppWmsTask wmsTask = wmsTasks[0]; + if (sysName != StaticString.WMS && wmsTask.CreatePerson == StaticString.WMS) + { + _sendWmsTaskStatus.SendTaskErr(taskId, $"{sysName} 删除/取消"); + } + } + string resetErrMessage = _wmsTaskDao.DeleteTaskWithTaskId(taskId, $"【{sysName}】删除/取消"); + if (string.IsNullOrEmpty(resetErrMessage)) return string.Empty; // 删除成功 + return $"删除/取消任务失败。任务号:{taskId},参考异常信息:{resetErrMessage}"; + } + +} diff --git a/WcsMain/Business/Convey/ConnectPlcServe.cs b/WcsMain/Business/Convey/ConnectPlcServe.cs new file mode 100644 index 0000000..dcb270a --- /dev/null +++ b/WcsMain/Business/Convey/ConnectPlcServe.cs @@ -0,0 +1,109 @@ +using WcsMain.Business.Convey.DataHandler; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.General; +using WcsMain.Enum.Tcp; +using WcsMain.Tcp.Client; +using WcsMain.Tcp.Entity; +using WcsMain.Tcp.Entity.Message; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Business.Convey; + +/// +/// 连接并接收 PLC 的信息 +/// +[Component] +public class ConnectPlcServe(AppTcpDao tcpDao, BaseConveyDataHandler conveyDataHandler) +{ + private readonly AppTcpDao _tcpDao = tcpDao; + private readonly BaseConveyDataHandler _conveyDataHandler = conveyDataHandler; + + + public void SetBaseAction() + { + CommonTool.PlcTcpClient = new PlcTcpClient(); + CommonTool.PlcTcpClient.SetConnecting((tcpData) => ConsoleLog.Info($"{tcpData} 正在连接")); + CommonTool.PlcTcpClient.SetConnectFailAction((tcpData, ex) => ConsoleLog.Warning($"{tcpData} 连接失败,参考信息:{ex.Message}")); + CommonTool.PlcTcpClient.SetConnectSuccess((tcpData) => ConsoleLog.Success($"{tcpData} 连接成功")); + CommonTool.PlcTcpClient.SetConnectOffline((tcpData) => ConsoleLog.Warning($"{tcpData} 失去连接")); + CommonTool.PlcTcpClient.GetMessage(GetMessage); + } + + /// + /// 连接PLC服务 + /// + public void ConnectPlc() + { + Task.Factory.StartNew(() => + { + List? tcps = default; + while (true) + { + tcps = _tcpDao.Query(new AppTcp { TcpStatus = (int)TrueFalseEnum.TRUE, TcpType = (int)TcpType.PLC }); + if (tcps != default) break; + Thread.Sleep(5000); + continue; + } + if (tcps.Count == 0) return; + CommonTool.PlcTcpClient.SetBaseTcpServe(tcps); + CommonTool.PlcTcpClient.Connect(); + }, TaskCreationOptions.LongRunning); + } + + /// + /// 收到信息时的处理 + /// + /// + /// + private void GetMessage(TcpServeConnectionData tcpServe, string msg) + { + ConsoleLog.Tcp($"{tcpServe} 收到信息:{msg}"); + MsgHeader? header = CommonTool.PlcTcpClient.GetMsgHeader(msg); + if(header == default) { return; } + switch(header.MsgType) + { + case "SEND": + GetRequest(tcpServe, header, msg); + return; + case "RESP": + GetResponse(); + return; + default: + return; + } + } + + /***************************** 请求和响应事件 *****************************/ + + + private void GetResponse() + { + + } + + + private void GetRequest(TcpServeConnectionData tcpServe, MsgHeader header, string msg) + { + switch(header.MsgTag) + { + case "GTROUTER": // plc向WCS请求路向 ---- 箱式线 + _conveyDataHandler.GetRouter(tcpServe.DisplayName, msg); + return; + + + } + + + } + + + /****************************** 请求事件 ******************************/ + + + + + + +} diff --git a/WcsMain/Business/Convey/DataHandler/BaseConveyDataHandler.cs b/WcsMain/Business/Convey/DataHandler/BaseConveyDataHandler.cs new file mode 100644 index 0000000..e4af11a --- /dev/null +++ b/WcsMain/Business/Convey/DataHandler/BaseConveyDataHandler.cs @@ -0,0 +1,136 @@ +using System.Reflection; +using WcsMain.Business.Convey.DataHandler.GetRouter; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.Tcp.Entity.Convey; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Business.Convey.DataHandler; + +/// +/// 输送线收到的数据处理基础类 +/// +[Component] +public class BaseConveyDataHandler(AppRouterMethodDao routerMethodDao) +{ + private readonly AppRouterMethodDao _routerMethodDao = routerMethodDao; + + private Dictionary? methodConfig; + + /// + /// 处理 PLC 请求分拣路向逻辑 + /// + /// + /// + public void GetRouter(string? displayName, string msg) + { + LoadingMethodConfig(); // 加载配置信息,仅第一次加载 + if(methodConfig == default) + { + ConsoleLog.Warning("路由处理参数尚未加载,可能是和数据库服务连接存在异常"); + return; + } + GetRouterData? routerData = FormatGetRouterData(msg); + if(routerData == default ) + { + ConsoleLog.Warning($"数据不能正常识别,数据:{msg}"); + return; + } + var getInstance = methodConfig.TryGetValue(routerData.Area ?? "", out var instance); + if(!getInstance || instance == default) + { + ConsoleLog.Warning($"点位:{routerData.Area} 未配置处理逻辑或者处理逻辑不可用"); + return; + } + if(routerData.Code == "NoRead") + { + instance.NoRead(routerData, displayName, msg); + } + else + { + instance.ReadSuccess(routerData, displayName, msg); + } + } + + /*************************/ + + /// + /// 加载获取路由的参数 + /// + private void LoadingMethodConfig() + { + if (methodConfig != default) return; + List? appRouters = _routerMethodDao.Query(); + if (appRouters == default) return; + methodConfig = []; + // 扫描创建继承 BaseGetRouter 的类的实例 + Dictionary classInstances = []; + var assembly = Assembly.GetExecutingAssembly(); + var assTypes = assembly.GetTypes(); + foreach (var assType in assTypes) + { + var interfaces = assType.GetInterfaces(); + foreach (var inteface in interfaces) + { + if (inteface.GetType() != typeof(BaseGetRouter)) continue; + var instance = Activator.CreateInstance(inteface.GetType()); + if (instance == default) continue; + classInstances.Add(assType.Name, (BaseGetRouter)instance); + } + } + // 将实例和请求点位绑定 + foreach (var appRouter in appRouters) + { + if(string.IsNullOrEmpty(appRouter.ClassName)) continue; + bool isGetInstance = classInstances.TryGetValue(appRouter.ClassName, out var inst); + if (!isGetInstance) continue; + methodConfig.TryAdd(appRouter.Area ?? "", inst); + } + } + + + + /****************************** 格式化数据 ***************************/ + + /// + /// PLC请求WCS路向的请求体格式化 + /// + /// + /// + private GetRouterData? FormatGetRouterData(string msg) + { + if (string.IsNullOrEmpty(msg)) return default; + msg = msg.Trim().TrimStart('<').TrimEnd('>'); + string[] datas = msg.Split(';'); + if (datas.Length != 14) return default; + try + { + return new GetRouterData + { + MsgType = datas[0], + ClientId = datas[1], + MsgId = datas[2], + MsgTag = datas[3], + Area = datas[4], + SortStatus = datas[5], + Code = datas[6], + Size = datas[7], + Weight = Convert.ToDecimal(string.IsNullOrEmpty(datas[8]) ? 0 : datas[8]), + Length = Convert.ToDecimal(string.IsNullOrEmpty(datas[9]) ? 0 : datas[9]), + Width = Convert.ToDecimal(string.IsNullOrEmpty(datas[10]) ? 0 : datas[10]), + Hight = Convert.ToDecimal(string.IsNullOrEmpty(datas[11]) ? 0 : datas[11]), + Spare1 = datas[12], + Spare2 = datas[13], + }; + } + catch (Exception ex) // 防止转换decimal失败 + { + _ = ex; + return default; + } + + + } + + +} diff --git a/WcsMain/Business/Convey/DataHandler/GetRouter/BaseGetRouter.cs b/WcsMain/Business/Convey/DataHandler/GetRouter/BaseGetRouter.cs new file mode 100644 index 0000000..0604022 --- /dev/null +++ b/WcsMain/Business/Convey/DataHandler/GetRouter/BaseGetRouter.cs @@ -0,0 +1,30 @@ +using WcsMain.Tcp.Entity.Convey; + +namespace WcsMain.Business.Convey.DataHandler.GetRouter; + +public class BaseGetRouter +{ + /// + /// 读码失败 + /// + /// + /// + /// + /// + public virtual void NoRead(GetRouterData routerData, string? disPlayName, string msg) + { + throw new NotImplementedException(); + } + + /// + /// 不是读码失败 + /// + /// + /// + /// + /// + public virtual void ReadSuccess(GetRouterData routerData, string? disPlayName, string msg) + { + throw new NotImplementedException(); + } +} diff --git a/WcsMain/Business/Convey/DataHandler/GetRouter/DeliverGetRouter.cs b/WcsMain/Business/Convey/DataHandler/GetRouter/DeliverGetRouter.cs new file mode 100644 index 0000000..6c63f84 --- /dev/null +++ b/WcsMain/Business/Convey/DataHandler/GetRouter/DeliverGetRouter.cs @@ -0,0 +1,8 @@ +namespace WcsMain.Business.Convey.DataHandler.GetRouter; + +/// +/// 发货获取路向 +/// +public class DeliverGetRouter : BaseGetRouter +{ +} diff --git a/WcsMain/Business/Convey/DataHandler/GetRouter/LoginGetRouter.cs b/WcsMain/Business/Convey/DataHandler/GetRouter/LoginGetRouter.cs new file mode 100644 index 0000000..c6e24a5 --- /dev/null +++ b/WcsMain/Business/Convey/DataHandler/GetRouter/LoginGetRouter.cs @@ -0,0 +1,9 @@ +namespace WcsMain.Business.Convey.DataHandler.GetRouter; + +/// +/// 注册口获取路向 +/// +public class LoginGetRouter : BaseGetRouter +{ + +} diff --git a/WcsMain/Business/Convey/DataHandler/GetRouter/PickGetRouter.cs b/WcsMain/Business/Convey/DataHandler/GetRouter/PickGetRouter.cs new file mode 100644 index 0000000..f3bc597 --- /dev/null +++ b/WcsMain/Business/Convey/DataHandler/GetRouter/PickGetRouter.cs @@ -0,0 +1,8 @@ +namespace WcsMain.Business.Convey.DataHandler.GetRouter; + +/// +/// 拣选请求获取路向 +/// +public class PickGetRouter : BaseGetRouter +{ +} diff --git a/WcsMain/Business/Convey/DataHandler/GetRouter/RecheckGetRouter.cs b/WcsMain/Business/Convey/DataHandler/GetRouter/RecheckGetRouter.cs new file mode 100644 index 0000000..ab00e82 --- /dev/null +++ b/WcsMain/Business/Convey/DataHandler/GetRouter/RecheckGetRouter.cs @@ -0,0 +1,8 @@ +namespace WcsMain.Business.Convey.DataHandler.GetRouter; + +/// +/// 复核获取路向 +/// +public class RecheckGetRouter : BaseGetRouter +{ +} diff --git a/WcsMain/Business/Convey/DataHandler/GetRouter/ReplenishGetRouter.cs b/WcsMain/Business/Convey/DataHandler/GetRouter/ReplenishGetRouter.cs new file mode 100644 index 0000000..0588de3 --- /dev/null +++ b/WcsMain/Business/Convey/DataHandler/GetRouter/ReplenishGetRouter.cs @@ -0,0 +1,8 @@ +namespace WcsMain.Business.Convey.DataHandler.GetRouter; + +/// +/// 补货获取路向 +/// +public class ReplenishGetRouter : BaseGetRouter +{ +} diff --git a/WcsMain/Common/CommonData.cs b/WcsMain/Common/CommonData.cs new file mode 100644 index 0000000..20f80a5 --- /dev/null +++ b/WcsMain/Common/CommonData.cs @@ -0,0 +1,49 @@ +using Autofac; +using System.Diagnostics.CodeAnalysis; +using WcsMain.AppEntity.SystemData; +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.Common; + +/// +/// 共享数据 +/// +public static class CommonData +{ + + /// + /// 系统设置项,加载于 appsettings.json + /// + [NotNull] + public static AppSettingJsonEntity? Settings { get; set; } + + /// + /// 数据库的 Config 配置 + /// + [NotNull] + public static AppConfigEntity? AppConfig { get; set; } + + /// + /// 堆垛机数据 + /// + [NotNull] + public static List? AppStackers { get; set; } + + /// + /// 点位信息 + /// + [NotNull] + public static List? AppLocations { get; set; } + + /// + /// 电子标签基础资料 + /// + [NotNull] + public static List? AppElTags { get; set; } + + + /// + /// 指示是否连接PLC + /// + public static bool IsConnectPlc { get; set; } = false; +} \ No newline at end of file diff --git a/WcsMain/Common/CommonTool.cs b/WcsMain/Common/CommonTool.cs new file mode 100644 index 0000000..0898011 --- /dev/null +++ b/WcsMain/Common/CommonTool.cs @@ -0,0 +1,76 @@ +using Fleck; +using PlcTool.Siemens; +using SqlSugar; +using System.Diagnostics.CodeAnalysis; +using WcsMain.Business.CommonAction; +using WcsMain.ElTag.Atop; +using WcsMain.Tcp.Client; + +namespace WcsMain.Common; + +/// +/// 公用工具类 +/// +public class CommonTool +{ + + #region 数据库连接 + + + /// + /// 数据库连接 + /// + [NotNull] + public static ISqlSugarClient? DbServe { get; set; } + + #endregion + + #region PLC 连接 + + /// + /// PLC连接 + /// + public static SiemensS7? Siemens { get; set; } + + + #endregion + + + #region PLC 的 TCP 连接 + + /// + /// TCP 连接的PLC通讯 + /// + [NotNull] + public static PlcTcpClient? PlcTcpClient { get; set; } + + + #endregion + + + #region 电子标签的 TCP 连接 + + /// + /// 电子标签的 TCP 连接 + /// + [NotNull] + public static OprTcpClient? OprTcpClient { get; set; } + + #endregion + + + + #region WMS 接口 + + // 请查看 Plugins 文件夹 WmsWebApiPost + + #endregion + + + + public static WebSocketServer? Websocket; + + public static readonly LedUsing LedUsing = new(256, 96, 1); + + +} \ No newline at end of file diff --git a/WcsMain/ConsoleLog.cs b/WcsMain/ConsoleLog.cs new file mode 100644 index 0000000..320dd48 --- /dev/null +++ b/WcsMain/ConsoleLog.cs @@ -0,0 +1,252 @@ +using System.Runtime.InteropServices; +using LogTool; +using System.Text; + +namespace WcsMain; + +/// +/// 输出信息 +/// +public class ConsoleLog +{ + + private static readonly object _locker = new(); + + /// + /// 输出提示信息 + /// + /// + public static void Info(params string[] messages) => Info(true, messages); + + /// + /// 输出提示信息 + /// + /// + /// + public static void Info(bool isShow, params string[] messages) + { + if (!isShow) return; + if (messages == default || messages.Length == 0) return; + DateTime now = DateTime.Now; + StringBuilder stringBuilder = new(); + stringBuilder.AppendLine(now.ToString("yyyy-MM-dd HH:mm:ss:fff")); + foreach (string message in messages) + { + stringBuilder.AppendLine($" -- {message}"); + } + lock (_locker) + { + Console.ForegroundColor = ConsoleColor.DarkCyan; + Console.Write(stringBuilder.ToString()); + WcsLog.Instance().WriteEventLog(stringBuilder); + } + } + + + /// + /// 输出错误信息 + /// + /// + public static void Error(params string[] messages) => Error(true, messages); + + /// + /// 输出错误信息 + /// + /// + /// + public static void Error(bool isShow, params string[] messages) + { + if (!isShow) return; + if (messages == default || messages.Length == 0) return; + DateTime now = DateTime.Now; + StringBuilder stringBuilder = new(); + stringBuilder.AppendLine(now.ToString("yyyy-MM-dd HH:mm:ss:fff")); + foreach (string message in messages) + { + stringBuilder.AppendLine($" -- {message}"); + } + lock (_locker) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Write(stringBuilder.ToString()); + WcsLog.Instance().WriteEventLog(stringBuilder); + } + } + + + /// + /// 输出异常信息 + /// + /// + public static void Exception(params string[] messages) => Exception(true, messages); + + /// + /// 输出异常信息 + /// + /// + /// + public static void Exception(bool isShow, params string[] messages) + { + if (!isShow) return; + if (messages == default || messages.Length == 0) return; + DateTime now = DateTime.Now; + StringBuilder stringBuilder = new(); + stringBuilder.AppendLine(now.ToString("yyyy-MM-dd HH:mm:ss:fff")); + foreach (string message in messages) + { + stringBuilder.AppendLine($" -- {message}"); + } + lock (_locker) + { + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.Write(stringBuilder.ToString()); + WcsLog.Instance().WriteExceptionLog(stringBuilder); + } + } + + + /// + /// 输出成功信息 + /// + /// + public static void Success(params string[] messages) => Success(true, messages); + + /// + /// 输出成功信息 + /// + /// + /// + public static void Success(bool isShow, params string[] messages) + { + if (!isShow) return; + if (messages == default || messages.Length == 0) return; + DateTime now = DateTime.Now; + StringBuilder stringBuilder = new(); + stringBuilder.AppendLine(now.ToString("yyyy-MM-dd HH:mm:ss:fff")); + foreach (string message in messages) + { + stringBuilder.AppendLine($" -- {message}"); + } + lock (_locker) + { + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.Write(stringBuilder.ToString()); + WcsLog.Instance().WriteEventLog(stringBuilder); + } + } + + + /// + /// 输出警告信息 + /// + /// + public static void Warning(params string[] messages) => Warning(true, messages); + + /// + /// 输出警告信息 + /// + /// + /// + public static void Warning(bool isShow, params string[] messages) + { + if (!isShow) return; + if (messages == default || messages.Length == 0) return; + DateTime now = DateTime.Now; + StringBuilder stringBuilder = new(); + stringBuilder.AppendLine(now.ToString("yyyy-MM-dd HH:mm:ss:fff")); + foreach (string message in messages) + { + stringBuilder.AppendLine($" -- {message}"); + } + lock (_locker) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.Write(stringBuilder.ToString()); + WcsLog.Instance().WriteEventLog(stringBuilder); + } + } + + + /// + /// 输出蓝色提示信息 + /// + /// + public static void Tip(params string[] messages) => Tip(true, messages); + + /// + /// 输出蓝色提示信息 + /// + /// + /// + public static void Tip(bool isShow, params string[] messages) + { + if (!isShow) return; + if (messages == default || messages.Length == 0) return; + DateTime now = DateTime.Now; + StringBuilder stringBuilder = new(); + stringBuilder.AppendLine(now.ToString("yyyy-MM-dd HH:mm:ss:fff")); + foreach (string message in messages) + { + stringBuilder.AppendLine($" -- {message}"); + } + lock (_locker) + { + Console.ForegroundColor = ConsoleColor.Blue; + Console.Write(stringBuilder.ToString()); + WcsLog.Instance().WriteEventLog(stringBuilder); + } + } + + /// + /// Tcp 通讯专用 + /// + /// + public static void Tcp(params string[] messages) => Tcp(true, messages); + + /// + /// Tcp 通讯专用 + /// + /// + /// + public static void Tcp(bool isShow, params string[] messages) + { + if (!isShow) return; + if (messages == default || messages.Length == 0) return; + DateTime now = DateTime.Now; + StringBuilder stringBuilder = new(); + stringBuilder.AppendLine(now.ToString("yyyy-MM-dd HH:mm:ss:fff")); + foreach (string message in messages) + { + stringBuilder.AppendLine($" -- {message}"); + } + lock (_locker) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Write(stringBuilder.ToString()); + WcsLog.Instance().WriteTcpLog(stringBuilder); + } + } + + + const int STD_INPUT_HANDLE = -10; + const uint ENABLE_QUICK_EDIT_MODE = 0x0040; + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern nint GetStdHandle(int hConsoleHandle); + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool GetConsoleMode(nint hConsoleHandle, out uint mode); + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool SetConsoleMode(nint hConsoleHandle, uint mode); + + /// + /// 禁用快速编辑 ---- 此项会屏蔽控制台输入 + /// + public static void DisbleQuickEditMode() + { + nint hStdin = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(hStdin, out uint mode); + mode &= ~ENABLE_QUICK_EDIT_MODE; + SetConsoleMode(hStdin, mode); + } + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppApiAcceptDao.cs b/WcsMain/DataBase/Dao/AppApiAcceptDao.cs new file mode 100644 index 0000000..2c38a82 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppApiAcceptDao.cs @@ -0,0 +1,105 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ApiAccept; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppApiAcceptDao +{ + + /// + /// 插入一条数据 + /// + /// + /// + public int Insert(AppApiAccept data) + { + try + { + return CommonTool.DbServe.Insertable(data).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 查询所有数据 + /// + /// + public List? Select() + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .OrderByDescending(o => o.RequestTime).ToList(); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + + + /*----------------------业务方法--------------------------------- */ + + /// + /// 分页查询数据 + /// + /// + /// + public (List? apiRequests, int totalRows) SelectPage(GetApiAcceptWithPageRequest request) + { + try + { + int totalRows = 0; + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(request.SearchStr), + w => w.RequestMsg!.Contains(request.SearchStr!) + || w.ResponseMsg!.Contains(request.SearchStr!) + || w.Path!.Contains(request.SearchStr!) + || w.ClientAddress!.Contains(request.SearchStr!)); + if (request.TimeRange is { Count: 2 }) // 时间范围 + { + sqlFuc.Where(w => w.RequestTime > request.TimeRange[0] && w.RequestTime < request.TimeRange[1]); + } + sqlFuc = sqlFuc.OrderByDescending(o => new { o.RequestTime }); + var queryResult = sqlFuc.ToPageList(request.Page!.PageIndex, request.Page!.PageSize, ref totalRows); + return (queryResult, totalRows); + } + catch (Exception ex) + { + _ = ex; + return default; + } + + } + + /// + /// 保留多少天数据 + /// + /// + /// + public int ClearData(int days) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable().Where(w => w.RequestTime < DateTime.Now.AddDays(-days)); + return sqlFuc.ExecuteCommand(); + + } + catch (Exception ex) + { + _ = ex; + return default; + } + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppApiRequestDao.cs b/WcsMain/DataBase/Dao/AppApiRequestDao.cs new file mode 100644 index 0000000..f7495e1 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppApiRequestDao.cs @@ -0,0 +1,111 @@ +using System.ComponentModel; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ApiRequest; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// tbl_app_api_request 的操作类 +/// +[Component] +public class AppApiRequestDao +{ + + /// + /// 插入一条数据 + /// + /// + /// + public int Insert(AppApiRequest data) + { + try + { + return CommonTool.DbServe.Insertable(data).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 查询所有数据 + /// + /// + public List? Select() + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .OrderByDescending(o => o.RequestTime).ToList(); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + + + /*----------------------业务方法--------------------------------- */ + + /// + /// 分页查询数据 + /// + /// + /// + public (List? apiRequests, int totalRows) SelectPage(GetApiRequestWithPageRequest request) + { + try + { + int totalRows = 0; + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(request.SearchStr), + w => w.RequestMsg!.Contains(request.SearchStr!) + || w.ResponseMsg!.Contains(request.SearchStr!) + || w.RequestUrl!.Contains(request.SearchStr!) + || w.RequestMethod!.Contains(request.SearchStr!)); + if (request.TimeRange is { Count: 2 }) // 时间范围 + { + sqlFuc.Where(w => w.RequestTime > request.TimeRange[0] && w.RequestTime < request.TimeRange[1]); + } + sqlFuc = sqlFuc.OrderByDescending(o => new { o.RequestTime }); + var queryResult = sqlFuc.ToPageList(request.Page!.PageIndex, request.Page!.PageSize, ref totalRows); + return (queryResult, totalRows); + } + catch (Exception ex) + { + _ = ex; + return default; + } + + } + + + /// + /// 保留多少条数据 + /// + /// + /// + public int ClearData(int days) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable().Where(w => w.RequestTime < DateTime.Now.AddDays(-days)); + return sqlFuc.ExecuteCommand(); + + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppConfigDao.cs b/WcsMain/DataBase/Dao/AppConfigDao.cs new file mode 100644 index 0000000..2f4331f --- /dev/null +++ b/WcsMain/DataBase/Dao/AppConfigDao.cs @@ -0,0 +1,114 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Config; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppConfigDao +{ + /// + /// 返回所有的配置信息 + /// + /// + public List? Query() + { + try + { + List configs = CommonTool.DbServe.Queryable().OrderBy(o => o.ConfigType).ToList(); + return configs; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 根据主键更新信息 + /// + /// + /// + public int Update(AppConfig config) + { + try + { + return CommonTool.DbServe.Updateable(config).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 插入一条信息 + /// + /// + /// + public int Insert(params AppConfig[] configs) + { + try + { + return CommonTool.DbServe.Insertable(configs).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + + /// + /// 返回所有的配置信息 + /// + /// + /// + public List? GetAllConfig(string? configKey) + { + try + { + List configs = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(configKey), w => w.ConfigKey!.Contains(configKey!)) + .OrderBy(o => o.ConfigType).ToList(); + return configs; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 分页查询系统配置信息 + /// + /// + /// + public (List? configs, int totalNumber) GetAllConfigWithPage(GetConfigWithPageRequest request) + { + try + { + int totalNumber = 0; + List configs = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(request.SearchString), w => w.ConfigKey!.Contains(request.SearchString!) + || w.ConfigValue!.Contains(request.SearchString!) + || w.ConfigName!.Contains(request.SearchString!) + || w.ConfigType!.Contains(request.SearchString!) + || w.Remark!.Contains(request.SearchString!)) + .OrderBy(o => o.ConfigType).ToPageList((int)request.PageIndex!, (int)request.PageSize!, ref totalNumber); + return (configs, totalNumber); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppDBDao.cs b/WcsMain/DataBase/Dao/AppDBDao.cs new file mode 100644 index 0000000..d813b28 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppDBDao.cs @@ -0,0 +1,146 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.DB; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + + +namespace WcsMain.DataBase.Dao; + +/// +/// tbl_app_db 表的增删改查 +/// +[Component] +public class AppDBDao +{ + /// + /// 插入数据 + /// + /// + /// + public int Insert(AppDB appDB) + { + try + { + int insertResult = CommonTool.DbServe.Insertable(appDB).ExecuteCommand(); + return insertResult; + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 更新数据,以主键为条件,主键 : DBName + /// + /// + /// + public int Update(AppDB appDB) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(appDB).IgnoreColumns(ignoreAllNullColumns: true); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 删除数据,以主键为条件 + /// + /// + /// + public int Delete(AppDB appDB) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable(appDB); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 查找数据 + /// + /// + /// + public List? Select(AppDB appDB) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable(); + if (appDB.DBName != null) + { + sqlFuc = sqlFuc.Where(w => w.DBName == appDB.DBName); + } + if (appDB.DBAddress != null) + { + sqlFuc = sqlFuc.Where(w => w.DBAddress == appDB.DBAddress); + } + if (appDB.IsSystem != null) + { + sqlFuc = sqlFuc.Where(w => w.IsSystem == appDB.IsSystem); + } + if (appDB.Remark != null) + { + sqlFuc = sqlFuc.Where(w => w.Remark == appDB.Remark); + } + return sqlFuc.OrderBy(o => new { o.PlcId, o.DBName }).ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查找所有数据 + /// + /// + public List? Select() => Select(new AppDB()); + + + /// + /// 查询所有DB地址,同时返回PLC名称 + /// + /// + /// + public List? QueryWithPlcName(AppDB appDB) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .LeftJoin((db, plc) => db.PlcId == plc.PLCId) + .WhereIF(!string.IsNullOrEmpty(appDB.DBName), db => db.DBName!.Contains(appDB.DBName!)) + .WhereIF(appDB.PlcId != default, db => db.PlcId == appDB.PlcId) + .OrderBy(db => new { db.PlcId, db.DBName }) + .Select((db, plc) => new GetDBWithPlcNameResponse + { + PlcId = db.PlcId, + PlcName = plc.PLCName, + DBName = db.DBName, + DBAddress = db.DBAddress, + IsSystem = db.IsSystem, + Remark = db.Remark, + }); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppElTagBaseDao.cs b/WcsMain/DataBase/Dao/AppElTagBaseDao.cs new file mode 100644 index 0000000..2d000b3 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppElTagBaseDao.cs @@ -0,0 +1,94 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppElTagBaseDao +{ + + /// + /// 条件查询 + /// + /// + /// + public List? Query(AppElTagBase queryData) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(queryData.Location != default, w => w.Location == queryData.Location) + .WhereIF(queryData.TagName != default, w => w.TagName == queryData.TagName) + .WhereIF(queryData.TagId != default, w => w.TagId == queryData.TagId) + .WhereIF(queryData.TagType != default, w => w.TagType == queryData.TagType) + .WhereIF(queryData.ControllerDisplayName != default, w => w.ControllerDisplayName == queryData.ControllerDisplayName) + .WhereIF(queryData.Area != default, w => w.Area == queryData.Area) + .WhereIF(queryData.LedStatus != default, w => w.LedStatus == queryData.LedStatus) + .WhereIF(queryData.TaskId != default, w => w.TaskId == queryData.TaskId) + .WhereIF(queryData.Remark != default, w => w.Remark == queryData.Remark); + return sqlFuc.ToList(); + } + catch(Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查询所有 + /// + /// + public List? Query() => Query(new AppElTagBase()); + + /// + /// 根据主键更新,不更新null值 + /// + /// + /// + public int Update(AppElTagBase appElTag) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(appElTag).IgnoreNullColumns(); + return sqlFuc.ExecuteCommand(); + } + catch(Exception ex) { _ = ex; return 0; } + } + + + + + + + + + + /*******************************业务************************************/ + + + + + /// + /// 重置绑定的任务信息 + /// + /// + /// + public int ResetLocation(string location) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable() + .SetColumns(s => s.LastLightTime == null) + .SetColumns(s => s.TaskId == null) + .Where(w => w.Location == location); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) { _ = ex; return 0; } + } + + + + +} diff --git a/WcsMain/DataBase/Dao/AppElTagTaskDao.cs b/WcsMain/DataBase/Dao/AppElTagTaskDao.cs new file mode 100644 index 0000000..53451ee --- /dev/null +++ b/WcsMain/DataBase/Dao/AppElTagTaskDao.cs @@ -0,0 +1,160 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.ElTag; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.TaskEnum; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppElTagTaskDao +{ + + /// + /// 条件查询 + /// + /// + /// + public List? Query(AppElTagTask queryData) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(queryData.TaskId != default, w => w.TaskId == queryData.TaskId) + .WhereIF(queryData.TaskGroup != default, w => w.TaskGroup == queryData.TaskGroup) + .WhereIF(queryData.Location != default, w => w.Location == queryData.Location) + .WhereIF(queryData.OrderId != default, w => w.OrderId == queryData.OrderId) + .WhereIF(queryData.VehicleNo != default, w => w.VehicleNo == queryData.VehicleNo) + .WhereIF(queryData.GoodsId != default, w => w.GoodsId == queryData.GoodsId) + .WhereIF(queryData.GoodsName != default, w => w.GoodsName == queryData.GoodsName) + .WhereIF(queryData.TaskStatus != default, w => w.TaskStatus == queryData.TaskStatus) + .WhereIF(queryData.NeedNum != default, w => w.NeedNum == queryData.NeedNum) + .WhereIF(queryData.PickNum != default, w => w.PickNum == queryData.PickNum) + .WhereIF(queryData.CreatePerson != default, w => w.CreatePerson == queryData.CreatePerson) + .WhereIF(queryData.Remark != default, w => w.Remark == queryData.Remark); + return sqlFuc.ToList(); + } + catch(Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 全部查询 + /// + /// + public List? Query() => Query(new AppElTagTask()); + + + /// + /// 根据主键更新 + /// + /// + /// + public int Update(params AppElTagTask[] updateDatas) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(updateDatas).IgnoreColumns(ignoreAllNullColumns: true); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 添加数据 + /// + /// + /// + public int Insert(params AppElTagTask[] updateDatas) + { + try + { + var sqlFuc = CommonTool.DbServe.Insertable(updateDatas); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /****************************************业务************************************/ + + + /// + /// 分页查询 电子标签任务表 + /// + /// + /// + public (List? elTagTasks, int totalRows) QueryWithPage(QueryTaskRequest request) + { + try + { + int totalRows = 0; + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(request.SearchStr), + w => w.TaskId!.Contains(request.SearchStr!) + || w.TaskGroup!.Contains(request.SearchStr!) + || w.Location!.Contains(request.SearchStr!) + || w.VehicleNo!.Contains(request.SearchStr!) + || w.GoodsId!.Contains(request.SearchStr!) + || w.Remark!.Contains(request.SearchStr!)); + if (request.TaskType != default) + { + List taskTaskType = []; + request.TaskType.ForEach(item => taskTaskType.Add(item)); + sqlFuc.Where(w => taskTaskType.Contains(w.TaskType)); + } + if (request.TaskStatus != default) + { + List taskStatus = []; + request.TaskStatus.ForEach(item => taskStatus.Add(item)); + sqlFuc.Where(w => taskStatus.Contains(w.TaskStatus)); + } + if (request.TimeRange is { Count: 2 }) // 时间范围 + { + sqlFuc.Where(w => w.CreateTime > request.TimeRange[0] && w.CreateTime < request.TimeRange[1]); + } + sqlFuc.OrderByDescending(o => new { o.CreateTime }); + var queryResult = sqlFuc.ToPageList(request.Page!.PageIndex, request.Page!.PageSize, ref totalRows); + return (queryResult, totalRows); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + + /// + /// 查询正在运行的电子标签任务 ---- 点亮中和已确认 + /// + /// + public List? QueryRunningTask() + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .Where(w => w.TaskStatus == (int)ElTagTaskStatusEnum.Lighting || w.TaskStatus == (int)ElTagTaskStatusEnum.Confirm); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + +} diff --git a/WcsMain/DataBase/Dao/AppLocationDao.cs b/WcsMain/DataBase/Dao/AppLocationDao.cs new file mode 100644 index 0000000..dacba30 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppLocationDao.cs @@ -0,0 +1,207 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Location; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum; +using WcsMain.Enum.Location; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// tbl_app_location 的库操作 +/// +[Component] +public class AppLocationDao +{ + + /// + /// 增加一条记录 + /// + /// + /// + public int Insert(AppLocation appLocation) + { + try + { + return CommonTool.DbServe.Insertable(appLocation).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /// + /// 更新一条记录,根据主键,主键WcsLocation + /// + /// + /// + public int Update(params AppLocation[] appLocation) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(appLocation).IgnoreColumns(ignoreAllNullColumns: true); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /// + /// 删除一条记录,根据主键,主键WcsLocation + /// + /// + /// + public int Delete(AppLocation appLocation) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable(appLocation); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /// + /// 查找数据 + /// + /// + /// + /// + /// 时间不在筛选行列 + /// + public List? Select(AppLocation appLocation) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(appLocation.WcsLocation), + w => w.WcsLocation == appLocation.WcsLocation) + .WhereIF(!string.IsNullOrEmpty(appLocation.WmsLocation), + w => w.WmsLocation == appLocation.WmsLocation) + .WhereIF(appLocation.LocationType != null, w => w.LocationType == appLocation.LocationType) + .WhereIF(appLocation.TunnelNo != null, w => w.TunnelNo == appLocation.TunnelNo) + .WhereIF(appLocation.EquipmentId != null, w => w.EquipmentId == appLocation.EquipmentId) + .WhereIF(appLocation.LocationStatus != null, w => w.LocationStatus == appLocation.LocationStatus) + .WhereIF(appLocation.Queue != null, w => w.Queue == appLocation.Queue) + .WhereIF(appLocation.Line != null, w => w.Line == appLocation.Line) + .WhereIF(appLocation.Layer != null, w => w.Layer == appLocation.Layer) + .WhereIF(appLocation.Depth != null, w => w.Depth == appLocation.Depth) + .WhereIF(appLocation.InterveneLocation != default, w => w.InterveneLocation == appLocation.InterveneLocation) + .WhereIF(appLocation.VehicleType == default, w => w.VehicleType == appLocation.VehicleType) + .WhereIF(!string.IsNullOrEmpty(appLocation.VehicleNo), w => w.VehicleNo == appLocation.VehicleNo) + .OrderBy(o => new { o.Queue, o.Line, o.Layer, o.Depth }) + .ToList(); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查找所有数据 + /// + /// + public List? Select() => Select(new AppLocation()); + + + /// + /// 查询自起点索引后的多少条数据 + /// + /// + /// + public (List? locations, int totalRows) SelectPage(GetLocationWithPageRequest request) + { + try + { + int totalRows = 0; + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(request.SearchStr), + w => w.WcsLocation!.Contains(request.SearchStr!) + || w.WmsLocation!.Contains(request.SearchStr!) + || w.VehicleNo!.Contains(request.SearchStr!) + || w.Remark!.Contains(request.SearchStr!)); + if (request.LocationStatus != default) // 查询点位状态 + { + List locationStatus = []; + request.LocationStatus.ForEach(item => locationStatus.Add(item)); + sqlFuc.Where(w => locationStatus.Contains(w.LocationStatus)); + } + if (request.LocationType != default) // 查询点位类型 + { + List locationTypes = []; + request.LocationType.ForEach(item => locationTypes.Add(item)); + sqlFuc.Where(w => locationTypes.Contains(w.LocationType)); + } + sqlFuc.OrderBy(o => new { o.EquipmentId, o.Queue, o.Line, o.Layer, o.Depth }); + var queryResult = sqlFuc.ToPageList(request.Page!.PageIndex, request.Page!.PageSize, ref totalRows); + return (queryResult, totalRows); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查询一个堆垛机可用的站台 + /// + /// + /// + /// + public List? QueryCanUseStand(int? stackerId, StandTypeEnum standType) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(stackerId != null, w => w.EquipmentId == stackerId) + .Where(w => w.LocationType.ToString()!.Contains(standType.ToString())) + .Where(w => w.LocationStatus == (int)LocationStatusEnum.empty) + .OrderBy(o => new { o.Queue, o.Line, o.Layer, o.Depth }); + //var ss = sqlFuc.ToSql(); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查找所有的站台 + /// + /// + public List? SelectStand() + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .Where(w => w.LocationType != (int)StandTypeEnum.storageLocation) + .OrderBy(o => o.WcsLocation) + .ToList(); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppMenuDao.cs b/WcsMain/DataBase/Dao/AppMenuDao.cs new file mode 100644 index 0000000..26be8a5 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppMenuDao.cs @@ -0,0 +1,140 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.Menu; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// 菜单表的操作 +/// +[Component] +public class AppMenuDao +{ + + /// + /// 查询所有数据 + /// + /// + public List? Query() => Query(new AppMenu()); + + /// + /// 查询数据 + /// + /// + /// + public List? Query(AppMenu queryEntity) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(queryEntity.MainMenuIndex != default, w => w.MainMenuIndex == queryEntity.MainMenuIndex) + .WhereIF(queryEntity.MainMenuName != default, w => w.MainMenuName == queryEntity.MainMenuName) + .WhereIF(queryEntity.MainMenuIco != default, w => w.MainMenuIco == queryEntity.MainMenuIco) + .WhereIF(queryEntity.MinorMenuIndex != default, w => w.MinorMenuIndex == queryEntity.MinorMenuIndex) + .WhereIF(queryEntity.MinorMenuName != default, w => w.MinorMenuName == queryEntity.MinorMenuName) + .WhereIF(queryEntity.MinorMenuIco != default, w => w.MinorMenuIco == queryEntity.MinorMenuIco) + .WhereIF(queryEntity.MinorMenuRouter != default, w => w.MinorMenuRouter == queryEntity.MinorMenuRouter) + .WhereIF(queryEntity.MenuStatus != default, w => w.MenuStatus == queryEntity.MenuStatus) + .WhereIF(queryEntity.Remark != default, w => w.Remark == queryEntity.Remark) + .OrderBy(o => o.MainMenuIndex).OrderBy(o => o.MinorMenuIndex); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 根据主键更新数据 + /// + /// + /// + public int Update(AppMenu updateEntity) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(updateEntity) + .IgnoreColumns(ignoreAllNullColumns: true); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 插入一些数据 + /// + /// + /// + public int Insert(params AppMenu[] menus) + { + try + { + var sqlFuc = CommonTool.DbServe.Insertable(menus); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + + + + /*--------------------------业务方法---------------------------------------*/ + + + /// + /// 根据用户组查找菜单 + /// + /// + /// + public List? GetMenuWithGroupId(string groupId) + { + try + { + string sql = $"select * from tbl_app_menu where minor_menu_index in (select minor_menu_index from tbl_app_user_rule where group_id = '{groupId}') and menu_status = 1 order by minor_menu_index asc"; + + var sqlFuc = CommonTool.DbServe.Ado.SqlQuery(sql); + return [.. sqlFuc]; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 返回分页查询数据 + /// + /// + /// + public (List quertResult, int totalRows) SelectPage(GetMenuWithPageRequest request) + { + try + { + int totalRows = 0; + var sqlFuc = CommonTool.DbServe.Queryable() + .OrderBy(o => new { o.MainMenuIndex, o.MinorMenuIndex }); + var queryResult = sqlFuc.ToPageList(request.Page!.PageIndex, request.Page!.PageSize, ref totalRows); + return (queryResult, totalRows); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppPLCDao.cs b/WcsMain/DataBase/Dao/AppPLCDao.cs new file mode 100644 index 0000000..2fa5884 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppPLCDao.cs @@ -0,0 +1,141 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppPLCDao +{ + + /// + /// 查询PLC数据 + /// + /// + public List? Query() + { + try + { + List appPLCs = CommonTool.DbServe.Queryable().OrderBy(o => o.PLCId).ToList(); + return appPLCs; + + } + catch (Exception ex) + { + _ = ex; + } + return default; + } + + /// + /// 根据条件查询 + /// + /// + /// + public List? Query(AppPLC plc) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(plc.PLCId != default, w=>w.PLCId == plc.PLCId) + .WhereIF(plc.PLCIp != default, w => w.PLCIp == plc.PLCIp) + .WhereIF(plc.PLCName != default, w => w.PLCName == plc.PLCName) + .WhereIF(plc.PLCStatus != default, w => w.PLCStatus == plc.PLCStatus) + .WhereIF(plc.PLCKind != default, w => w.PLCKind == plc.PLCKind) + .WhereIF(plc.PLCRack != default, w => w.PLCRack == plc.PLCRack) + .WhereIF(plc.PLCSlot != default, w => w.PLCSlot == plc.PLCSlot) + .OrderBy(o => o.PLCId); + var ss = sqlFuc.ToSql(); + return sqlFuc.ToList(); + + } + catch (Exception ex) + { + _ = ex; + } + return default; + } + + + /// + /// 根据主键返回更新条数 + /// + /// + /// + public int Update(AppPLC plcInfo) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(plcInfo).IgnoreColumns(ignoreAllNullColumns: true); + return sqlFuc.ExecuteCommand(); + + } + catch (Exception ex) + { + _ = ex; + } + return default; + } + + + /// + /// 插入一条新记录 + /// + /// + /// + public int Insert(AppPLC plcInfo) + { + try + { + var sqlFuc = CommonTool.DbServe.Insertable(plcInfo); + return sqlFuc.ExecuteCommand(); + + } + catch (Exception ex) + { + _ = ex; + } + return default; + } + + /// + /// 删除一行数据,根据主键 + /// + /// + /// + public int Delete(AppPLC plcInfo) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable(plcInfo); + return sqlFuc.ExecuteCommand(); + + } + catch (Exception ex) + { + _ = ex; + } + return default; + } + + + /// + /// 根据状态查询PLC数据 + /// + /// + /// + public List? GetDataWithStatus(int status) + { + try + { + List appPLCs = CommonTool.DbServe.Queryable().Where(w => w.PLCStatus == status).ToList(); + return appPLCs; + + } + catch (Exception ex) + { + _ = ex; + } + return default; + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppRouterMethodDao.cs b/WcsMain/DataBase/Dao/AppRouterMethodDao.cs new file mode 100644 index 0000000..cd23609 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppRouterMethodDao.cs @@ -0,0 +1,39 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppRouterMethodDao +{ + + /// + /// 查询数据 + /// + /// + /// + public List? Query(AppRouterMethod routerMethod) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(routerMethod.Area == default, w => w.Area == routerMethod.Area) + .WhereIF(routerMethod.ClassName == default, w => w.ClassName == routerMethod.ClassName) + .WhereIF(routerMethod.Remark == default, w => w.Remark == routerMethod.Remark); + return sqlFuc.ToList(); + } + catch(Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查询所有 + /// + /// + public List? Query() => Query(new AppRouterMethod()); + +} diff --git a/WcsMain/DataBase/Dao/AppSettingsDao.cs b/WcsMain/DataBase/Dao/AppSettingsDao.cs new file mode 100644 index 0000000..2b80896 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppSettingsDao.cs @@ -0,0 +1,135 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// tbl_app_settings 表的增删改查 +/// +[Component] +public class AppSettingsDao +{ + + /// + /// 添加一条记录 + /// + /// + /// + public int Insert(AppSettings appSetting) + { + try + { + int insertResult = CommonTool.DbServe.Insertable(appSetting).ExecuteCommand(); + return insertResult; + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /// + /// 更新一条记录,主键:SettingKey + /// + /// + /// + public int Update(AppSettings appSetting) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(appSetting).IgnoreColumns(ignoreAllNullColumns: true); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 删除一条记录,以主键为条件 + /// + /// + /// + public int Delete(AppSettings appSetting) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable(appSetting); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 以不为 null 为条件查询 + /// + /// + /// + public List? Select(AppSettings appSetting) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable(); + if (appSetting.SettingKey != null) + { + sqlFuc = sqlFuc.Where(w => w.SettingKey == appSetting.SettingKey); + } + if (appSetting.SettingName != null) + { + sqlFuc = sqlFuc.Where(w => w.SettingName == appSetting.SettingName); + } + if (appSetting.SettingValue != null) + { + sqlFuc = sqlFuc.Where(w => w.SettingValue == appSetting.SettingValue); + } + if (appSetting.SettingType != null) + { + sqlFuc = sqlFuc.Where(w => w.SettingType == appSetting.SettingType); + } + if (appSetting.Remark != null) + { + sqlFuc = sqlFuc.Where(w => w.Remark == appSetting.Remark); + } + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查找所有数据 + /// + /// + public List? Select() => Select(new AppSettings()); + + + /// + /// 返回设置信息 + /// + /// + public List? GetSetting(string settingKey) + { + try + { + List settings = CommonTool.DbServe.Queryable().Where(w => w.SettingKey == settingKey).ToList(); + return settings; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppStackerDao.cs b/WcsMain/DataBase/Dao/AppStackerDao.cs new file mode 100644 index 0000000..e546104 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppStackerDao.cs @@ -0,0 +1,102 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// tbl_app_stocker 操作类 +/// +[Component] +public class AppStackerDao +{ + + /// + /// 插入数据 + /// + /// + /// + public int? Insert(params AppStacker[] data) + { + try + { + return CommonTool.DbServe.Insertable(data).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 删除一条数据,根据主键 + /// + /// + /// + public int? Delete(AppStacker appStacker) + { + try + { + return CommonTool.DbServe.Deleteable(appStacker).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 修改一条数据 + /// + /// + /// + public int? Update(AppStacker appStacker) + { + try + { + return CommonTool.DbServe.Updateable(appStacker).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 根据条件查询 ---- 不包括站台 + /// + /// + /// + public List? Select(AppStacker appStacker) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(appStacker.StackerId != default, w => w.StackerId == appStacker.StackerId) + .WhereIF(appStacker.StackerName != default, w => w.StackerName == appStacker.StackerName) + .WhereIF(appStacker.StackerStatus != default, w => w.StackerStatus == appStacker.StackerStatus) + .WhereIF(appStacker.ForkStatus != default, w => w.ForkStatus == appStacker.ForkStatus) + .WhereIF(appStacker.ActionPlc != default, w => w.ActionPlc == appStacker.ActionPlc) + .WhereIF(appStacker.InStand != default, w => w.InStand == appStacker.InStand) + .WhereIF(appStacker.OutStand != default, w => w.OutStand == appStacker.OutStand) + .WhereIF(appStacker.Remark != default, w => w.Remark == appStacker.Remark); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + + } + + /// + /// 查询所有的数据 + /// + /// + public List? Select() => Select(new AppStacker()); + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppTcpDao.cs b/WcsMain/DataBase/Dao/AppTcpDao.cs new file mode 100644 index 0000000..9122ec2 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppTcpDao.cs @@ -0,0 +1,121 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppTcpDao +{ + + /// + /// 查询所有socket数据 + /// + /// + public List? Query() => Query(new AppTcp()); + + /// + /// 条件查询 + /// + /// + /// + public List? Query(AppTcp socket) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(socket.TcpId != default, w => w.TcpId == socket.TcpId) + .WhereIF(socket.TcpIp != default, w => w.TcpIp == socket.TcpIp) + .WhereIF(socket.TcpPort != default, w => w.TcpPort == socket.TcpPort) + .WhereIF(socket.TcpType != default, w => w.TcpType == socket.TcpType) + .WhereIF(socket.TcpStatus != default, w => w.TcpStatus == socket.TcpStatus) + .WhereIF(socket.DisplayName != default, w => w.DisplayName == socket.DisplayName) + .WhereIF(socket.Remark != default, w => w.Remark == socket.Remark); + return sqlFuc.ToList(); + + } + catch (Exception ex) + { + _ = ex; + } + return default; + } + + /// + /// 插入一条记录 + /// + /// + /// + public int Insert(AppTcp socket) + { + try + { + int insertRows = CommonTool.DbServe.Insertable(socket).ExecuteCommand(); + return insertRows; + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 根据主键更新记录,忽略 null + /// + /// + /// + public int Update(AppTcp socket) + { + try + { + int insertRows = CommonTool.DbServe.Updateable(socket).IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + return insertRows; + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 删除一条记录 ---- 根据主键 + /// + /// + /// + public int Delete(AppTcp socket) + { + try + { + int insertRows = CommonTool.DbServe.Deleteable(socket).ExecuteCommand(); + return insertRows; + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 根据状态查询 socket 数据 + /// + /// + /// + public List? GetNeedUseSocket(int status) + { + try + { + List sockets = CommonTool.DbServe.Queryable().Where(w => w.TcpStatus == status).ToList(); + return sockets; + + } + catch (Exception ex) + { + _ = ex; + } + return default; + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppUserDao.cs b/WcsMain/DataBase/Dao/AppUserDao.cs new file mode 100644 index 0000000..3555a3b --- /dev/null +++ b/WcsMain/DataBase/Dao/AppUserDao.cs @@ -0,0 +1,86 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.User; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// 用户表操作类 +/// +[Component] +public class AppUserDao +{ + + + /// + /// 查询用户 + /// + /// + /// + public List? Query(AppUser user) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(user.UserId != default, w => w.UserId == user.UserId) + .WhereIF(user.UserName != default, w => w.UserName == user.UserName) + .WhereIF(user.UserPassword != default, w => w.UserPassword == user.UserPassword) + .WhereIF(user.UserStatus != default, w => w.UserStatus == user.UserStatus) + .WhereIF(user.UserGroup != default, w => w.UserGroup == user.UserGroup); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + /*------------------------------------业务代码-------------------------------------*/ + + + /// + /// 分页查询所有用户信息 + /// + /// + /// + public (List users, int totalRows) QueryWithPage(GetUserWithPageRequest request) + { + try + { + int totalRows = 0; + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(request.SearchStr), + w => w.UserId!.Contains(request.SearchStr!) + || w.UserName!.Contains(request.SearchStr!) + || w.UserGroup!.Contains(request.SearchStr!)); // 模糊查询 + + if (request.UserStatus != default) // 查询任务状态 + { + List userStatuses = []; + foreach (var userStatus in request.UserStatus) + { + if (userStatus == "禁用") { userStatuses.Add((int)UserStatusEnum.disabled); } + if (userStatus == "正常") { userStatuses.Add((int)UserStatusEnum.normal); } + } + sqlFuc.Where(w => userStatuses.Contains(w.UserStatus)); + } + if (request.TimeRange is { Count: 2 }) // 时间范围 + { + sqlFuc.Where(w => w.CreateTime > request.TimeRange[0] && w.CreateTime < request.TimeRange[1]); + } + sqlFuc.OrderByDescending(o => new { o.CreateTime }); + var queryResult = sqlFuc.ToPageList(request.Page!.PageIndex, request.Page!.PageSize, ref totalRows); + return (queryResult, totalRows); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppUserGroupDao.cs b/WcsMain/DataBase/Dao/AppUserGroupDao.cs new file mode 100644 index 0000000..f29aac5 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppUserGroupDao.cs @@ -0,0 +1,69 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppUserGroupDao +{ + + public List? Query() => Query(new AppUserGroup()); + + + public List? Query(AppUserGroup queryEntity) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(queryEntity.GroupId != default, w => w.GroupId == queryEntity.GroupId) + .WhereIF(queryEntity.GroupName != default, w => w.GroupName == queryEntity.GroupName) + .WhereIF(queryEntity.GroupStatus != default, w => w.GroupStatus == queryEntity.GroupStatus) + .OrderBy(o => o.GroupId); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 添加用户组 + /// + /// + /// + public int Insert(params AppUserGroup[] userGroups) + { + try + { + var result = CommonTool.DbServe.Insertable(userGroups).ExecuteCommand(); + return result; + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 删除用户组,根据主键 + /// + /// + /// + public int Delete(AppUserGroup userGroup) + { + try + { + var result = CommonTool.DbServe.Deleteable(userGroup).ExecuteCommand(); + return result; + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppUserRuleDao.cs b/WcsMain/DataBase/Dao/AppUserRuleDao.cs new file mode 100644 index 0000000..03a6102 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppUserRuleDao.cs @@ -0,0 +1,68 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.UserRule; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppUserRuleDao +{ + + public List? Query() => Query(new AppUserRule()); + + /// + /// 查询用户权限 + /// + /// + /// + public List? Query(AppUserRule queryEntity) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(queryEntity.GroupId != default, w => w.GroupId == queryEntity.GroupId) + .WhereIF(queryEntity.MinorMenuIndex != default, w => w.MinorMenuIndex == queryEntity.MinorMenuIndex) + .OrderBy(o => o.GroupId); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 删除用户的权限,并添加新的权限 + /// + /// + /// + public bool UpdateUserRule(UpdateUserRuleRequest request) + { + try + { + var result = CommonTool.DbServe.Ado.UseTran(() => + { + CommonTool.DbServe.Deleteable().Where(w => w.GroupId == request.UserGroupId).ExecuteCommand(); + if (request.UserRules == default) return; + List userRules = []; + foreach (var userRule in request.UserRules) + { + userRules.Add(new AppUserRule() + { + GroupId = request.UserGroupId, + MinorMenuIndex = userRule + }); + } + CommonTool.DbServe.Insertable(userRules).ExecuteCommand(); + }); + return result.Data; + } + catch (Exception e) + { + _ = e; + return false; + } + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppVehicleBindingDao.cs b/WcsMain/DataBase/Dao/AppVehicleBindingDao.cs new file mode 100644 index 0000000..2f74216 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppVehicleBindingDao.cs @@ -0,0 +1,93 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +[Component] +public class AppVehicleBindingDao +{ + + /// + /// 插入一条数据 + /// + /// + /// + public int Insert(AppVehicleBinding entity) + { + try + { + var sqlFuc = CommonTool.DbServe.Insertable(entity); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 查询所有 + /// + /// + public List? Query() => Query(new AppVehicleBinding()); + + /// + /// 查询数据 + /// + /// + /// + public List? Query(AppVehicleBinding queryEntity) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(queryEntity.VehicleNo), w => w.VehicleNo == queryEntity.VehicleNo) + .WhereIF(queryEntity.PlcId == default, w => w.PlcId == queryEntity.PlcId); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + + } + + + + + + /*------------------------------业务方法-----------------------------------------------*/ + + + /// + /// 根据载具号删除条目 + /// + /// + /// + public int DeleteWithVehicleNo(string vehicleNo) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable() + .Where(w => w.VehicleNo == vehicleNo); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + + + + + + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppWcsTaskDao.cs b/WcsMain/DataBase/Dao/AppWcsTaskDao.cs new file mode 100644 index 0000000..9495f13 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppWcsTaskDao.cs @@ -0,0 +1,482 @@ +using System.Text; +using WcsMain.ApiServe.Controllers.Dto.WcsDto.WcsTask; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.TaskEnum; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// tbl_app_wcs_task 表操作类 +/// +[Component] +public class AppWcsTaskDao +{ + + /// + /// 新增一条记录 + /// + /// + /// + public int Insert(params AppWcsTask[] appWcsTask) + { + try + { + return CommonTool.DbServe.Insertable(appWcsTask).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /// + /// 更新一条记录,根据主键,主键PlcId + /// + /// + /// + public int Update(AppWcsTask appWcsTask) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(appWcsTask).IgnoreColumns(ignoreAllNullColumns: true); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /// + /// 删除一条记录,根据主键,主键PlcId + /// + /// + /// + public int Delete(AppWcsTask appWcsTask) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable(appWcsTask); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /// + /// 查找数据 + /// + /// + /// + /// + /// 时间不在筛选行列 + /// + public List? Select(AppWcsTask appWcsTask) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(appWcsTask.PlcId != default, w => w.PlcId == appWcsTask.PlcId) + .WhereIF(appWcsTask.NextPlcId != default, w => w.NextPlcId == appWcsTask.NextPlcId) + .WhereIF(appWcsTask.TaskCategory != default, w => w.TaskCategory == appWcsTask.TaskCategory) + .WhereIF(appWcsTask.TaskId != default, w => w.TaskId == appWcsTask.TaskId) + .WhereIF(appWcsTask.TaskType != default, w => w.TaskType == appWcsTask.TaskType) + .WhereIF(appWcsTask.TaskSort != default, w => w.TaskSort == appWcsTask.TaskSort) + .WhereIF(appWcsTask.TaskStatus != default, w => w.TaskStatus == appWcsTask.TaskStatus) + .WhereIF(appWcsTask.Origin != default, w => w.Origin == appWcsTask.Origin) + .WhereIF(appWcsTask.Destination != default, w => w.Destination == appWcsTask.Destination) + .WhereIF(appWcsTask.PlcVehicleNo != default, w => w.PlcVehicleNo == appWcsTask.PlcVehicleNo) + .WhereIF(appWcsTask.VehicleNo != default, w => w.VehicleNo == appWcsTask.VehicleNo) + .WhereIF(appWcsTask.VehicleSize != default, w => w.VehicleSize == appWcsTask.VehicleSize) + .WhereIF(appWcsTask.Weight != default, w => w.Weight == appWcsTask.Weight) + .OrderByDescending(o => o.CreateTime).OrderBy(o => o.TaskSort); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查找所有数据 + /// + /// + public List? Select() => Select(new AppWcsTask()); + + + + + + /// + /// 根据 TaskId 删除任务 + /// + /// + /// + public int DeleteWithTaskId(string taskId) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable().Where(w => w.TaskId == taskId); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 带参数分页查询 + /// + /// + /// + public (List? wcsTasks, int totalRows) SelectPage(GetWcsTaskWithPageRequest pageRequest) + { + try + { + int totalRows = 0; + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(pageRequest.SearchStr), + w => w.Destination!.Contains(pageRequest.SearchStr!) + || w.PlcId.ToString()!.Contains(pageRequest.SearchStr!) + || w.Origin!.Contains(pageRequest.SearchStr!) + || w.VehicleNo!.Contains(pageRequest.SearchStr!) + || w.TaskId!.Contains(pageRequest.SearchStr!) + || w.Remark!.Contains(pageRequest.SearchStr!)); // 模糊查询 + if (pageRequest.TaskType != default) // 查询任务类型 + { + List taskTypes = []; + foreach (var taskType in pageRequest.TaskType) + { + if (taskType == "入库任务") { taskTypes.Add((int)TaskTypeEnum.inTask); } + if (taskType == "出库任务") { taskTypes.Add((int)TaskTypeEnum.outTask); } + if (taskType == "拣选任务") { taskTypes.Add((int)TaskTypeEnum.pick); } + if (taskType == "盘点任务") { taskTypes.Add((int)TaskTypeEnum.check); } + if (taskType == "移库任务") { taskTypes.Add((int)TaskTypeEnum.moveTask); } + } + sqlFuc.Where(w => taskTypes.Contains(w.TaskType)); + } + if (pageRequest.TaskStatus != default) // 查询任务状态 + { + List taskStatuss = []; + foreach (var taskStatus in pageRequest.TaskStatus) + { + if (taskStatus == "待执行") { taskStatuss.Add((int)WcsTaskStatusEnum.create); } + if (taskStatus == "离开起点") { taskStatuss.Add((int)WcsTaskStatusEnum.leaveOrigin); } + if (taskStatus == "执行中") { taskStatuss.Add((int)WcsTaskStatusEnum.running); } + if (taskStatus == "到达终点") { taskStatuss.Add((int)WcsTaskStatusEnum.arriveDestination); } + if (taskStatus == "执行完成") { taskStatuss.Add((int)WcsTaskStatusEnum.complete); } + if (taskStatus == "执行异常") { taskStatuss.Add((int)WcsTaskStatusEnum.err); } + } + sqlFuc.Where(w => taskStatuss.Contains(w.TaskStatus)); + } + if (pageRequest.TimeRange is { Count: 2 }) // 时间范围 + { + sqlFuc.Where(w => w.CreateTime > pageRequest.TimeRange[0] && w.CreateTime < pageRequest.TimeRange[1]); + } + sqlFuc.OrderByDescending(o => new { o.CreateTime }); + var queryResult = sqlFuc.ToPageList(pageRequest.Page!.PageIndex, pageRequest.Page!.PageSize, ref totalRows); + return (queryResult, totalRows); + } + catch (Exception ex) + { + _ = ex; + return default; + } + + } + + + + /******************************************************************** 业务方法 *********************************************/ + + /// + /// 保留多少天前数据 ---- 删除的是备份表 + /// + /// + /// + public int ClearData(int days) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable().AS("[tbl_app_wcs_task_bak]").Where(w => w.CreateTime < DateTime.Now.AddDays(-days)); + return sqlFuc.ExecuteCommand(); + + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + /// + /// 转换WMS任务到WCS任务 + /// + /// + /// + public string TransWmsTaskToWcsTask(params AppWcsTask[]? wcsTasks) + { + if (wcsTasks == default || wcsTasks.Length < 1) + { + return "传入的任务为空"; + } + var result = CommonTool.DbServe.Ado.UseTran(() => + { + CommonTool.DbServe.Insertable(wcsTasks).ExecuteCommand(); + foreach (var wcsTask in wcsTasks) + { + CommonTool.DbServe.Updateable(new AppWmsTask() + { TaskId = wcsTask.TaskId, TaskStatus = (int)WmsTaskStatusEnum.queuing }) + .IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommand(); + } + }); + return result.Data ? string.Empty : result.ErrorException.Message; + } + + /// + /// 查找各组 taskId 中最前的任务 + /// + /// + public List? GetNeedExecuteTasks() + { + try + { + const string sql = "SELECT * FROM tbl_app_wcs_task a WHERE task_sort = (SELECT MIN(task_sort) from tbl_app_wcs_task where a.task_id = task_id) and task_status = 0 ORDER BY create_time asc"; + List tasks = CommonTool.DbServe.Ado.SqlQuery(sql); + return tasks; + + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 校验除了这个任务以外,该组是否有其他任务 + /// + /// + /// + public List? CheckIsHaveOtherTask(AppWcsTask wcsTask) + { + try + { + List wcsTasks = CommonTool.DbServe.Queryable() + .Where(w => w.TaskId == wcsTask.TaskId && w.TaskSort != wcsTask.TaskSort).ToList(); + return wcsTasks; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 根据任务ID查询所有的wcs任务数据,包括备份表内的数据 + /// + /// /// + /// + public List? GetAllTasksWithBakData(string taskId) + { + try + { + string selectSql = $"select * from (select * from tbl_app_wcs_task union all select * from tbl_app_wcs_task_bak) as a where task_id = '{taskId}' order by create_time asc "; + List wcsTasks = CommonTool.DbServe.SqlQueryable(selectSql).ToList(); + return wcsTasks; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + /// + /// 根据 PlcId 查找任务数据,包括备份表 + /// + /// + /// + public List? GetAllTasksWithBakDataUsePlcId(int? plcId) + { + try + { + string selectSql = $"select * from (select * from tbl_app_wcs_task union all select * from tbl_app_wcs_task_bak) as a where plc_id = '{plcId}' order by create_time asc "; + List wcsTasks = CommonTool.DbServe.SqlQueryable(selectSql).ToList(); + return wcsTasks; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + + /// + /// 根据堆垛机编号查询该堆垛机下需要执行的入库任务 + /// + /// + /// + /// + public List? SelectInTaskWithStacker(int stackerId, string? vehicleNo = default) + { + try + { + StringBuilder sql = new(); + sql.AppendLine($"select * from tbl_app_wcs_task where origin in (select wcs_location from tbl_app_location where equipment_id = {stackerId}) and task_type = {(int)TaskTypeEnum.inTask} "); + sql.AppendLine($"and task_category = {(int)TaskCategoryEnum.stacker} "); + sql.AppendLine($"and task_status = {(int)WcsTaskStatusEnum.create} "); + if (!string.IsNullOrEmpty(vehicleNo)) + { + sql.AppendLine($"and vehicle_no = '{vehicleNo}' "); + } + sql.AppendLine("order by priority desc, wms_time asc "); + var sqlFuc = CommonTool.DbServe.Ado.SqlQuery(sql.ToString()); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 根据堆垛机编号查询该堆垛机下需要执行的出库任务 + /// + /// + /// + /// + public List? SelectOutTaskWithStacker(int stackerId, string? vehicleNo = default) + { + try + { + StringBuilder sql = new(); + sql.AppendLine($"select * from tbl_app_wcs_task where origin in (select wcs_location from tbl_app_location where equipment_id = {stackerId}) and task_type = {(int)TaskTypeEnum.outTask} "); + sql.AppendLine($"and task_category = {(int)TaskCategoryEnum.stacker} "); + sql.AppendLine($"and task_status = {(int)WcsTaskStatusEnum.create} "); + if (!string.IsNullOrEmpty(vehicleNo)) + { + sql.AppendLine($"and vehicle_no = '{vehicleNo}' "); + } + sql.AppendLine("order by priority desc, wms_time asc "); + var sqlFuc = CommonTool.DbServe.Ado.SqlQuery(sql.ToString()); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + + /// + /// 根据堆垛机编号查询该堆垛机下需要执行的拣选出库任务 + /// + /// + /// + /// + public List? SelectPickOutTaskWithStacker(int stackerId, string? vehicleNo = "") + { + try + { + StringBuilder sql = new(); + sql.AppendLine($"select * from tbl_app_wcs_task where origin in (select wcs_location from tbl_app_location where equipment_id = {stackerId}) and task_type = {(int)TaskTypeEnum.pick} "); + sql.AppendLine($"and task_category = {(int)TaskCategoryEnum.stacker} "); + sql.AppendLine($"and task_status = {(int)WcsTaskStatusEnum.create} "); + if (!string.IsNullOrEmpty(vehicleNo)) + { + sql.AppendLine($"and vehicle_no = '{vehicleNo}' "); + } + sql.AppendLine("order by priority desc, create_time asc "); + var sqlFuc = CommonTool.DbServe.Ado.SqlQuery(sql.ToString()); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 根据堆垛机编号查询该堆垛机下需要执行的移库任务 + /// + /// + /// + /// + public List? SelectMoveTaskWithStacker(int stackerId, string? vehicleNo = "") + { + try + { + StringBuilder sql = new(); + sql.AppendLine($"select * from tbl_app_wcs_task where origin in (select wcs_location from tbl_app_location where equipment_id = {stackerId}) and task_type = {(int)TaskTypeEnum.moveTask} "); + sql.AppendLine($"and task_category = {(int)TaskCategoryEnum.stacker} "); + sql.AppendLine($"and task_status = {(int)WcsTaskStatusEnum.create} "); + if (!string.IsNullOrEmpty(vehicleNo)) + { + sql.AppendLine($"and vehicle_no = '{vehicleNo}' "); + } + sql.AppendLine("order by priority desc, create_time asc "); + var sqlFuc = CommonTool.DbServe.Ado.SqlQuery(sql.ToString()); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + /// + /// 根据起点或者终点查找当前正在运行的任务 + /// + /// + /// + public List? QueryTaskWithWcsLocation(params string?[] destinations) + { + List? result = []; + if (destinations.Length < 1) return result; + try + { + foreach (var destination in destinations) + { + var sqlFuc = CommonTool.DbServe.Queryable() + .Where(x => x.Destination == destination || x.Origin == destination) + .Where(x => DataService.EnumData.GetWcsTaskStatusEnumRunningStatus().Contains((int)WcsTaskStatusEnum.running)); + result.AddRange(sqlFuc.ToList()); + } + return result; + } + catch (Exception ex) + { + _ = ex; + return default; + } + + + + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/AppWmsTaskDao.cs b/WcsMain/DataBase/Dao/AppWmsTaskDao.cs new file mode 100644 index 0000000..0522076 --- /dev/null +++ b/WcsMain/DataBase/Dao/AppWmsTaskDao.cs @@ -0,0 +1,496 @@ +using WcsMain.ApiServe.Controllers.Dto.WcsDto.WmsTask; +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.TaskEnum; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// tbl_app_wms_task 表的增删改查 +/// +[Component] +public class AppWmsTaskDao +{ + /// + /// 添加记录 + /// 注意:尚未清楚是否为事务插入 + /// + /// + /// + public int Insert(params AppWmsTask[] appWmsTasks) + { + try + { + return CommonTool.DbServe.Insertable(appWmsTasks).ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + + /// + /// 更新数据,以主键为条件,主键 : TaskId + /// + /// + /// + public int Update(AppWmsTask appWmsTask) + { + try + { + var sqlFuc = CommonTool.DbServe.Updateable(appWmsTask).IgnoreColumns(ignoreAllNullColumns: true); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 删除数据,以主键为条件 + /// + /// + /// + public int Delete(AppWmsTask appWmsTask) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable(appWmsTask); + return sqlFuc.ExecuteCommand(); + } + catch (Exception ex) + { + _ = ex; + return 0; + } + } + + /// + /// 查找数据 + /// + /// + /// + /// + /// 时间不在筛选行列 + /// + public List? Select(AppWmsTask appWmsTask) + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(appWmsTask.TaskId), w => w.TaskId == appWmsTask.TaskId) + .WhereIF(appWmsTask.TaskType != null, w => w.TaskType == appWmsTask.TaskType) + .WhereIF(appWmsTask.TaskStatus != null, w => w.TaskStatus == appWmsTask.TaskStatus) + .WhereIF(!string.IsNullOrEmpty(appWmsTask.Origin), w => w.Origin == appWmsTask.Origin) + .WhereIF(!string.IsNullOrEmpty(appWmsTask.Destination), + w => w.Destination == appWmsTask.Destination) + .WhereIF(!string.IsNullOrEmpty(appWmsTask.VehicleNo), w => w.VehicleNo == appWmsTask.VehicleNo) + .WhereIF(appWmsTask.VehicleSize != null, w => w.VehicleSize == appWmsTask.VehicleSize) + .WhereIF(appWmsTask.Weight != null, w => w.Weight == appWmsTask.Weight) + .WhereIF(!string.IsNullOrEmpty(appWmsTask.CreatePerson), + w => w.CreatePerson == appWmsTask.CreatePerson) + .WhereIF(!string.IsNullOrEmpty(appWmsTask.TaskMsg), w => w.TaskMsg == appWmsTask.TaskMsg) + .OrderBy(o => o.CreateTime); + + _ = sqlFuc.ToSql(); + return sqlFuc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + /// + /// 查找所有数据 + /// + /// + public List? Select() => Select(new AppWmsTask()); + + + + + /********************************************************* 扩展方法 *****************************************************************/ + + /// + /// 保留多少条数据 + /// + /// + /// + public int ClearData(int days) + { + try + { + var sqlFuc = CommonTool.DbServe.Deleteable().Where(w => w.CreateTime < DateTime.Now.AddDays(-days)); + return sqlFuc.ExecuteCommand(); + + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 将taskId更新成新的 + /// + /// + /// + /// + public void UpdateTaskId(string? oldTaskId, string? newTaskId) //int + { + + + + } + + + /// + /// 查询数据返回前端 + /// 查询数据,按创建时间倒序排列 + /// + /// + public List? SelectToWeb() + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable().OrderByDescending(o => o.CreateTime); + return sqlFuc.ToList(); + + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查询任务数据返回前端,只查询未结束的任务 + /// 查询数据,按创建时间倒序排列 + /// + /// + public List? SelectWmsTaskNotEnd() + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable().Where(w => w.TaskStatus == (int)WmsTaskStatusEnum.queuing + || w.TaskStatus == (int)WmsTaskStatusEnum.create + || w.TaskStatus == (int)WmsTaskStatusEnum.running).OrderByDescending(o => o.CreateTime); + return sqlFuc.ToList(); + + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 查询自起点索引后的多少条数据 + /// + /// + /// + public (List? wmsTasks, int totalRows) SelectPage(GetWmsTaskWithPageRequest pageRequest) + { + try + { + int totalRows = 0; + var sqlFuc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(pageRequest.SearchStr), + w => w.Destination!.Contains(pageRequest.SearchStr!) + || w.Origin!.Contains(pageRequest.SearchStr!) + || w.VehicleNo!.Contains(pageRequest.SearchStr!) + || w.TaskId!.Contains(pageRequest.SearchStr!) + || w.TaskMsg!.Contains(pageRequest.SearchStr!)); // 模糊查询 + if (pageRequest.TaskType != default) // 查询任务类型 + { + List taskTypes = []; + pageRequest.TaskType.ForEach(item => taskTypes.Add(item)); + sqlFuc.Where(w => taskTypes.Contains(w.TaskType)); + } + if (pageRequest.TaskStatus != default) // 查询任务状态 + { + List taskStatuses = []; + pageRequest.TaskStatus.ForEach(item => taskStatuses.Add(item)); + sqlFuc.Where(w => taskStatuses.Contains(w.TaskStatus)); + } + if (pageRequest.TimeRange is { Count: 2 }) // 时间范围 + { + sqlFuc.Where(w => w.CreateTime > pageRequest.TimeRange[0] && w.CreateTime < pageRequest.TimeRange[1]); + } + sqlFuc.OrderByDescending(o => new { o.CreateTime }); + var queryResult = sqlFuc.ToPageList(pageRequest.Page!.PageIndex, pageRequest.Page!.PageSize, ref totalRows); + return (queryResult, totalRows); + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + + + /// + /// 查找未执行完成的任务 + /// + /// + public List? SelectNotComplete() + { + try + { + var sqlFuc = CommonTool.DbServe.Queryable() + .Where(w => w.TaskStatus != (int)WmsTaskStatusEnum.complete && w.TaskStatus != (int)WmsTaskStatusEnum.err) + .ToList(); + return sqlFuc; + } + catch (Exception ex) + { + _ = ex; + return default; + } + } + + /// + /// 重置任务状态 + /// + /// + /// + /// 重置信息 + /// + public string ResetTaskWithTaskId(string? taskId, string? newDestination, string resetMsg) + { + if (string.IsNullOrEmpty(taskId)) + { + return "任务号为空"; + } + /* + 需要执行的操作 + 1、重置WMS任务表状态 + 2、删除WCS任务表数据 + */ + var result = CommonTool.DbServe.Ado.UseTran(() => + { + // -- 更新wms任务状态 + if (string.IsNullOrEmpty(newDestination)) + { + CommonTool.DbServe.Updateable(new AppWmsTask() + { TaskId = taskId, TaskStatus = (int)WmsTaskStatusEnum.create }) + .UpdateColumns(u => new { u.TaskStatus }).WhereColumns(w => new { w.TaskId }) + .ExecuteCommand(); + } + else + { + CommonTool.DbServe.Updateable(new AppWmsTask() + { TaskId = taskId, TaskStatus = (int)WmsTaskStatusEnum.create, Destination = newDestination }) + .UpdateColumns(u => new { u.TaskStatus, u.Destination }).WhereColumns(w => new { w.TaskId }) + .ExecuteCommand(); + } + // -- 更新wcs任务表状态 + CommonTool.DbServe + .Updateable(new AppWcsTask() + { TaskId = taskId, TaskStatus = (int)WcsTaskStatusEnum.err, Remark = $"{resetMsg}{(string.IsNullOrEmpty(newDestination) ? "" : "新的目的地为:")}{newDestination}" }) + .UpdateColumns(u => new { u.TaskStatus, u.Remark }).WhereColumns(w => new { w.TaskId }) + .ExecuteCommand(); + // -- 备份wcs任务表 + CommonTool.DbServe.Ado.ExecuteCommand( + $"insert into tbl_app_wcs_task_bak select * from tbl_app_wcs_task where task_id = '{taskId}'"); + // -- 删除wcs任务表 + CommonTool.DbServe.Deleteable().Where(w => w.TaskId == taskId).ExecuteCommand(); + }); + return result.Data ? string.Empty : result.ErrorMessage; + } + + /// + /// 完成任务 + /// + /// + /// /// + /// + public string CompleteTaskWithTaskId(string? taskId, string completeText = "") + { + if (string.IsNullOrEmpty(taskId)) + { + return "任务号为空"; + } + /* + 需要执行的操作 + 1、完成WMS任务表状态 + 2、删除WCS任务表数据 + */ + var result = CommonTool.DbServe.Ado.UseTran(() => + { + // -- 更新wms任务状态 + CommonTool.DbServe + .Updateable(new AppWmsTask() + { TaskId = taskId, TaskStatus = (int)WmsTaskStatusEnum.complete, EndTime = DateTime.Now, TaskMsg = completeText }) + .IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + // -- 更新wcs任务表状态 + CommonTool.DbServe + .Updateable(new AppWcsTask() + { TaskId = taskId, TaskStatus = (int)WcsTaskStatusEnum.complete, Remark = completeText, CompleteTime = DateTime.Now }) + .UpdateColumns(u => new { u.TaskStatus, u.Remark, u.CompleteTime }).WhereColumns(w => new { w.TaskId }) + .ExecuteCommand(); + // -- 备份WCS任务表 + CommonTool.DbServe.Ado.ExecuteCommand( + $"insert into tbl_app_wcs_task_bak select * from tbl_app_wcs_task where task_id = '{taskId}'"); + // -- 删除WCS任务表 + CommonTool.DbServe.Deleteable().Where(w => w.TaskId == taskId).ExecuteCommand(); + }); + return result.Data ? string.Empty : result.ErrorMessage; + } + + /// + /// 完成任务 ---- 完成最后一个任务 + /// + /// + /// + /// + public string CompleteTaskWithTaskInfo(AppWcsTask wcsTaskUpdateEntity, AppWmsTask wmsTaskUpdateEntity) + { + /* + 需要执行的操作 + 1、完成WMS任务表状态 + 2、删除WCS任务表数据 + */ + var result = CommonTool.DbServe.Ado.UseTran(() => + { + // -- 更新wms任务状态 + CommonTool.DbServe + .Updateable(wmsTaskUpdateEntity) + .IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + // -- 更新wcs任务表状态 + CommonTool.DbServe + .Updateable(wcsTaskUpdateEntity) + .IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + // -- 备份WCS任务表 + CommonTool.DbServe.Ado.ExecuteCommand( + $"insert into tbl_app_wcs_task_bak select * from tbl_app_wcs_task where task_id = '{wmsTaskUpdateEntity.TaskId}'"); + // -- 删除WCS任务表 + CommonTool.DbServe.Deleteable().Where(w => w.TaskId == wmsTaskUpdateEntity.TaskId).ExecuteCommand(); + }); + return result.Data ? string.Empty : result.ErrorMessage; + } + + + /// + /// 删除任务 + /// + /// + /// 删除原因 + /// + public string DeleteTaskWithTaskId(string? taskId, string deleteMsg) + { + if (string.IsNullOrEmpty(taskId)) return "任务号为空"; + var result = CommonTool.DbServe.Ado.UseTran(() => + { + // -- 更新wms表状态 + CommonTool.DbServe.Updateable(new AppWmsTask() + { TaskId = taskId, TaskStatus = (int)WmsTaskStatusEnum.err, EndTime = DateTime.Now, TaskMsg = deleteMsg }) + .IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommand(); + // -- 更新wcs任务表状态 + CommonTool.DbServe + .Updateable(new AppWcsTask() + { TaskId = taskId, TaskStatus = (int)WcsTaskStatusEnum.err, Remark = deleteMsg }) + .UpdateColumns(u => new { u.TaskStatus, u.Remark }).WhereColumns(w => new { w.TaskId }) + .ExecuteCommand(); + // -- 备份WCS任务表 + CommonTool.DbServe.Ado.ExecuteCommand( + $"insert into tbl_app_wcs_task_bak select * from tbl_app_wcs_task where task_id = '{taskId}'"); + // -- 删除WCS任务表 + CommonTool.DbServe.Deleteable().Where(w => w.TaskId == taskId).ExecuteCommand(); + }); + return result.Data ? string.Empty : result.ErrorMessage; + } + + /// + /// 取消任务 + /// + /// + /// /// + /// + public string CancelTaskWithTaskId(string? taskId, string cancelPerson) + { + if (string.IsNullOrEmpty(taskId)) return "任务号为空"; + var result = CommonTool.DbServe.Ado.UseTran(() => + { + // -- 更新wms表状态 + CommonTool.DbServe.Updateable(new AppWmsTask() + { TaskId = taskId, TaskStatus = (int)WmsTaskStatusEnum.err, EndTime = DateTime.Now, TaskMsg = $"【{cancelPerson}】取消任务" }) + .IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommand(); + // -- 更新wcs任务表状态 + CommonTool.DbServe + .Updateable(new AppWcsTask() + { TaskId = taskId, TaskStatus = (int)WcsTaskStatusEnum.err, Remark = $"【{cancelPerson}】取消任务", CompleteTime = DateTime.Now }) + .UpdateColumns(u => new { u.TaskStatus, u.Remark, u.CompleteTime }).WhereColumns(w => new { w.TaskId }) + .ExecuteCommand(); + // -- 备份WCS任务表 + CommonTool.DbServe.Ado.ExecuteCommand( + $"insert into tbl_app_wcs_task_bak select * from tbl_app_wcs_task where task_id = '{taskId}'"); + // -- 删除WCS任务表 + CommonTool.DbServe.Deleteable().Where(w => w.TaskId == taskId).ExecuteCommand(); + }); + return result.Data ? string.Empty : result.ErrorMessage; + } + + /// + /// 添加记录 + /// 注意:尚未清楚是否为事务插入 + /// + /// + /// + public int InsertTaskAndMarkErr(params AppWmsTask[] appWmsTasks) + { + if (appWmsTasks.Length < 1) return 0; + try + { + int count = 0; + Action action = () => { }; + foreach (var wmsTask in appWmsTasks) + { + action += () => + { + // -- 更新wms表状态 + CommonTool.DbServe.Updateable(new AppWmsTask() + { TaskStatus = (int)WmsTaskStatusEnum.err, EndTime = DateTime.Now, TaskMsg = $"由于收到新任务,该任务被取消,操作人:{wmsTask.CreatePerson}" }) + .UpdateColumns(u => new { u.TaskStatus, u.TaskMsg, u.EndTime }) + .Where(w => w.VehicleNo == wmsTask.VehicleNo && (w.TaskStatus == (int)WmsTaskStatusEnum.queuing || w.TaskStatus == (int)WmsTaskStatusEnum.create)) + .ExecuteCommand(); + // -- 更新wcs任务表状态 + CommonTool.DbServe.Updateable(new AppWcsTask() + { TaskStatus = (int)WcsTaskStatusEnum.err, CompleteTime = DateTime.Now, Remark = $"由于收到新任务,该任务被取消,操作人:{wmsTask.CreatePerson}" }) + .UpdateColumns(u => new { u.TaskStatus, u.Remark, u.CompleteTime }) + .Where(w => w.VehicleNo == wmsTask.VehicleNo && w.TaskStatus == (int)WcsTaskStatusEnum.create) + .ExecuteCommand(); + // -- 备份WCS任务表 + CommonTool.DbServe.Ado.ExecuteCommand( + $"insert into tbl_app_wcs_task_bak select * from tbl_app_wcs_task where vehicle_no = '{wmsTask.VehicleNo}' and task_type = {wmsTask.TaskType}"); + // -- 删除WCS任务表 + CommonTool.DbServe.Deleteable() + .Where(w => w.VehicleNo == wmsTask.VehicleNo && w.TaskType == wmsTask.TaskType). + ExecuteCommand(); + // -- 插入WMS任务 + CommonTool.DbServe.Insertable(wmsTask).ExecuteCommand(); + }; + count++; + } + var result = CommonTool.DbServe.Ado.UseTran(action); + if (result.IsSuccess) return count; + } + catch (Exception ex) { _ = ex; } + return 0; + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/Dao/BsPickStandDao.cs b/WcsMain/DataBase/Dao/BsPickStandDao.cs new file mode 100644 index 0000000..3fc2de3 --- /dev/null +++ b/WcsMain/DataBase/Dao/BsPickStandDao.cs @@ -0,0 +1,50 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.Dao; + +/// +/// tbl_bs_pick_stand 的操作类 +/// +[Component] +public class BsPickStandDao +{ + + /// + /// 查询所有数据 + /// + /// + public List? Query() => Query(new BsPickStand()); + + /// + /// 根据条件查询数据 + /// + /// + /// + public List? Query(BsPickStand queryEntity) + { + try + { + var sqlFUc = CommonTool.DbServe.Queryable() + .WhereIF(!string.IsNullOrEmpty(queryEntity.PickStand), w => w.PickStand == queryEntity.PickStand) + .WhereIF(queryEntity.StandType != default, w => w.StandType == queryEntity.StandType) + .WhereIF(queryEntity.StandStatus != default, w => w.StandStatus == queryEntity.StandStatus) + .OrderBy(o => o.PickStand); + return sqlFUc.ToList(); + } + catch (Exception ex) + { + _ = ex; + return default; + } + + + + + + } + + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/MixDao/TaskDao.cs b/WcsMain/DataBase/MixDao/TaskDao.cs new file mode 100644 index 0000000..f0e7913 --- /dev/null +++ b/WcsMain/DataBase/MixDao/TaskDao.cs @@ -0,0 +1,198 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.TaskEnum; +using WcsMain.ExtendMethod; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataBase.MixDao; + +/* + * 任务表的混合操作类,一般用在此处的都是同时需要操作多个表的 + */ + +/// +/// 任务表的混合操作类 +/// +[Component] +public class TaskDao +{ + /// + /// wcs任务开始 + /// + /// + /// + /// + /// 将该任务状态更新为启动,若该任务是第一个任务,把对应的WMS任务更新为启动 + /// + public string StartTask(AppWcsTask wcsTask) + { + DateTime dateTime = DateTime.Now; + var result = CommonTool.DbServe.Ado.UseTran(() => + { + /* 更新WCS任务表状态 */ + CommonTool.DbServe.Updateable(new AppWcsTask + { + PlcId = wcsTask.PlcId, + TaskStatus = (int)WcsTaskStatusEnum.running, + StartTime = dateTime + }).IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + if(wcsTask.IsFirstTask()) + { + /* 更新WMS任务表状态 */ + CommonTool.DbServe.Updateable(new AppWmsTask + { + TaskId = wcsTask.TaskId, + TaskStatus = (int)WmsTaskStatusEnum.running, + StartTime = dateTime + }).IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + } + }); + return result.Data ? string.Empty : result.ErrorException.Message; + } + + /// + /// 任务异常,取消全部后续任务和该任务号的Wms任务 + /// + /// + /// + /// + public string TaskErrAndCancelOtherTask(AppWcsTask wcsTask, string errMsg) + { + DateTime dateTime = DateTime.Now; + var result = CommonTool.DbServe.Ado.UseTran(() => + { + /* 更新WCS任务表状态 */ + CommonTool.DbServe.Updateable(new AppWcsTask + { + TaskStatus = (int)WcsTaskStatusEnum.err, + CompleteTime = dateTime, + Remark = errMsg + }) + .UpdateColumns(u => new { u.TaskStatus, u.CompleteTime, u.Remark }) + .Where(w => w.TaskSort >= wcsTask.TaskSort && w.TaskId == wcsTask.TaskId) + .ExecuteCommand(); + if (wcsTask.IsFirstTask()) + { + /* 更新WMS任务表状态 */ + CommonTool.DbServe.Updateable(new AppWmsTask + { + TaskId = wcsTask.TaskId, + TaskStatus = (int)WmsTaskStatusEnum.err, + EndTime = dateTime, + TaskMsg = errMsg + }) + .IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + } + }); + return result.Data ? string.Empty : result.ErrorException.Message; + } + + /// + /// 任务异常,只异常当前任务 + /// + /// + /// + /// + public string TaskErr(AppWcsTask wcsTask, string errMsg) + { + DateTime dateTime = DateTime.Now; + var result = CommonTool.DbServe.Ado.UseTran(() => + { + /* 更新WCS任务表状态 */ + CommonTool.DbServe.Updateable(new AppWcsTask + { + TaskStatus = (int)WcsTaskStatusEnum.err, + CompleteTime = dateTime, + Remark = errMsg + }) + .UpdateColumns(u => new { u.TaskStatus, u.CompleteTime, u.Remark }) + .Where(w => w.PlcId == wcsTask.PlcId) + .ExecuteCommand(); + if (wcsTask.IsFirstTask()) + { + /* 更新WMS任务表状态 */ + CommonTool.DbServe.Updateable(new AppWmsTask + { + TaskId = wcsTask.TaskId, + TaskStatus = (int)WmsTaskStatusEnum.err, + EndTime = dateTime, + TaskMsg = errMsg + }) + .IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + } + }); + return result.Data ? string.Empty : result.ErrorException.Message; + } + + /// + /// WCS任务完成 + /// + /// + /// + /// + /// + /// 若完成的是最后一个任务,则完成WMS任务 + /// + public string ComlpeteTask(AppWcsTask wcsTask, string msg) + { + DateTime dateTime = DateTime.Now; + var result = CommonTool.DbServe.Ado.UseTran(() => + { + /* 更新WCS任务表状态 */ + CommonTool.DbServe.Updateable(new AppWcsTask + { + TaskStatus = (int)WcsTaskStatusEnum.complete, + CompleteTime = dateTime, + Remark = msg + }) + .UpdateColumns(u => new { u.TaskStatus, u.CompleteTime, u.Remark }) + .Where(w => w.TaskSort >= wcsTask.TaskSort && w.TaskId == wcsTask.TaskId) + .ExecuteCommand(); + if (wcsTask.IsLastTask()) + { + /* 更新WMS任务表状态 */ + CommonTool.DbServe.Updateable(new AppWmsTask + { + TaskId = wcsTask.TaskId, + TaskStatus = (int)WmsTaskStatusEnum.complete, + EndTime = dateTime, + TaskMsg = msg + }) + .IgnoreColumns(ignoreAllNullColumns: true) + .ExecuteCommand(); + } + }); + return result.Data ? string.Empty : result.ErrorException.Message; + } + + /// + /// 插入WCS任务,同时将WMS任务更成排队中 + /// + /// + /// + public string? CreateWcsTask(params AppWcsTask[] wcsTasks) + { + if (wcsTasks.Length < 1) return "未传入任务"; + DateTime dateTime = DateTime.Now; + var result = CommonTool.DbServe.Ado.UseTran(() => + { + /* 插入WCS任务表 */ + CommonTool.DbServe.Insertable(wcsTasks).ExecuteCommand(); + /* 更新WMS任务表状态为排队中 */ + foreach (var wcsTask in wcsTasks) + { + CommonTool.DbServe.Updateable(new AppWmsTask + { + TaskId = wcsTask.TaskId, + TaskStatus = (int)WmsTaskStatusEnum.queuing, + StartTime = dateTime + }).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommand(); + } + }); + return result.Data ? string.Empty : result.ErrorException.Message; + } +} diff --git a/WcsMain/DataBase/TableEntity/AppApiAccept.cs b/WcsMain/DataBase/TableEntity/AppApiAccept.cs new file mode 100644 index 0000000..73f66c0 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppApiAccept.cs @@ -0,0 +1,111 @@ +using System.Text; +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// 接口接收记录表 +/// +[SugarTable("tbl_app_api_accept")] +public class AppApiAccept +{ + /// + /// 请求编号 + /// + [SugarColumn(ColumnName = "accept_id", IsPrimaryKey = true)] + [JsonPropertyName("acceptId")] + public string? AcceptId { get; set; } + + /// + /// 请求路径 + /// + [SugarColumn(ColumnName = "path")] + [JsonPropertyName("path")] + public string? Path { get; set; } + + /// + /// 请求方法 + /// + [SugarColumn(ColumnName = "method")] + [JsonPropertyName("method")] + public string? Method { get; set; } + + /// + /// 请求的数据类型 + /// + [SugarColumn(ColumnName = "media_type")] + [JsonPropertyName("mediaType")] + public string? MediaType { get; set; } + + /// + /// 客户端地址 + /// + [SugarColumn(ColumnName = "client_address")] + [JsonPropertyName("clientAddress")] + public string? ClientAddress { get; set; } + + /// + /// 请求时间 + /// + [SugarColumn(ColumnName = "request_time")] + [JsonPropertyName("requestTime")] + public DateTime? RequestTime { get; set; } + + /// + /// 响应时间 + /// + [SugarColumn(ColumnName = "response_time")] + [JsonPropertyName("responseTime")] + public DateTime? ResponseTime { get; set; } + + /// + /// 接口耗时 + /// + [SugarColumn(ColumnName = "use_time")] + [JsonPropertyName("useTime")] + public double? UseTime { get; set; } + + /// + /// 请求数据 + /// + [SugarColumn(ColumnName = "request_msg")] + [JsonPropertyName("requestMsg")] + public string? RequestMsg { get; set; } + + /// + /// 响应数据 + /// + [SugarColumn(ColumnName = "response_msg")] + [JsonPropertyName("responseMsg")] + public string? ResponseMsg { get; set; } + + /// + /// 异常信息 + /// + [SugarColumn(ColumnName = "err_msg")] + [JsonPropertyName("errMsg")] + public string? ErrMsg { get; set; } + + + + + public override string ToString() + { + StringBuilder builder = new(); + builder.Append($"[请求路径]{Path}"); + builder.Append($"[请求方式]{Method}"); + builder.Append($"[请求格式]{MediaType}"); + builder.Append($"[用户地址]{ClientAddress}"); + builder.Append($"[请求时间]{RequestTime}"); + builder.Append($"[响应时间]{ResponseTime}"); + builder.Append($"[接口耗时]{UseTime}"); + builder.Append($"[请求数据]{RequestTime}"); + builder.Append($"[响应数据]{ResponseMsg}"); + if (!string.IsNullOrEmpty(ErrMsg)) + { + builder.Append($"[异常信息]{ErrMsg}"); + } + return builder.ToString(); + } +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppApiRequest.cs b/WcsMain/DataBase/TableEntity/AppApiRequest.cs new file mode 100644 index 0000000..519f2e0 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppApiRequest.cs @@ -0,0 +1,84 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// 接口请求类,存放请求的信息 +/// +[SugarTable("tbl_app_api_request")] +public class AppApiRequest +{ + /// + /// 请求ID + /// + [SugarColumn(ColumnName = "request_id", IsPrimaryKey = true)] + [JsonPropertyName("requestId")] + public string? RequestId { get; set; } + + /// + /// 请求地址 + /// + [SugarColumn(ColumnName = "request_url")] + [JsonPropertyName("requestUrl")] + public string? RequestUrl { get; set; } + + /// + /// 是否成功 + /// + [SugarColumn(ColumnName = "is_success")] + [JsonPropertyName("isSuccess")] + public int? IsSuccess { get; set; } + + /// + /// 请求方式 + /// + [SugarColumn(ColumnName = "request_method")] + [JsonPropertyName("requestMethod")] + public string? RequestMethod { get; set; } + + + /// + /// 请求信息 + /// + [SugarColumn(ColumnName = "request_msg")] + [JsonPropertyName("requestMsg")] + public string? RequestMsg { get; set; } + + /// + /// 响应信息 + /// + [SugarColumn(ColumnName = "response_msg")] + [JsonPropertyName("responseMsg")] + public string? ResponseMsg { get; set; } + + /// + /// 请求时间 + /// + [SugarColumn(ColumnName = "request_time")] + [JsonPropertyName("requestTime")] + public DateTime? RequestTime { get; set; } + + /// + /// 响应时间 + /// + [SugarColumn(ColumnName = "response_time")] + [JsonPropertyName("responseTime")] + public DateTime? ResponseTime { get; set; } + + /// + /// 耗时 + /// + [SugarColumn(ColumnName = "use_time")] + [JsonPropertyName("useTime")] + public double? UseTime { get; set; } + + /// + /// 错误信息 + /// + [SugarColumn(ColumnName = "err_msg")] + [JsonPropertyName("errMsg")] + public string? ErrMsg { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppConfig.cs b/WcsMain/DataBase/TableEntity/AppConfig.cs new file mode 100644 index 0000000..267130e --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppConfig.cs @@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// t_app_config 的实体 +/// +[SugarTable("tbl_app_config")] +public class AppConfig +{ + /// + /// 配置键 + /// + [SugarColumn(ColumnName = "config_key", IsPrimaryKey = true)] + [JsonPropertyName("configKey")] + public string? ConfigKey { get; set; } + + /// + /// 配置名称 + /// + [SugarColumn(ColumnName = "config_name")] + [JsonPropertyName("configName")] + public string? ConfigName { get; set; } + + /// + /// 配置类型 + /// + [SugarColumn(ColumnName = "config_type")] + [JsonPropertyName("configType")] + public string? ConfigType { get; set; } + + /// + /// 配置值 + /// + [SugarColumn(ColumnName = "config_value")] + [JsonPropertyName("configValue")] + public string? ConfigValue { get; set; } + + /// + /// 配置备注 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppDB.cs b/WcsMain/DataBase/TableEntity/AppDB.cs new file mode 100644 index 0000000..fa311b5 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppDB.cs @@ -0,0 +1,51 @@ +using System.Text.Json.Serialization; +using PlcTool.Siemens.PLCAttribute; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// DB地址实体类 tbl_app_db +/// +[SugarTable("tbl_app_db")] +public class AppDB +{ + /// + /// PLC编号 + /// + [SugarColumn(ColumnName = "plc_id")] + [PlcId] + [JsonPropertyName("plcId")] + public int? PlcId { get; set; } + /// + /// 地址名称 + /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "db_name")] + [PlcDBName] + [JsonPropertyName("dbName")] + public string? DBName { get; set; } + + /// + /// 地址值 + /// + [SugarColumn(ColumnName = "db_address")] + [PlcDBAddress] + [JsonPropertyName("dbAddress")] + public string? DBAddress { get; set; } + + /// + /// 是否系统值 + /// + [SugarColumn(ColumnName = "is_system")] + [JsonPropertyName("isSystem")] + public int? IsSystem { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppElTagBase.cs b/WcsMain/DataBase/TableEntity/AppElTagBase.cs new file mode 100644 index 0000000..7621545 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppElTagBase.cs @@ -0,0 +1,70 @@ +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + + +[SugarTable("tbl_app_eltag_base")] +public class AppElTagBase +{ + /// + /// 点位 + /// + [SugarColumn(ColumnName = "location", IsPrimaryKey = true)] + public string? Location { get; set; } + + /// + /// 标签的名称 + /// + [SugarColumn(ColumnName = "tag_name")] + public string? TagName { get; set; } + + /// + /// 标签的ID + /// + [SugarColumn(ColumnName = "tag_id")] + public int? TagId { get; set; } + + /// + /// 标签类型 + /// + [SugarColumn(ColumnName = "tag_type")] + public int? TagType { get; set; } + + /// + /// 控制器的别称 + /// + [SugarColumn(ColumnName = "controller_diaplay_name")] + public string? ControllerDisplayName { get; set;} + + /// + /// 所属区域 + /// + [SugarColumn(ColumnName = "area")] + public string? Area { get; set; } + + /// + /// LED灯的默认状态 + /// + [SugarColumn(ColumnName = "led_status")] + public int? LedStatus { get; set; } + + /// + /// 上次点亮的时间 + /// + [SugarColumn(ColumnName = "last_light_time")] + public DateTime? LastLightTime { get; set; } + + /// + /// 当前点亮的任务号 + /// + [SugarColumn(ColumnName = "task_id")] + public string? TaskId { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "remark")] + public string? Remark { get; set; } + + +} diff --git a/WcsMain/DataBase/TableEntity/AppElTagTask.cs b/WcsMain/DataBase/TableEntity/AppElTagTask.cs new file mode 100644 index 0000000..84ad4cf --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppElTagTask.cs @@ -0,0 +1,131 @@ +using SqlSugar; +using System.Text.Json.Serialization; + +namespace WcsMain.DataBase.TableEntity; + +[SugarTable("tbl_app_eltag_task")] +public class AppElTagTask +{ + /// + /// 任务号 + /// + [SugarColumn(ColumnName = "task_id", IsPrimaryKey = true)] + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 任务组 + /// + [SugarColumn(ColumnName = "task_group")] + [JsonPropertyName("taskGroup")] + public string? TaskGroup { get; set; } + + /// + /// 任务类型 + /// + [SugarColumn(ColumnName = "task_type")] + [JsonPropertyName("taskType")] + public int? TaskType { get; set; } + + /// + /// 点位 + /// + [SugarColumn(ColumnName = "location")] + [JsonPropertyName("location")] + public string? Location { get; set; } + + /// + /// 订单号 + /// + [SugarColumn(ColumnName = "order_id")] + [JsonPropertyName("orderId")] + public string? OrderId { get; set; } + + /// + /// 载具号 + /// + [SugarColumn(ColumnName = "vehicle_no")] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 物料编号 + /// + [SugarColumn(ColumnName = "goods_id")] + [JsonPropertyName("goodsId")] + public string? GoodsId { get; set; } + + /// + /// 物料名称 + /// + [SugarColumn(ColumnName = "goods_name")] + [JsonPropertyName("goodsName")] + public string? GoodsName { get; set; } + + /// + /// 任务状态 + /// + [SugarColumn(ColumnName = "task_status")] + [JsonPropertyName("taskStatus")] + public int? TaskStatus { get; set; } + + /// + /// 需求数量 + /// + [SugarColumn(ColumnName = "need_num")] + [JsonPropertyName("needNum")] + public int? NeedNum { get; set; } + + /// + /// 拣选数量 + /// + [SugarColumn(ColumnName = "pick_num")] + [JsonPropertyName("pickNum")] + public int? PickNum { get; set; } + + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_person")] + [JsonPropertyName("createPerson")] + public string? CreatePerson { get; set; } + + /// + /// 任务创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + [JsonPropertyName("createTime")] + public DateTime? CreateTime { get; set; } + + /// + /// 点亮时间 + /// + [SugarColumn(ColumnName = "light_time")] + [JsonPropertyName("lightTime")] + public DateTime? LightTime { get; set; } + + /// + /// 确认时间 + /// + [SugarColumn(ColumnName = "confirm_time")] + [JsonPropertyName("confirmTime")] + public DateTime? ConfirmTime { get; set;} + + /// + /// 熄灭时间 + /// + [SugarColumn(ColumnName = "off_time")] + [JsonPropertyName("offTime")] + public DateTime? OffTime { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + + +} diff --git a/WcsMain/DataBase/TableEntity/AppLocation.cs b/WcsMain/DataBase/TableEntity/AppLocation.cs new file mode 100644 index 0000000..4ed94ac --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppLocation.cs @@ -0,0 +1,162 @@ +using System.Text.Json.Serialization; +using SqlSugar; +using WcsMain.Enum.Location; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// tbl_app_location +/// 点位信息表 +/// +[SugarTable("tbl_app_location")] +public class AppLocation +{ + /// + /// Wcs点位 + /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "wcs_location")] + [JsonPropertyName("wcsLocation")] + public string? WcsLocation { get; set; } + + /// + /// Wms点位,该列主要用于映射 + /// + [SugarColumn(ColumnName = "wms_location")] + [JsonPropertyName("wmsLocation")] + public string? WmsLocation { get; set; } + + /// + /// 巷道编号 + /// + [SugarColumn(ColumnName = "tunnel_no")] + [JsonPropertyName("tunnelNo")] + public int? TunnelNo { get; set; } + + /// + /// 设备编号 + /// + [SugarColumn(ColumnName = "equipment_id")] + [JsonPropertyName("equipmentId")] + public int? EquipmentId { get; set; } + + /// + /// 点位状态 + /// + [SugarColumn(ColumnName = "location_status")] + [JsonPropertyName("locationStatus")] + public int? LocationStatus { get; set; } + + /// + /// 点位类型 + /// + [SugarColumn(ColumnName = "location_type")] + [JsonPropertyName("locationType")] + public int? LocationType { get; set; } + + /// + /// 排 + /// + [SugarColumn(ColumnName = "queue")] + [JsonPropertyName("queue")] + public int? Queue { get; set; } + + /// + /// 列 + /// + [SugarColumn(ColumnName = "line")] + [JsonPropertyName("line")] + public int? Line { get; set; } + + /// + /// 层 + /// + [SugarColumn(ColumnName = "layer")] + [JsonPropertyName("layer")] + public int? Layer { get; set; } + + /// + /// 深 + /// + [SugarColumn(ColumnName = "depth")] + [JsonPropertyName("depth")] + public int? Depth { get; set; } + + /// + /// 干涉的点位 + /// + [SugarColumn(ColumnName = "intervene_location")] + [JsonPropertyName("interveneLocation")] + public string? InterveneLocation { get; set; } + + /// + /// 兼容的载具类型 + /// + [SugarColumn(ColumnName = "vehicle_type")] + [JsonPropertyName("vehicleType")] + public string? VehicleType { get; set; } + + /// + /// 载具编号 + /// + [SugarColumn(ColumnName = "vehicle_no")] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 修改时间 + /// + [SugarColumn(ColumnName = "modify_time")] + [JsonPropertyName("modifyTime")] + public DateTime? ModifyTime { get; set; } + + /// + /// 说明信息 + /// + [SugarColumn(ColumnName = "explain")] + [JsonPropertyName("explain")] + public string? Explain { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + + + + /// + /// 创建一个正在使用的信息实例 + /// + /// + /// + /// + public static AppLocation CreateUsedInstance(string? wcsLocation, string? vehicleNo) + { + return new AppLocation() + { + WcsLocation = wcsLocation, + VehicleNo = vehicleNo, + ModifyTime = DateTime.Now, + LocationStatus = (int)LocationStatusEnum.used + }; + } + + /// + /// 创建一个空货位的实例 + /// + /// + /// + public static AppLocation CreateEmptyInstance(string? wcsLocation) + { + return new AppLocation() + { + WcsLocation = wcsLocation, + VehicleNo = "", + ModifyTime = DateTime.Now, + LocationStatus = (int)LocationStatusEnum.empty + }; + } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppMenu.cs b/WcsMain/DataBase/TableEntity/AppMenu.cs new file mode 100644 index 0000000..7baa86b --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppMenu.cs @@ -0,0 +1,75 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// 菜单表 +/// +[SugarTable("tbl_app_menu")] +public class AppMenu +{ + /// + /// 主菜单序号 + /// + [SugarColumn(ColumnName = "main_menu_index")] + [JsonPropertyName("mainMenuIndex")] + public string? MainMenuIndex { get; set; } + + /// + /// 主菜单名称 + /// + [SugarColumn(ColumnName = "main_menu_name")] + [JsonPropertyName("mainMenuName")] + public string? MainMenuName { get; set; } + + /// + /// 主菜单图标 + /// + [SugarColumn(ColumnName = "main_menu_ico")] + [JsonPropertyName("mainMenuIco")] + public string? MainMenuIco { get; set; } + + /// + /// 次菜单序号 + /// + [SugarColumn(ColumnName = "minor_menu_index", IsPrimaryKey = true)] + [JsonPropertyName("minorMenuIndex")] + public string? MinorMenuIndex { get; set; } + + /// + /// 次菜单名称 + /// + [SugarColumn(ColumnName = "minor_menu_name")] + [JsonPropertyName("minorMenuName")] + public string? MinorMenuName { get; set; } + + /// + /// 次菜单图标 + /// + [SugarColumn(ColumnName = "minor_menu_ico")] + [JsonPropertyName("minorMenuIco")] + public string? MinorMenuIco { get; set; } + + /// + /// 次菜单路由 + /// + [SugarColumn(ColumnName = "minor_menu_router")] + [JsonPropertyName("minorMenuRouter")] + public string? MinorMenuRouter { get; set; } + + /// + /// 菜单状态 ---- 1 表示可以使用 + /// + [SugarColumn(ColumnName = "menu_status")] + [JsonPropertyName("menuStatus")] + public int? MenuStatus { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppPLC.cs b/WcsMain/DataBase/TableEntity/AppPLC.cs new file mode 100644 index 0000000..acb4839 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppPLC.cs @@ -0,0 +1,79 @@ +using System.Text.Json.Serialization; +using PlcTool.Siemens.PLCAttribute; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// t_app_plc 表的实体类 +/// +[SugarTable("tbl_app_plc")] +public class AppPLC +{ + /// + /// PLC 编号 + /// + [SugarColumn(ColumnName = "plc_id", IsPrimaryKey = true)] + [PlcId] + [JsonPropertyName("plcId")] + public int? PLCId { get; set; } + + /// + /// PLC IP 地址 + /// + [SugarColumn(ColumnName = "plc_ip")] + [PlcIP] + [JsonPropertyName("plcIp")] + public string? PLCIp { get; set; } + + + /// + /// PLC 名称 + /// + [SugarColumn(ColumnName = "plc_name")] + [JsonPropertyName("plcName")] + public string? PLCName { get; set; } + + /// + /// PLC 状态,是否启用 + /// + [SugarColumn(ColumnName = "plc_status")] + [JsonPropertyName("plcStatus")] + public int? PLCStatus { get; set; } + + /// + /// PLC 系列 + /// + [SugarColumn(ColumnName = "plc_kind")] + [PlcKind] + [JsonPropertyName("plcKind")] + public string? PLCKind { get; set; } + + /// + /// PLC 插槽 + /// + [SugarColumn(ColumnName = "rack")] + [Rack] + [JsonPropertyName("rack")] + public int? PLCRack { get; set; } + + /// + /// PLC 机架 + /// + [SugarColumn(ColumnName = "slot")] + [Slot] + [JsonPropertyName("slot")] + public int? PLCSlot { get; set; } + + + + /// + /// 备注信息 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppRouterMethod.cs b/WcsMain/DataBase/TableEntity/AppRouterMethod.cs new file mode 100644 index 0000000..7b2cc2e --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppRouterMethod.cs @@ -0,0 +1,31 @@ +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// tbl_app_router_method +/// +[SugarTable("tbl_app_router_method")] +public class AppRouterMethod +{ + /// + /// 请求点 + /// + [SugarColumn(ColumnName = "area")] + public string? Area { get; set; } + + /// + /// 调用的类 + /// + [SugarColumn(ColumnName = "class_name")] + public string? ClassName { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "remark")] + public string? Remark { get; set; } + + + +} diff --git a/WcsMain/DataBase/TableEntity/AppSettings.cs b/WcsMain/DataBase/TableEntity/AppSettings.cs new file mode 100644 index 0000000..6a70a61 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppSettings.cs @@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// 设置表实体类,tbl_app_settings +/// +[SugarTable("tbl_app_settings")] +public class AppSettings +{ + /// + /// 主键名称 + /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "setting_key")] + [JsonPropertyName("settingKey")] + public string? SettingKey { get; set; } + + /// + /// 名称 + /// + [SugarColumn(ColumnName = "setting_name")] + [JsonPropertyName("settingName")] + public string? SettingName { get; set; } + + /// + /// 值 + /// + [SugarColumn(ColumnName = "setting_value")] + [JsonPropertyName("settingValue")] + public string? SettingValue { get; set; } + + /// + /// 是否系统数据 + /// + [SugarColumn(ColumnName = "setting_type")] + [JsonPropertyName("settingType")] + public string? SettingType { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppStacker.cs b/WcsMain/DataBase/TableEntity/AppStacker.cs new file mode 100644 index 0000000..4ce2ae8 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppStacker.cs @@ -0,0 +1,71 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// 设备表 +/// +[SugarTable("tbl_app_stacker")] +public class AppStacker +{ + /// + /// 设备编号 + /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "stacker_id")] + [JsonPropertyName("stackerId")] + public int? StackerId { get; set; } + + /// + /// 堆垛机名称 + /// + [SugarColumn(ColumnName = "stacker_name")] + [JsonPropertyName("stackerName")] + public string? StackerName { get; set; } + + /// + /// 堆垛机状态 + /// + /// + /// 0 - 禁用 + /// 1 - 启用 + /// + [SugarColumn(ColumnName = "stacker_status")] + [JsonPropertyName("stackerStatus")] + public int? StackerStatus { get; set; } + + /// + /// 货叉状态 + /// + [SugarColumn(ColumnName = "fork_status")] + [JsonPropertyName("forkStatus")] + public string? ForkStatus { get; set; } + + /// + /// 控制该堆垛机的PLC + /// + [SugarColumn(ColumnName = "action_plc")] + [JsonPropertyName("actionPlc")] + public int? ActionPlc { get; set; } + + /// + /// 入库站台,格式 : 101-102-103 + /// + [SugarColumn(ColumnName = "in_stand")] + [JsonPropertyName("inStand")] + public string? InStand { get; set; } + + /// + /// 出库站台,格式 : 101-102-103 + /// + [SugarColumn(ColumnName = "out_stand")] + [JsonPropertyName("outStand")] + public string? OutStand { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppTcp.cs b/WcsMain/DataBase/TableEntity/AppTcp.cs new file mode 100644 index 0000000..e2ca33b --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppTcp.cs @@ -0,0 +1,62 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// tbl_app_tcp +/// +[SugarTable("tbl_app_tcp")] +public class AppTcp +{ + /// + /// tcp 编号 + /// + [SugarColumn(ColumnName = "tcp_id", IsPrimaryKey = true)] + [JsonPropertyName("tcpId")] + public int? TcpId { get; set; } + + /// + /// tcp 地址 + /// + [SugarColumn(ColumnName = "tcp_ip")] + [JsonPropertyName("tcpIp")] + public string? TcpIp { get; set; } + + /// + /// tcp端口 + /// + [SugarColumn(ColumnName = "tcp_port")] + [JsonPropertyName("tcpPort")] + public int? TcpPort { get; set; } + + /// + /// tcp类型 + /// + [SugarColumn(ColumnName = "tcp_type")] + [JsonPropertyName("tcptype")] + public int? TcpType { get; set; } + + /// + /// tcp 状态 + /// + [SugarColumn(ColumnName = "tcp_status")] + [JsonPropertyName("tcpStatus")] + public int? TcpStatus { get; set; } + + /// + /// tcp 别称,不能重复 + /// + [SugarColumn(ColumnName = "display_name")] + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } + + + /// + /// 备注信息 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppUser.cs b/WcsMain/DataBase/TableEntity/AppUser.cs new file mode 100644 index 0000000..49b186e --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppUser.cs @@ -0,0 +1,60 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// 用户表 +/// +[SugarTable("tbl_app_user")] +public class AppUser +{ + /// + /// 用户ID + /// + [SugarColumn(ColumnName = "user_id")] + [JsonPropertyName("userId")] + public string? UserId { get; set; } + + /// + /// 用户名称 + /// + [SugarColumn(ColumnName = "user_name")] + [JsonPropertyName("userName")] + public string? UserName { get; set; } + + /// + /// 用户密码 + /// + [SugarColumn(ColumnName = "user_password")] + [JsonPropertyName("userPassword")] + public string? UserPassword { get; set; } + + /// + /// 用户状态 + /// + [SugarColumn(ColumnName = "user_status")] + [JsonPropertyName("userStatus")] + public int? UserStatus { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + [JsonPropertyName("createTime")] + public DateTime? CreateTime { get; set; } + + /// + /// 修改时间 + /// + [SugarColumn(ColumnName = "modify_time")] + [JsonPropertyName("modifyTime")] + public DateTime? ModifyTime { get; set; } + + /// + /// 用户组 + /// + [SugarColumn(ColumnName = "user_group")] + [JsonPropertyName("userGroup")] + public string? UserGroup { get; set; } +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppUserGroup.cs b/WcsMain/DataBase/TableEntity/AppUserGroup.cs new file mode 100644 index 0000000..3de6ce8 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppUserGroup.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +[SugarTable("tbl_app_user_group")] +public class AppUserGroup +{ + /// + /// 用户组ID + /// + [SugarColumn(ColumnName = "group_id", IsPrimaryKey = true)] + [JsonPropertyName("groupId")] + public string? GroupId { get; set; } + + /// + /// 用户组名称 + /// + [SugarColumn(ColumnName = "group_name")] + [JsonPropertyName("groupName")] + public string? GroupName { get; set; } + + /// + /// 用户组状态 + /// + [SugarColumn(ColumnName = "group_status")] + [JsonPropertyName("groupStatus")] + public int? GroupStatus { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppUserRule.cs b/WcsMain/DataBase/TableEntity/AppUserRule.cs new file mode 100644 index 0000000..72d3aa1 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppUserRule.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +[SugarTable("tbl_app_user_rule")] +public class AppUserRule +{ + /// + /// 用户组 + /// + [SugarColumn(ColumnName = "group_id", IsPrimaryKey = true)] + [JsonPropertyName("groupId")] + public string? GroupId { get; set; } + + /// + /// 二级菜单权限 + /// + [SugarColumn(ColumnName = "minor_menu_index")] + [JsonPropertyName("minorMenuIndex")] + public string? MinorMenuIndex { get; set; } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppVehicleBinding.cs b/WcsMain/DataBase/TableEntity/AppVehicleBinding.cs new file mode 100644 index 0000000..e244d3f --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppVehicleBinding.cs @@ -0,0 +1,30 @@ +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// 载具信息和 plcid 绑定 +/// +[SugarTable("tbl_app_vehicle_binding")] +public class AppVehicleBinding +{ + /// + /// plcId + /// + [SugarColumn(ColumnName = "plc_id")] + public int? PlcId { get; set; } + + /// + /// 载具号 + /// + [SugarColumn(ColumnName = "vehicle_no")] + public string? VehicleNo { get; set; } + + /// + /// 绑定时间 + /// + [SugarColumn(ColumnName = "binding_time")] + public DateTime? BindingTime { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppWcsTask.cs b/WcsMain/DataBase/TableEntity/AppWcsTask.cs new file mode 100644 index 0000000..8285659 --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppWcsTask.cs @@ -0,0 +1,167 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// tbl_app_wcs_task +/// Wcs任务表 +/// +[SugarTable("tbl_app_wcs_task")] +public class AppWcsTask +{ + + /// + /// Plc任务号 + /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "plc_id")] + [JsonPropertyName("plcId")] + public int? PlcId { get; set; } + + /// + /// 下一个任务号,若没有下一个任务则此处是默认值 + /// + [SugarColumn(ColumnName = "next_plc_id")] + [JsonPropertyName("nextPlcId")] + public int? NextPlcId { get; set; } + + /// + /// 任务号 + /// + [SugarColumn(ColumnName = "task_id")] + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + + /// + /// 任务类别 + /// + [SugarColumn(ColumnName = "task_category")] + [JsonPropertyName("taskCategory")] + public int? TaskCategory { get; set; } + + + /// + /// 任务类型 + /// + [SugarColumn(ColumnName = "task_type")] + [JsonPropertyName("taskType")] + public int? TaskType { get; set; } + + /// + /// 任务排序 + /// 999表示最后一个任务 + /// + [SugarColumn(ColumnName = "task_sort")] + [JsonPropertyName("taskSort")] + public int? TaskSort { get; set; } + + /// + /// 任务状态 + /// + [SugarColumn(ColumnName = "task_status")] + [JsonPropertyName("taskStatus")] + public int? TaskStatus { get; set; } + + /// + /// 任务优先级 + /// + [SugarColumn(ColumnName = "priority")] + [JsonPropertyName("priority")] + public int? Priority { get; set; } + + /// + /// 任务起点 + /// + [SugarColumn(ColumnName = "origin")] + [JsonPropertyName("origin")] + public string? Origin { get; set; } + + /// + /// 任务终点 + /// + [SugarColumn(ColumnName = "destination")] + [JsonPropertyName("destination")] + public string? Destination { get; set; } + + + /// + /// Wms创建时间 + /// + [SugarColumn(ColumnName = "wms_time")] + [JsonPropertyName("wmsTime")] + public DateTime? WmsTime { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + [JsonPropertyName("createTime")] + public DateTime? CreateTime { get; set; } + + /// + /// 写入PLC的载具编号 + /// + [SugarColumn(ColumnName = "plc_vehicle_no")] + [JsonPropertyName("plcVehicleNo")] + public int? PlcVehicleNo { get; set; } + + /// + /// 载具编号 + /// + [SugarColumn(ColumnName = "vehicle_no")] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 载具尺寸 + /// + [SugarColumn(ColumnName = "vehicle_size")] + [JsonPropertyName("vehicleSize")] + public int? VehicleSize { get; set; } + + /// + /// 重量 + /// + [SugarColumn(ColumnName = "weight")] + [JsonPropertyName("weight")] + public decimal? Weight { get; set; } + + + /// + /// 任务创建人 + /// + [SugarColumn(ColumnName = "create_person")] + [JsonPropertyName("createPerson")] + public string? CreatePerson { get; set; } + + + /// + /// 开始时间 + /// + [SugarColumn(ColumnName = "start_time")] + [JsonPropertyName("startTime")] + public DateTime? StartTime { get; set; } + + /// + /// 完成时间 + /// + [SugarColumn(ColumnName = "complete_time")] + [JsonPropertyName("completeTime")] + public DateTime? CompleteTime { get; set; } + + /// + /// 备注信息 + /// + [SugarColumn(ColumnName = "remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + + + + public override string ToString() + { + return $"任务号:{TaskId},Plc任务号:{PlcId}"; + } + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/AppWmsTask.cs b/WcsMain/DataBase/TableEntity/AppWmsTask.cs new file mode 100644 index 0000000..13ae70e --- /dev/null +++ b/WcsMain/DataBase/TableEntity/AppWmsTask.cs @@ -0,0 +1,128 @@ +using System.Text.Json.Serialization; +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// tbl_app_wms_task +/// wms任务表 +/// +[SugarTable("tbl_app_wms_task")] +public class AppWmsTask +{ + /// + /// Wms任务号 + /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "task_id")] + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } + + /// + /// 任务类型 + /// + [SugarColumn(ColumnName = "task_type")] + [JsonPropertyName("taskType")] + public int? TaskType { get; set; } + + /// + /// 任务状态 + /// + [SugarColumn(ColumnName = "task_status")] + [JsonPropertyName("taskStatus")] + public int? TaskStatus { get; set; } + + + /// + /// 任务优先级 + /// + [SugarColumn(ColumnName = "priority")] + [JsonPropertyName("priority")] + public int? Priority { get; set; } + + /// + /// 任务起点 + /// + [SugarColumn(ColumnName = "origin")] + [JsonPropertyName("origin")] + public string? Origin { get; set; } + + /// + /// 任务起点 + /// + [SugarColumn(ColumnName = "midpoint")] + [JsonPropertyName("midPoint")] + public string? MidPoint { get; set; } + + /// + /// 任务目的地 + /// + [SugarColumn(ColumnName = "destination")] + [JsonPropertyName("destination")] + public string? Destination { get; set; } + + /// + /// 载具编号 + /// + [SugarColumn(ColumnName = "vehicle_no")] + [JsonPropertyName("vehicleNo")] + public string? VehicleNo { get; set; } + + /// + /// 载具尺寸 + /// + [SugarColumn(ColumnName = "vehicle_size")] + [JsonPropertyName("vehicleSize")] + public int? VehicleSize { get; set; } + + /// + /// 重量 + /// + [SugarColumn(ColumnName = "weight")] + [JsonPropertyName("weight")] + public decimal? Weight { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + [JsonPropertyName("createTime")] + public DateTime? CreateTime { get; set; } + + /// + /// 修改时间 + /// + [SugarColumn(ColumnName = "modify_time")] + [JsonPropertyName("modifyTime")] + public DateTime? ModifyTime { get; set; } + + /// + /// 任务开始时间 + /// + [SugarColumn(ColumnName = "start_time")] + [JsonPropertyName("startTime")] + public DateTime? StartTime { get; set; } + + + /// + /// 任务结束时间 + /// + [SugarColumn(ColumnName = "end_time")] + [JsonPropertyName("endTime")] + public DateTime? EndTime { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_person")] + [JsonPropertyName("createPerson")] + public string? CreatePerson { get; set; } + + /// + /// 错误信息 + /// + [SugarColumn(ColumnName = "task_msg")] + [JsonPropertyName("taskMsg")] + public string? TaskMsg { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/DataBase/TableEntity/BsPickStand.cs b/WcsMain/DataBase/TableEntity/BsPickStand.cs new file mode 100644 index 0000000..11ab4ca --- /dev/null +++ b/WcsMain/DataBase/TableEntity/BsPickStand.cs @@ -0,0 +1,30 @@ +using SqlSugar; + +namespace WcsMain.DataBase.TableEntity; + +/// +/// 拣选站台信息 +/// +[SugarTable("tbl_bs_pick_stand")] +public class BsPickStand +{ + /// + /// 拣选站台名称 + /// + [SugarColumn(ColumnName = "pick_stand")] + public string? PickStand { get; set; } + + /// + /// 拣选站台类型 + /// + [SugarColumn(ColumnName = "stand_type")] + public int? StandType { get; set; } + + /// + /// 站台状态 + /// + [SugarColumn(ColumnName = "stand_status")] + public int? StandStatus { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/DataService/DataBaseData.cs b/WcsMain/DataService/DataBaseData.cs new file mode 100644 index 0000000..eb88f11 --- /dev/null +++ b/WcsMain/DataService/DataBaseData.cs @@ -0,0 +1,171 @@ +using System.Text.RegularExpressions; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.DataService; + +/// +/// +/// +[Component] +public class DataBaseData(AppSettingsDao settingsDao) +{ + private AppSettingsDao _settingsDao = settingsDao; + + /// + /// 获取一个设置值 + /// + /// + /// + public string? GetSetting(string settingKey) + { + List? settings = _settingsDao.GetSetting(settingKey); + if (settings == default || settings.Count < 1) + { + return string.Empty; + } + return settings[0].SettingValue; + } + + + private static readonly object getNewPlcTaskIdLock = new(); + /// + /// 获取新的 PLC 任务号,发生异常则返回默认值 + /// + /// + /// + public int[]? GetNewPlcTaskId(int count) + { + lock (getNewPlcTaskIdLock) + { + string? oldPlcTaskIdstr = GetSetting("plc_task_id"); + if (string.IsNullOrEmpty(oldPlcTaskIdstr)) { return default; } + if (!Regex.IsMatch(oldPlcTaskIdstr, "^\\d+$")) { return default; } + int oldPlcTaskId = Convert.ToInt32(oldPlcTaskIdstr); + if (oldPlcTaskId > 2000000000) + { + oldPlcTaskId = 100000; + } + oldPlcTaskId++; + int[] ints = new int[count]; + for (int i = 0; i < count; i++) + { + ints[i] = oldPlcTaskId + i; + } + int updateNewTaskPlcId = _settingsDao.Update(new AppSettings() { SettingKey = "plc_task_id", SettingValue = ints.Last().ToString() }); + if (updateNewTaskPlcId < 0) { return default; } + return ints; + } + } + /// + /// 获取一个PldId + /// + /// + public int? GetNewPlcTaskId() + { + var ints = GetNewPlcTaskId(1); + if (ints == default || ints.Length < 1) { return default; } + return ints.First(); + } + + #region 返回一个 UUID,以时间 yyyyMMdd 形式 + + private static readonly object getNewUUIDLock = new(); + private string lastUUID = string.Empty; + private string lasTimeTick = DateTime.Now.ToString("yyyyMMddHHmmssfff"); + private ushort sortUUID = 0; + /// + /// 返回一个唯一识别号 + /// + /// + public string GetNewUUID() + { + lock (getNewUUIDLock) + { + while (true) + { + string? wcsId = CommonData.AppConfig.WcsId; + if (string.IsNullOrEmpty(wcsId)) + { + wcsId = "0"; + } + string timeTick = DateTime.Now.ToString("yyyyMMddHHmmssfff"); + if (timeTick != lasTimeTick) + { + lasTimeTick = timeTick; + sortUUID = 0; + } + string idNo = wcsId.PadLeft(4, '0'); + string sort = sortUUID.ToString().PadLeft(5, '0'); + string newUUID = $"{timeTick}{idNo}{sort}"; + sortUUID++; + if (sortUUID > 60000) + { + sortUUID = 0; + } + if (newUUID != lastUUID) + { + lastUUID = newUUID; + return newUUID; + } + } + } + } + + + #endregion + + + #region 返回一个 UUID,以时间戳形式 + + private static readonly object getNewUUIDLock2 = new(); + private string lastUUID2 = string.Empty; + private string lasTimeTick2 = DateTime.Now.ToString("yyyyMMddHHmmssfff"); + private ushort sortUUID2 = 0; + /// + /// 返回一个唯一识别号 以时间戳为基础 + /// + /// + /// + /// 这方法产生的ID会短一点,但是单位时间内产生的数量较少 + /// + public string GetNewUUID2() + { + lock (getNewUUIDLock2) + { + while (true) + { + string? wcsId = CommonData.AppConfig.WcsId; + if (string.IsNullOrEmpty(wcsId)) + { + wcsId = "0"; + } + string timeTick = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); + if (timeTick != lasTimeTick2) + { + lasTimeTick2 = timeTick; + sortUUID2 = 0; + } + string idNo = wcsId.PadLeft(3, '0'); + string sort = sortUUID2.ToString().PadLeft(3, '0'); + string newUUID = $"{timeTick}{idNo}{sort}"; + sortUUID2++; + if (sortUUID2 > 900) + { + sortUUID2 = 0; + } + if (newUUID != lastUUID2) + { + lastUUID2 = newUUID; + return newUUID; + } + } + } + } + + #endregion + + +} \ No newline at end of file diff --git a/WcsMain/DataService/EnumData.cs b/WcsMain/DataService/EnumData.cs new file mode 100644 index 0000000..e25f580 --- /dev/null +++ b/WcsMain/DataService/EnumData.cs @@ -0,0 +1,20 @@ +using WcsMain.Enum.TaskEnum; + +namespace WcsMain.DataService; + +/// +/// 枚举的数据操作 +/// +public static class EnumData +{ + /// + /// 返回可以视作正在运行的任务的Wcs任务状态的枚举 + /// + /// + public static int[] GetWcsTaskStatusEnumRunningStatus() + { + return [(int)WcsTaskStatusEnum.leaveOrigin, (int)WcsTaskStatusEnum.running]; + } + + +} diff --git a/WcsMain/ElTag/Atop/AtopEnum/AtopSubCommandEnum.cs b/WcsMain/ElTag/Atop/AtopEnum/AtopSubCommandEnum.cs new file mode 100644 index 0000000..07ffea0 --- /dev/null +++ b/WcsMain/ElTag/Atop/AtopEnum/AtopSubCommandEnum.cs @@ -0,0 +1,11 @@ +namespace WcsMain.ElTag.Atop.AtopEnum; + +public enum AtopSubCommandEnum +{ + Confirm = 6, // 确认 + StockOut = 7, // 缺货 + TimeOut = 10, // 超时 + ErrInstruct = 12, // 指令非法 + Stuck = 13, // 卡键 ---- 按键按下未弹起 + Others = 100, // 其他,灯不亮 +} diff --git a/WcsMain/ElTag/Atop/AtopEnum/BuzzerType.cs b/WcsMain/ElTag/Atop/AtopEnum/BuzzerType.cs new file mode 100644 index 0000000..c8db8e1 --- /dev/null +++ b/WcsMain/ElTag/Atop/AtopEnum/BuzzerType.cs @@ -0,0 +1,14 @@ +namespace WcsMain.ElTag.Atop.AtopEnum; + +/// +/// 蜂鸣器类型 +/// +public enum BuzzerType +{ + OFF = 0, + ON = 1, + _2sec = 2, + _1sec = 3, + _0_5_sec = 4, + _0_25_sec = 5 +} diff --git a/WcsMain/ElTag/Atop/AtopEnum/LedColor.cs b/WcsMain/ElTag/Atop/AtopEnum/LedColor.cs new file mode 100644 index 0000000..b01e1ce --- /dev/null +++ b/WcsMain/ElTag/Atop/AtopEnum/LedColor.cs @@ -0,0 +1,14 @@ +namespace WcsMain.ElTag.Atop.AtopEnum; + +/// +/// 电子标签灯的颜色 +/// +public enum LedColor +{ + Red = 0, + Green = 1, + Orange = 2, + Blue = 3, + Pink = 4, + Cyan = 5, +} diff --git a/WcsMain/ElTag/Atop/AtopEnum/LedStatus.cs b/WcsMain/ElTag/Atop/AtopEnum/LedStatus.cs new file mode 100644 index 0000000..41a7155 --- /dev/null +++ b/WcsMain/ElTag/Atop/AtopEnum/LedStatus.cs @@ -0,0 +1,14 @@ +namespace WcsMain.ElTag.Atop.AtopEnum; + +/// +/// 灯的状态 +/// +public enum LedStatus +{ + LEDOff = 0, + LEDOn = 1, + _2secblinking = 2, + _1secblinking = 3, + _0_5secblinking = 4, + _0_25secblinking = 5 +} diff --git a/WcsMain/ElTag/Atop/AtopEnum/TagButtons.cs b/WcsMain/ElTag/Atop/AtopEnum/TagButtons.cs new file mode 100644 index 0000000..f300859 --- /dev/null +++ b/WcsMain/ElTag/Atop/AtopEnum/TagButtons.cs @@ -0,0 +1,11 @@ +namespace WcsMain.ElTag.Atop.AtopEnum; + +/// +/// 标签按钮 +/// +public enum TagButtons +{ + ConfirmKey = 1, + ShortageKey = 2, + AllKey = 3 +} diff --git a/WcsMain/ElTag/Atop/AtopEnum/TagMode.cs b/WcsMain/ElTag/Atop/AtopEnum/TagMode.cs new file mode 100644 index 0000000..121f122 --- /dev/null +++ b/WcsMain/ElTag/Atop/AtopEnum/TagMode.cs @@ -0,0 +1,10 @@ +namespace WcsMain.ElTag.Atop.AtopEnum; + +/// +/// 标签的模式 +/// +public enum TagMode +{ + Picking = 0, + Stock = 1 +} diff --git a/WcsMain/ElTag/Atop/BaseOprDataHandler.cs b/WcsMain/ElTag/Atop/BaseOprDataHandler.cs new file mode 100644 index 0000000..90dc721 --- /dev/null +++ b/WcsMain/ElTag/Atop/BaseOprDataHandler.cs @@ -0,0 +1,109 @@ +using System.Text.RegularExpressions; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.ElTag.Atop.AtopEnum; +using WcsMain.ElTag.Atop.Entity; +using WcsMain.Enum.TaskEnum; +using WcsMain.EquipOperation.ElTag; +using WcsMain.ExtendMethod; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ElTag.Atop; + +/// +/// 电子标签收到数据的数据处理类 +/// +[Component] +public class BaseOprDataHandler(AppElTagBaseDao tagBaseDao, AppElTagTaskDao tagTaskDao, AtopOperation atopOperation) +{ + + private readonly AppElTagBaseDao _tagBaseDao = tagBaseDao; + private readonly AppElTagTaskDao _tagTaskDao = tagTaskDao; + private readonly AtopOperation _atopOperation = atopOperation; + + /// + /// 收到数据时的处理方法 + /// + /// + public void GetMsg(TagReturnInfo tcpServe) + { + Task.Factory.StartNew(() => + { + switch (tcpServe.SubCommand) + { + case (int)AtopSubCommandEnum.Confirm: // 确认 + Confirm(tcpServe); + break; + case (int)AtopSubCommandEnum.StockOut: // 缺货 + goto case (int)AtopSubCommandEnum.Confirm; + case (int)AtopSubCommandEnum.TimeOut: // 超时 + ConsoleLog.Exception($"【异常】电子标签反馈超时,控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + break; + case (int)AtopSubCommandEnum.ErrInstruct: // 指令非法 + ConsoleLog.Warning($"【警告】电子标签反馈指令非法,控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + break; + case (int)AtopSubCommandEnum.Stuck: // 卡键 + ConsoleLog.Warning($"【警告】电子标签反馈标签卡键,控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + break; + } + }); + } + + + + /// + /// 按下确认键 + /// + /// + public void Confirm(TagReturnInfo tcpServe) + { + // _atopOperation.ShowMsgOnly(tcpServe.ControllerDisplayName, tcpServe.TagId, tcpServe.Data); // 重新点亮标签,仅显示字符 + /* 找出这个标签最新点亮的那条记录 */ + List? eltags = _tagBaseDao.Query(new AppElTagBase { ControllerDisplayName = tcpServe.ControllerDisplayName, TagId = tcpServe.TagId }); + if(eltags == default) + { + ConsoleLog.Exception($"【异常】电子标签确认查询数据库标签信息失败,与数据库服务器连连接中断,控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + _atopOperation.ShowMsg(tcpServe.ControllerDisplayName, tcpServe.TagId, tcpServe.Data); // 重新点亮标签 + return; + } + if(eltags.Count < 1) + { + ConsoleLog.Warning($"【异常】电子标签确认按钮按下,该标签不在基础资料表,控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + return; + } + /* 取最新一条记录 */ + eltags = [.. eltags.OrderByDescending(el => el.LastLightTime)]; + var eltag = eltags[0]; + string? taskId = eltag.TaskId; // 获取任务号 + if(string.IsNullOrEmpty(taskId) ) + { + ConsoleLog.Warning($"【异常】电子标签确认按钮按下,该标签没有绑定的显示任务,控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + return; + } + /* 获取该任务号对应的任务 */ + List? tagTasks = _tagTaskDao.Query(new AppElTagTask { TaskId = taskId }); + if(tagTasks == default) + { + ConsoleLog.Exception($"【异常】电子标签确认查询数据库任务失败,与数据库服务器连连接中断,控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + _atopOperation.ShowMsg(tcpServe.ControllerDisplayName, tcpServe.TagId, tcpServe.Data); // 重新点亮标签 + return; + } + if (tagTasks.Count < 1) + { + ConsoleLog.Warning($"【异常】电子标签确认按钮按下,该标签对应的任务:{taskId} 不存在,控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + return; + } + var tagTask = tagTasks[0]; // 获取到的任务 + int pickNum = Regex.Replace(tcpServe.Data!, "\\D", "").IsNumber() ? Convert.ToInt32(tcpServe.Data) : -1; + /* 更新任务状态为确认 */ + _tagTaskDao.Update(new AppElTagTask() { TaskId = tagTask.TaskId, ConfirmTime = DateTime.Now, TaskStatus = (int)ElTagTaskStatusEnum.Confirm, PickNum = pickNum, Remark = "标签确认", OffTime = DateTime.Now }); + /* 更新绑定的库位信息为空 */ + _tagBaseDao.ResetLocation(eltag.Location!); + ConsoleLog.Success($"电子标签确认按钮按下,标签任务号:{tagTask.TaskId} 已经确认,箱号:{tagTask.VehicleNo},拣选数量:{tagTask.NeedNum}({pickNum}) 控制器:{tcpServe.ControllerDisplayName},标签号:{tcpServe.TagId},数据:{tcpServe.Data}"); + /* 发送WMS电子标签任务完成 */ + // [TODO] + } + + +} diff --git a/WcsMain/ElTag/Atop/ConnectOprServe.cs b/WcsMain/ElTag/Atop/ConnectOprServe.cs new file mode 100644 index 0000000..d6847a8 --- /dev/null +++ b/WcsMain/ElTag/Atop/ConnectOprServe.cs @@ -0,0 +1,196 @@ +using LedSimple; +using System.Xml.Linq; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.ElTag.Atop.AtopEnum; +using WcsMain.ElTag.Atop.Entity; +using WcsMain.Enum.General; +using WcsMain.Enum.TaskEnum; +using WcsMain.Enum.Tcp; +using WcsMain.EquipOperation.ElTag; +using WcsMain.Tcp.Entity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ElTag.Atop; + +/// +/// 连接并接收电子标签信息 +/// +/// +[Component] +public class ConnectOprServe(AppTcpDao tcpDao, BaseOprDataHandler baseOprDataHandler, AtopOperation atopOperation, AppElTagTaskDao tagTaskDao) +{ + private readonly AppTcpDao _tcpDao = tcpDao; + private readonly BaseOprDataHandler _baseOprDataHandler = baseOprDataHandler; + private readonly AtopOperation _atopOperation = atopOperation; + private readonly AppElTagTaskDao _tagTaskDao = tagTaskDao; + + /// + /// 设置基本参数 + /// + public void SetBaseAction() + { + CommonTool.OprTcpClient = new OprTcpClient(); + CommonTool.OprTcpClient.SetConnecting((tcpData) => ConsoleLog.Info($"电子标签:{tcpData} 正在连接")); + CommonTool.OprTcpClient.SetConnectFailAction((tcpData, ex) => ConsoleLog.Warning($"电子标签:{tcpData} 连接失败,参考信息:{ex.Message}")); + CommonTool.OprTcpClient.SetConnectSuccess(ConnectSuccess); + CommonTool.OprTcpClient.SetConnectOffline((tcpData) => ConsoleLog.Warning($"电子标签:{tcpData} 失去连接")); + CommonTool.OprTcpClient.SetGetData(GetData); + } + + /// + /// 连接电子标签 + /// + public void Connect() + { + Task.Factory.StartNew(() => + { + List? tcps = default; + while (true) + { + tcps = _tcpDao.Query(new AppTcp { TcpStatus = (int)TrueFalseEnum.TRUE, TcpType = (int)TcpType.Opr }); + if (tcps != default) break; + Thread.Sleep(5000); + continue; + } + if (tcps.Count == 0) return; + CommonTool.OprTcpClient.SetBaseTcpServe(tcps); + CommonTool.OprTcpClient.Connect(); + }, TaskCreationOptions.LongRunning); + } + + /// + /// 连接成功的事件 ---- 重置标签 + /// + /// + public void ConnectSuccess(TcpServeConnectionData tcpServe) + { + ConsoleLog.Success($"电子标签:{tcpServe.TcpServe!.TcpIp} 连接成功"); + _atopOperation.BlinkingAll(tcpServe); + /* 点亮数据库中正在点亮的标签 */ + List? tasks = _tagTaskDao.Query(new AppElTagTask { TaskStatus = (int)ElTagTaskStatusEnum.Lighting }); + if (tasks == default) + { + ConsoleLog.Exception("【异常】查询 电子标签 任务失败,数据库连接异常"); + Thread.Sleep(5000); + return; + } + tasks.ForEach(LightTask); + } + + /// + /// 执行点亮标签任务 + /// + /// + public void LightTask(AppElTagTask tagTask) + { + if (CommonData.AppElTags == default || CommonData.AppElTags.Count < 1) return; + var tagInfo = CommonData.AppElTags.Find(f => f.Location == tagTask.Location); + if (tagInfo == default) return; + /* 点亮标签 */ + var resultException = _atopOperation.ShowMsg(tagInfo.ControllerDisplayName, tagInfo.TagId, tagTask.NeedNum); + if (resultException == default) + { + ConsoleLog.Success($"启动重复点亮电子标签成功,点位:{tagTask.Location},数据:{tagTask.NeedNum},载具号:{tagTask.VehicleNo},标签号:{tagInfo.TagId}"); + _tagTaskDao.Update(new AppElTagTask + { + TaskId = tagTask.TaskId, + TaskStatus = (int)ElTagTaskStatusEnum.Lighting, + LightTime = DateTime.Now, + Remark = "重启点亮" + }); // 更新任务状态 + return; + } + ConsoleLog.Warning($"【警告】启动重复点亮电子标签失败,点位:{tagTask.Location},数据:{tagTask.NeedNum},载具号:{tagTask.VehicleNo},标签号:{tagInfo.TagId},异常信息:{resultException.Message}"); + } + + + + + /// + /// 收到数据处理方法 + /// + /// + /// + public void GetData(TcpServeConnectionData tcpServe, byte[] datas) + { + TagReturnInfo returnInfo = new() + { + ControllerDisplayName = tcpServe.DisplayName, + Ip = tcpServe.TcpServe!.TcpIp, + Port = Convert.ToInt32(datas[2]) == 0x60 ? 1 : 2, + SubCommand = Convert.ToInt32(datas[6]), + TagId = datas.Length > 7 ? Convert.ToInt32(datas[7]) : 0 + }; + if (returnInfo.SubCommand == 6 || returnInfo.SubCommand == 7) + { + if (datas[8] >= 160) returnInfo.SubCommand = 8; + + List returnData = []; + for (int i = 8; i < datas.Length; i++) + { + if (i == 8 && datas[8] >= 160) + { + returnData.Add(datas[8] == 160 ? Convert.ToString(Convert.ToChar(32)) : Convert.ToString(Convert.ToChar(datas[8] - 128))); + continue; + } + if (datas[i] > 0) returnData.Add(Convert.ToString(Convert.ToChar(datas[i]))); + } + returnInfo.Data = string.Join("", returnData); + } + /*-------------------------------------------------------------------*/ + if (returnInfo.SubCommand == 9) + { + string p_str = ""; + byte[] ccb_data = new byte[250]; + for (int k = 8; k < datas.Length && k < 258; k++) + { + ccb_data[k - 8] = datas[k]; + } + int maxid = DiagResult(ccb_data, ref p_str); + returnInfo.Data = p_str; + if (string.IsNullOrEmpty(returnInfo.Data.Trim())) + { + returnInfo.Data = string.Empty; + } + } + ConsoleLog.Info($"电子标签获取返回值:{returnInfo}"); + _baseOprDataHandler.GetMsg(returnInfo); + } + + + private int DiagResult(byte[] ccb_data, ref string tagstr) + { + int k, tmp, maxid; + tagstr = ""; + tmp = 0; + maxid = 0; + for (k = 1; k <= 250; k++) + { + if ((k - 1) % 8 == 0) + { + tmp = ccb_data[3 + (k - 1) / 8]; + } + if (tmp % 2 != 1) + { + maxid = k; + tagstr += "1"; + } + else + { + tagstr += "0"; + } + tmp /= 2; + } + tagstr = tagstr[..maxid]; + return maxid; + } + + + + + + + +} diff --git a/WcsMain/ElTag/Atop/Entity/TagReturnInfo.cs b/WcsMain/ElTag/Atop/Entity/TagReturnInfo.cs new file mode 100644 index 0000000..03a42c8 --- /dev/null +++ b/WcsMain/ElTag/Atop/Entity/TagReturnInfo.cs @@ -0,0 +1,64 @@ +namespace WcsMain.ElTag.Atop.Entity; + +public class TagReturnInfo +{ + /// + /// 控制器别称 + /// + public string? ControllerDisplayName { get; set; } + + /// + /// 按下的按键类型 + /// + /// + /// Data(1)= 16H “confirmatin key “ pressed + /// 43H “shotage button/down-count button” pressed + /// 25H “function button/up-count button” pressed. + /// + public int KeyType { get; set; } + + /// + /// 消息类型 + /// + /// + /// 01H:Tag busy,04H 功能键 + /// + public int MsgType { get; set; } + + /// + /// 提交信息 + /// + /// + /// 13卡键 + /// 10通讯超时 + /// + public int SubCommand { get; set; } + + /// + /// IP 地址 + /// + public string? Ip { get; set; } + + + /// + /// 标签 ID + /// + public int TagId { get; set; } + + /// + /// 传回消息的端口 ---- 注意不是控制器端口 + /// + public int Port { get; set; } + + /// + /// 返回的数据 + /// + public string? Data { get; set; } + + + + public override string ToString() + { + return $"控制器别称:{ControllerDisplayName},标签Id:{TagId},消息端口:{Port},提交方式:{SubCommand},按钮类型:{KeyType},提交数据:{Data}"; + } +} diff --git a/WcsMain/ElTag/Atop/Entity/TagSendInfo.cs b/WcsMain/ElTag/Atop/Entity/TagSendInfo.cs new file mode 100644 index 0000000..6bbba47 --- /dev/null +++ b/WcsMain/ElTag/Atop/Entity/TagSendInfo.cs @@ -0,0 +1,228 @@ +using System.Drawing; +using WcsMain.ElTag.Atop.AtopEnum; + +namespace WcsMain.ElTag.Atop.Entity; + +/// +/// 发送到电子标签的信息 +/// +public class TagSendInfo +{ + /// + /// 标签自检 + /// + /// + public static byte[] CheckAllTag() + { + byte[] data = new byte[7]; + data[0] = 7; + data[1] = 0; + data[2] = 0x60; + data[6] = 0x09; + return data; + } + + /// + /// 设置标签模式 + /// + /// + /// + /// + public static byte[] SetTagModel(byte tagId, TagMode tagMode) + { + byte[] data = new byte[8]; + data[0] = 8; + data[1] = 0; + data[2] = 0x60; + data[6] = tagMode == TagMode.Stock ? (byte)0x19 : (byte)0x1A; + data[7] = tagId; + return data; + } + + + /// + /// 展示标签的ID + /// + /// + /// + public static byte[] ShowTagId(byte tagId) + { + byte[] data = new byte[8]; + data[0] = 8; + data[1] = 0; + data[2] = 0x60; + data[6] = 0x13; + data[7] = tagId; + return data; + } + + /// + /// 标签重启 + /// + /// + /// + public static byte[] RestartTag(byte tagId) + { + byte[] data = new byte[8]; + data[0] = 8; + data[1] = 0; + data[2] = 0x60; + data[6] = 0x14; + data[7] = tagId; + return data; + } + + /// + /// 关闭标签 + /// + /// + /// + public static byte[] TurnOffTag(byte tagId) + { + byte[] data = new byte[8]; + data[0] = 8; + data[1] = 0; + data[2] = 0x60; + data[6] = 1; + data[7] = tagId; + return data; + } + + + + /// + /// 返回一个设置标签按钮灯颜色的报文 + /// + /// 标签的ID + /// 颜色枚举值 + /// + public static byte[] LedColor(byte tagId, LedColor ledColor = AtopEnum.LedColor.Green) + { + byte[] data = new byte[10]; + data[0] = 0x0A; + data[1] = 0; + data[2] = 0x60; + data[6] = 0x1F; + data[7] = tagId; + data[8] = 0; + data[9] = Convert.ToByte(ledColor); + return data; + } + + /// + /// 返回一个设置按钮灯闪烁频率的报文 + /// + /// 标签的编号 + /// 闪烁频率 + /// + public static byte[] LedStatus(byte tagId, LedStatus ledStatus = AtopEnum.LedStatus._0_5secblinking) + { + byte[] data = new byte[10]; + data[0] = 0x0A; + data[1] = 0; + data[2] = 0x60; + data[6] = 0x1F; + data[7] = tagId; + data[8] = 4; + data[9] = Convert.ToByte(ledStatus); + return data; + } + + /// + /// 使电子标签按钮按下后保持数字显示 ---- 需要手动熄灭 + /// + /// + /// + /// + /// 该命令会导致按钮失效 + /// + public static byte[] SetValueKeepAlive(byte tagId) + { + byte[] data = new byte[8]; + data[0] = 8; + data[1] = 0; + data[2] = 0x60; + + data[6] = 3; + data[7] = tagId; + return data; + } + + + + /// + /// 展示字符串 + /// + /// + /// + /// + public static byte[] ShowValue(byte tagId, int? value) + { + string showStr; + if (value == default || value > 999) + { + showStr = "MAX"; + } + else + { + showStr = value!.ToString()!; + } + if(showStr!.Length < 6) + { + showStr = showStr.PadLeft(6, ' '); + } + else + { + showStr = showStr.Substring(showStr.Length - 6, 6); + } + byte[] data = new byte[15]; + data[0] = 0x0f; + data[1] = 0; + data[2] = 0x60; + + data[6] = 0x00; + data[7] = tagId; + for (int i = 0; i < showStr.Length; i++) + { + data[8 + i] = Convert.ToByte(showStr[i]); + } + data[14] = 0x00; + return data; + } + + /// + /// 展示字符串 + /// + /// + /// + /// + public static byte[] ShowValue(byte tagId, string? value) + { + string showStr = value ?? ""; + if (string.IsNullOrEmpty(value)) + { + showStr = "ERR"; + } + if (showStr!.Length < 6) + { + showStr = showStr.PadLeft(6, ' '); + } + else + { + showStr = showStr.Substring(showStr.Length - 6, 6); + } + byte[] data = new byte[15]; + data[0] = 0x0f; + data[1] = 0; + data[2] = 0x60; + + data[6] = 0x00; + data[7] = tagId; + for (int i = 0; i < showStr.Length; i++) + { + data[8 + i] = Convert.ToByte(showStr[i]); + } + data[14] = 0x00; + return data; + } +} diff --git a/WcsMain/ElTag/Atop/OprTcpClient.cs b/WcsMain/ElTag/Atop/OprTcpClient.cs new file mode 100644 index 0000000..3266918 --- /dev/null +++ b/WcsMain/ElTag/Atop/OprTcpClient.cs @@ -0,0 +1,16 @@ +using SocketTool; +using System.Text; +using WcsMain.ElTag.Atop.Entity; +using WcsMain.Tcp.Client; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.ElTag.Atop; + +/// +/// 电子标签主控制类 +/// +public class OprTcpClient : BaseTcpClient +{ + + +} diff --git a/WcsMain/Enum/ApiServer/ApiResponseCodeEnum.cs b/WcsMain/Enum/ApiServer/ApiResponseCodeEnum.cs new file mode 100644 index 0000000..a14769d --- /dev/null +++ b/WcsMain/Enum/ApiServer/ApiResponseCodeEnum.cs @@ -0,0 +1,18 @@ +namespace WcsMain.Enum.ApiServer; + +/// +/// WMS 接口返回代码 +/// +public enum ApiResponseCodeEnum +{ + success = 0, // 正常响应 + fail = 1, // 正常返回失败 + dataRepetition = 200, // 数据重复 + undefinedErr = 999, // 未知异常 + requestDataErr = 998, // 请求的数据格式错误 + dataBaseErr = 997, // 数据库操作异常 + + serviceErr = 9999, // 服务器异常,无法处理数据 + + +} \ No newline at end of file diff --git a/WcsMain/Enum/FucResultEnum.cs b/WcsMain/Enum/FucResultEnum.cs new file mode 100644 index 0000000..466a327 --- /dev/null +++ b/WcsMain/Enum/FucResultEnum.cs @@ -0,0 +1,11 @@ +namespace WcsMain.Enum; + +/// +/// 动作执行的结果 +/// +public enum FucResultEnum +{ + offline, + success, + fail, +} \ No newline at end of file diff --git a/WcsMain/Enum/General/TrueFalseEnum.cs b/WcsMain/Enum/General/TrueFalseEnum.cs new file mode 100644 index 0000000..186a762 --- /dev/null +++ b/WcsMain/Enum/General/TrueFalseEnum.cs @@ -0,0 +1,10 @@ +namespace WcsMain.Enum.General; + +/// +/// 通用枚举,0 1表示true or false +/// +public enum TrueFalseEnum +{ + FALSE = 0, + TRUE = 1, +} diff --git a/WcsMain/Enum/Location/LocationStatusEnum.cs b/WcsMain/Enum/Location/LocationStatusEnum.cs new file mode 100644 index 0000000..f18dd5f --- /dev/null +++ b/WcsMain/Enum/Location/LocationStatusEnum.cs @@ -0,0 +1,13 @@ +namespace WcsMain.Enum.Location; + +/// +/// 库位状态枚举 +/// +public enum LocationStatusEnum +{ + empty = 0, // 空闲 + used = 2, // 占用中,有库存 + locked = 1, // 锁定 + forbidden = 9, // 禁用 + special = 999, // 特殊点位 +} \ No newline at end of file diff --git a/WcsMain/Enum/Plc/PickTaskRouterEnum.cs b/WcsMain/Enum/Plc/PickTaskRouterEnum.cs new file mode 100644 index 0000000..dfa80fe --- /dev/null +++ b/WcsMain/Enum/Plc/PickTaskRouterEnum.cs @@ -0,0 +1,11 @@ +namespace WcsMain.Enum.Plc; + +/// +/// 拣选移栽机路向枚举 +/// +public enum PickTaskRouterEnum +{ + straight = 3, // 通过, + down = 1, // 移栽 + up = 2, // 左 +} \ No newline at end of file diff --git a/WcsMain/Enum/Plc/StackerConveyModelEnum.cs b/WcsMain/Enum/Plc/StackerConveyModelEnum.cs new file mode 100644 index 0000000..367ac1b --- /dev/null +++ b/WcsMain/Enum/Plc/StackerConveyModelEnum.cs @@ -0,0 +1,6 @@ +namespace WcsMain.Enum.Plc; + +public enum StackerConveyModelEnum +{ + +} \ No newline at end of file diff --git a/WcsMain/Enum/Plc/StackerConveyStatusEnum.cs b/WcsMain/Enum/Plc/StackerConveyStatusEnum.cs new file mode 100644 index 0000000..147d541 --- /dev/null +++ b/WcsMain/Enum/Plc/StackerConveyStatusEnum.cs @@ -0,0 +1,6 @@ +namespace WcsMain.Enum.Plc; + +public enum StackerConveyStatusEnum +{ + +} \ No newline at end of file diff --git a/WcsMain/Enum/SendWmsTaskStatusEnum.cs b/WcsMain/Enum/SendWmsTaskStatusEnum.cs new file mode 100644 index 0000000..219f7ad --- /dev/null +++ b/WcsMain/Enum/SendWmsTaskStatusEnum.cs @@ -0,0 +1,20 @@ +namespace WcsMain.Enum; + +/// +/// 发送给 WMS 的任务状态 +/// +public enum SendWmsTaskStatusEnum +{ + queuing = 1, // 任务排队中 + start = 2, // 任务开始 + leaveOrigin = 3, // 任务离开初始位置 + arriveMid = 4, // 任务到达中间点 + arrive = 5, // 任务到达目的地 + complete = 100, // 任务完成 + emptyOut = 700, // 空出库 + doubleIn = 800, // 重复入库 + cancel = 998, // 任务取消 + err = 999, // 任务异常 + other = 0, + +} \ No newline at end of file diff --git a/WcsMain/Enum/Stacker/StackerControlModeEnum.cs b/WcsMain/Enum/Stacker/StackerControlModeEnum.cs new file mode 100644 index 0000000..6118e2c --- /dev/null +++ b/WcsMain/Enum/Stacker/StackerControlModeEnum.cs @@ -0,0 +1,17 @@ +namespace WcsMain.Enum.Stacker; + +/// +/// 堆垛机控制方式 +/// +public enum StackerControlModeEnum +{ + offline = 0, // 离线 + selfLearning = 1, // 自学习 + debug = 2, // 调试 + manual = 3, // 手动 + standAlone = 4, // 单机,本机自动 + online = 5, // 联机、在线 + + non = -1, // 未知 + +} \ No newline at end of file diff --git a/WcsMain/Enum/Stacker/StackerStatusEnum.cs b/WcsMain/Enum/Stacker/StackerStatusEnum.cs new file mode 100644 index 0000000..83550c3 --- /dev/null +++ b/WcsMain/Enum/Stacker/StackerStatusEnum.cs @@ -0,0 +1,22 @@ +namespace WcsMain.Enum.Stacker; + +/// +/// 堆垛机状态 +/// +public enum StackerStatusEnum +{ + offline = 0, // 脱机、失联 + free = 1, // 空闲 + acceptTask = 2, // 任务接收 + getMove = 3, // 取货移动 + getting = 4, // 取货中 + getComplete = 5, // 取货完成 + setMove = 6, // 卸货移动 + setting = 7, // 卸货中 + setComplete = 8, // 卸货完成 + taskComplete = 9, // 任务完成 + deleteTask = 10, // 删除任务 + checking = 11, // 盘点中 + applyTask = 21, // 二次预约申请 + non = -1, // 未知 +} \ No newline at end of file diff --git a/WcsMain/Enum/Stacker/StackerUseStatusEnum.cs b/WcsMain/Enum/Stacker/StackerUseStatusEnum.cs new file mode 100644 index 0000000..cf3557f --- /dev/null +++ b/WcsMain/Enum/Stacker/StackerUseStatusEnum.cs @@ -0,0 +1,11 @@ +namespace WcsMain.Enum.Stacker; + +/// +/// 堆垛机使用状态 +/// +public enum StackerUseStatusEnum +{ + BusyOrErr, // 堆垛机不可用 + Free, // 堆垛机可用 + waitTask, // 堆垛机正在等待任务,重复入库时会有 +} diff --git a/WcsMain/Enum/StandTypeEnum.cs b/WcsMain/Enum/StandTypeEnum.cs new file mode 100644 index 0000000..a29a140 --- /dev/null +++ b/WcsMain/Enum/StandTypeEnum.cs @@ -0,0 +1,15 @@ +namespace WcsMain.Enum; + +/// +/// 用于指示 点位的类型 +/// +public enum StandTypeEnum +{ + storageLocation = 0, // 库位 + stackerInStand = 1, // 堆垛机入库站台 + stackerOutStand = 2, // 堆垛机出库站台 + stackerPickStand = 4, // 堆垛机拣选盘点站台 + + conveyInStand = -1, // 输送机入库站台 + conveyOutStand = -2, // 输送机出库站台 +} \ No newline at end of file diff --git a/WcsMain/Enum/TaskEnum/ElTagTaskStatusEnum.cs b/WcsMain/Enum/TaskEnum/ElTagTaskStatusEnum.cs new file mode 100644 index 0000000..d2971f5 --- /dev/null +++ b/WcsMain/Enum/TaskEnum/ElTagTaskStatusEnum.cs @@ -0,0 +1,15 @@ +namespace WcsMain.Enum.TaskEnum; + +/// +/// 电子标签任务状态枚举 +/// +public enum ElTagTaskStatusEnum +{ + NeedLight = 0, // 待点亮 + Lighting = 1, // 点亮中 + Confirm = 2, // 已确认 + Off = 3, // 已熄灭 + Error = 9, // 发生异常 + + +} diff --git a/WcsMain/Enum/TaskEnum/ElTagTaskTypeEnum.cs b/WcsMain/Enum/TaskEnum/ElTagTaskTypeEnum.cs new file mode 100644 index 0000000..458f582 --- /dev/null +++ b/WcsMain/Enum/TaskEnum/ElTagTaskTypeEnum.cs @@ -0,0 +1,10 @@ +namespace WcsMain.Enum.TaskEnum; + +/// +/// 电子标签的任务类型 +/// +public enum ElTagTaskTypeEnum +{ + PICK = 1, // 拣选任务 + STOCK = 2, // 存储确认 +} diff --git a/WcsMain/Enum/TaskEnum/TaskCategoryEnum.cs b/WcsMain/Enum/TaskEnum/TaskCategoryEnum.cs new file mode 100644 index 0000000..2af8f7f --- /dev/null +++ b/WcsMain/Enum/TaskEnum/TaskCategoryEnum.cs @@ -0,0 +1,12 @@ +namespace WcsMain.Enum.TaskEnum; + +/// +/// 任务类别 +/// +public enum TaskCategoryEnum +{ + stacker = 1, // 堆垛机任务 + stackerConvey = 2, // 库前输送任务 + + +} diff --git a/WcsMain/Enum/TaskEnum/TaskTypeEnum.cs b/WcsMain/Enum/TaskEnum/TaskTypeEnum.cs new file mode 100644 index 0000000..4219d70 --- /dev/null +++ b/WcsMain/Enum/TaskEnum/TaskTypeEnum.cs @@ -0,0 +1,15 @@ +namespace WcsMain.Enum.TaskEnum; + +/// +/// 任务类型 +/// +public enum TaskTypeEnum +{ + conveyTask = -1, // 输送机任务 + inTask = 1, // 入库任务 + outTask = 2, // 出库任务 + pick = 4, // 拣选任务 + check = 10, // 盘点任务 + moveTask = 9, // 移库任务 + newTaskForDoubleIn = 11, // 重复入库的新任务 +} \ No newline at end of file diff --git a/WcsMain/Enum/TaskEnum/WcsTaskStatusEnum.cs b/WcsMain/Enum/TaskEnum/WcsTaskStatusEnum.cs new file mode 100644 index 0000000..3349768 --- /dev/null +++ b/WcsMain/Enum/TaskEnum/WcsTaskStatusEnum.cs @@ -0,0 +1,16 @@ +namespace WcsMain.Enum.TaskEnum; + +/// +/// WCS 表状态 +/// +public enum WcsTaskStatusEnum +{ + create = 0, + leaveOrigin = 1, + running = 2, + arriveDestination = 3, + complete = 4, + doubleIn = 7, + emptyOut = 8, + err = 9, +} \ No newline at end of file diff --git a/WcsMain/Enum/TaskEnum/WcsTaskTypeEnum.cs b/WcsMain/Enum/TaskEnum/WcsTaskTypeEnum.cs new file mode 100644 index 0000000..efcb48d --- /dev/null +++ b/WcsMain/Enum/TaskEnum/WcsTaskTypeEnum.cs @@ -0,0 +1,13 @@ +namespace WcsMain.Enum.TaskEnum; + +public enum WcsTaskTypeEnum +{ + inTask = 1, // 入库任务 + outTask = 2, // 出库任务 + pick = 4, // 拣选任务 + check = 10, // 盘点任务 + moveTask = 9, // 移库任务 + + + newTaskForDoubleIn = 21, // 卸货位置有货的新任务 +} diff --git a/WcsMain/Enum/TaskEnum/WmsTaskStatusEnum.cs b/WcsMain/Enum/TaskEnum/WmsTaskStatusEnum.cs new file mode 100644 index 0000000..9febcb1 --- /dev/null +++ b/WcsMain/Enum/TaskEnum/WmsTaskStatusEnum.cs @@ -0,0 +1,13 @@ +namespace WcsMain.Enum.TaskEnum; + +/// +/// WMS任务状态枚举 +/// +public enum WmsTaskStatusEnum +{ + create = 0, // 任务创建 + queuing = 1, // 排队中 + running = 2, // 执行中 + complete = 3, // 执行完成 + err = 9, // 执行异常 +} \ No newline at end of file diff --git a/WcsMain/Enum/TaskEnum/WmsTaskTypeEnum.cs b/WcsMain/Enum/TaskEnum/WmsTaskTypeEnum.cs new file mode 100644 index 0000000..30027e7 --- /dev/null +++ b/WcsMain/Enum/TaskEnum/WmsTaskTypeEnum.cs @@ -0,0 +1,13 @@ +namespace WcsMain.Enum.TaskEnum; + +/// +/// Wms 的任务枚举 +/// +public enum WmsTaskTypeEnum +{ + inTask = 1, // 入库任务 + outTask = 2, // 出库任务 + pick = 4, // 拣选任务 + check = 10, // 盘点任务 + moveTask = 9, // 移库任务 +} diff --git a/WcsMain/Enum/TaskFeedBackTypeEnum.cs b/WcsMain/Enum/TaskFeedBackTypeEnum.cs new file mode 100644 index 0000000..87f077d --- /dev/null +++ b/WcsMain/Enum/TaskFeedBackTypeEnum.cs @@ -0,0 +1,13 @@ +namespace WcsMain.Enum; + +/// +/// 过账反馈类型 +/// +public enum TaskFeedBackTypeEnum +{ + complete = 1, // 任务完成 + cancel = 2, // 任务取消 + doubleIn = 3, // 重复入库 + emptyOut = 4, // 空出库 + +} \ No newline at end of file diff --git a/WcsMain/Enum/Tcp/TcpType.cs b/WcsMain/Enum/Tcp/TcpType.cs new file mode 100644 index 0000000..8bd203d --- /dev/null +++ b/WcsMain/Enum/Tcp/TcpType.cs @@ -0,0 +1,12 @@ +namespace WcsMain.Enum.Tcp; + +/// +/// Tcp 类型 +/// +public enum TcpType +{ + PLC = 0, // PLC + SCAN = 1, // 扫码器 + Opr = 2, // 电子标签 + +} diff --git a/WcsMain/Enum/UserStatusEnum.cs b/WcsMain/Enum/UserStatusEnum.cs new file mode 100644 index 0000000..2bc01be --- /dev/null +++ b/WcsMain/Enum/UserStatusEnum.cs @@ -0,0 +1,10 @@ +namespace WcsMain.Enum; + +/// +/// 用户状态枚举 +/// +public enum UserStatusEnum +{ + disabled = 0, // 禁用 + normal = 1, // 正常 +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/ConnectPLCs.cs b/WcsMain/EquipOperation/ConnectPLCs.cs new file mode 100644 index 0000000..82a586c --- /dev/null +++ b/WcsMain/EquipOperation/ConnectPLCs.cs @@ -0,0 +1,54 @@ +using PlcTool.Siemens; +using WcsMain.Common; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.EquipOperation; + +/// +/// 连接PLC +/// +[Component] +public class ConnectPLCs(AppPLCDao pLCDao, AppDBDao dBDao) +{ + private readonly AppPLCDao _plcDao = pLCDao; + private readonly AppDBDao _dbDao = dBDao; + + /// + /// 连接PLC + /// + /// 返回值表示是否继续检测PLC连接状态 + public bool ConnectPlc() + { + /* 查询所有PLC */ + List? plcs = _plcDao.GetDataWithStatus(1); + if (plcs == default) + { + ConsoleLog.Tip("$[异常]PLC 数据查找失败,请检查网络是否正常或者是否录入PLC数据"); + return true; + } + if (plcs.Count == 0) + { + ConsoleLog.Warning("【警告】您没有录入或者启用任何 PLC"); + CommonData.IsConnectPlc = false; + return false; + } + List? dbs = _dbDao.Select(); + CommonTool.Siemens = new SiemensS7(); + CommonTool.Siemens.SetPlcs(plcs).SetPlcDB(dbs); + var connectResult = CommonTool.Siemens.ConnectPlcs(); + if (connectResult.Success) + { + CommonData.IsConnectPlc = true; + } + else + { + CommonData.IsConnectPlc = false; + ConsoleLog.Error($"[异常]{connectResult.Message}"); + ConsoleLog.Error($"请检查设备网络后重新启动 WCS "); + } + return true; + } + +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/Convey/ConveyOperation.cs b/WcsMain/EquipOperation/Convey/ConveyOperation.cs new file mode 100644 index 0000000..3d92b9a --- /dev/null +++ b/WcsMain/EquipOperation/Convey/ConveyOperation.cs @@ -0,0 +1,267 @@ +using System.Text; +using System.Text.RegularExpressions; +using WcsMain.Common; +using WcsMain.EquipOperation.Entity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.EquipOperation.Convey; + +/// +/// 输送机操作 ---- 箱式线,不包含于立库库前设备,库前请参考堆垛机操作 +/// +[Component] +public class ConveyOperation +{ + /// + /// 写入输送机心跳 + /// + /// + public string WriteHeartBeat() + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) return "PLC尚未连接。"; + var (writeResult, _) = CommonTool.Siemens.WritePlcWhithName($"箱式线写心跳", (short)0); + return writeResult.Success ? string.Empty : writeResult.Message ?? "写入失败,未知原因"; + } + + /// + /// 箱式线允许取货 + /// + /// 要取货的点位 + /// + public bool AllGetVehicle(string? point) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) return false; + var readResult = CommonTool.Siemens.ReadInt16WithName($"箱式线允许取货{point}"); + return readResult.Success && readResult.Value == 1; + } + + /// + /// 箱式线允许卸货 + /// + /// 要取货的点位 + /// + public bool AllSetVehicle(string? point) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) return false; + var readResult = CommonTool.Siemens.ReadInt16WithName($"箱式线允许卸货{point}"); + return readResult.Success && readResult.Value == 1; + } + + /// + /// 读取箱式线入库站台反馈的载具号 + /// + /// + /// + public List? ReadConveyCode(string? point) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) return default; + var readResult1 = CommonTool.Siemens.ReadStringWithName($"箱式线入库载具号{point}-1", 20, Encoding.ASCII); + var readResult2 = CommonTool.Siemens.ReadStringWithName($"箱式线入库载具号{point}-2", 20, Encoding.ASCII); + if (!readResult1.Success! || !readResult2.Success!) return default; + // 返回读取到的任务号 + return [Regex.Replace(readResult1.Value ?? "", "\\W", ""), Regex.Replace(readResult2.Value ?? "", "\\W", "")]; + } + + + + + + + /// + /// 写入箱式线任务 + /// + /// + /// + /// + public string WriteTask(string? equipmentId, ConveyPLCTask task) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return "PLC尚未连接。"; + } + var (writeResult, _) = CommonTool.Siemens.WritePlcWhithName($"箱式线写任务{equipmentId}", + task.PlcId, + task.Router); + if (writeResult.Success) + { + return string.Empty; + } + return writeResult.Message ?? "写入失败,未知原因"; + } + + + + + /// + /// 获取箱式线出库站台状态 + /// + /// + /// + public bool GetOutConveyStatus(string? standId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return false; + } + var readGoodsExistResult = CommonTool.Siemens.ReadBoolWithName($"站台货物检测{standId}"); + if (!readGoodsExistResult.Success || readGoodsExistResult.Value) // 读取失败,或者有货 就不允许出库 + { + return false; + } + var readAGVExistResult = CommonTool.Siemens.ReadBoolWithName($"站台AGV检测{standId}"); + if (!readAGVExistResult.Success || readAGVExistResult.Value) // 读取失败,或者有AGV 就不允许出库 + { + return false; + } + if (!readGoodsExistResult.Value && !readAGVExistResult.Value) // 无货并且没有AGV才能出库 + { + return true; + } + return false; + } + + + // 10.40.200.210 + + /// + /// 获取入库站台状态 --- 是否可以入库 + /// + /// + /// + public bool GetInConveyStatus(string? standId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return false; + } + if (string.IsNullOrEmpty(standId)) { return false; } + if (standId.StartsWith('P')) + { + var readGoodsNeedIn = CommonTool.Siemens.ReadBoolWithName($"站台请求入库{standId}"); + if (!readGoodsNeedIn.Success || !readGoodsNeedIn.Value) // 读取失败,或者没有货 就不允许入库 + { + return false; + } + return true; + } + var readGoodsExistResult = CommonTool.Siemens.ReadBoolWithName($"站台货物检测{standId}"); + if (!readGoodsExistResult.Success || !readGoodsExistResult.Value) // 读取失败,或者没有货 就不允许入库 + { + return false; + } + var readAGVExistResult = CommonTool.Siemens.ReadBoolWithName($"站台AGV检测{standId}"); + if (!readAGVExistResult.Success || readAGVExistResult.Value) // 读取失败,或者有AGV 就不允许入库 + { + return false; + } + if (readGoodsExistResult.Value && !readAGVExistResult.Value) // 有货并且没有AGV才能入库 + { + return true; + } + return false; + } + + /// + /// 获取箱式线拣选站台状态 + /// + /// + /// + public bool GetPickConveyStatus(string? standId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return false; + } + var readGoodsExistResult = CommonTool.Siemens.ReadBoolWithName($"站台货物检测{standId}"); + if (!readGoodsExistResult.Success) // 读取失败,或者有货 就不允许出库 + { + return false; + } + return !readGoodsExistResult.Value; + } + + /// + /// 获取输送机箱子号 + /// + /// + /// + public string GetStandCode(int stackerId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return string.Empty; + } + var readResult1 = CommonTool.Siemens.ReadStringWithName($"箱式线入库载具号{stackerId}", 10, Encoding.ASCII); + if (!readResult1.Success) + { + // 读取失败 + return string.Empty; + } + // 返回读取到的任务号 + return Regex.Replace(readResult1.Value ?? "", "\\W", ""); + } + + + /// + /// 读取扫码点信息 + /// + /// + /// + public (string? errText, short scanOk, string code) ReadScanInfo(string scanId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return ("PLC尚未连接。", 0, ""); + } + var readResult = CommonTool.Siemens.ReadByteWithName($"扫码读取{scanId}", 22); + if (!readResult.Success || readResult.Value == default) // 读取失败 + { + return (readResult.Message, 0, ""); + } + try + { + var readData = readResult.Value; + short status = Convert.ToInt16(CommonTool.Siemens.Trans(readData, 0)); // PLC 返回任务状态 + string code = Regex.Replace(Encoding.ASCII.GetString(readData, 2, 20), "\\W", ""); + return (string.Empty, status, code); + } + catch (Exception ex) + { + return (ex.Message, 0, ""); + } + } + + /// + /// 清除扫码状态 + /// + /// + /// + public string ClearScanStatus(string scanId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return "PLC尚未连接。"; + } + var (writeResult, _) = CommonTool.Siemens.WritePlcWhithName($"扫码读取{scanId}", (short)0); + if (!writeResult.Success) // 读取失败 + { + return writeResult.Message ?? "写入失败"; + } + try + { + return string.Empty; + } + catch (Exception ex) + { + return ex.Message; + } + } + +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/ElTag/AtopOperation.cs b/WcsMain/EquipOperation/ElTag/AtopOperation.cs new file mode 100644 index 0000000..7eb403b --- /dev/null +++ b/WcsMain/EquipOperation/ElTag/AtopOperation.cs @@ -0,0 +1,102 @@ +using WcsMain.Common; +using WcsMain.ElTag.Atop.AtopEnum; +using WcsMain.ElTag.Atop.Entity; +using WcsMain.Tcp.Entity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.EquipOperation.ElTag; + +[Component] +public class AtopOperation +{ + + /// + /// 点亮这个连接中的所有电子标签,并闪烁 + /// + /// + public void BlinkingAll(TcpServeConnectionData tcpServe) + { + if (CommonData.AppElTags == default || CommonData.AppElTags.Count < 1) return; + var tagInfos = CommonData.AppElTags.FindAll(f => f.ControllerDisplayName == tcpServe.DisplayName); + if (tagInfos == default) return; + /* 使标签闪烁一段时间,告知使用者电子标签已经连上 */ + Thread.Sleep(5000); + int col = new Random().Next(0, 5); + foreach (var tagInfo in tagInfos) + { + col++; + if (col >= 5) col = 0; + CommonTool.OprTcpClient.Send(TagSendInfo.ShowTagId(Convert.ToByte(tagInfo.TagId)), tagInfo.ControllerDisplayName!); + CommonTool.OprTcpClient.Send(TagSendInfo.LedColor(Convert.ToByte(tagInfo.TagId), (LedColor)col), tagInfo.ControllerDisplayName!); // 显示颜色 + CommonTool.OprTcpClient.Send(TagSendInfo.LedStatus(Convert.ToByte(tagInfo.TagId), LedStatus._0_25secblinking), tagInfo.ControllerDisplayName!); // 显示闪烁频率 + } + Thread.Sleep(5000); + foreach (var tagInfo in tagInfos) + { + CommonTool.OprTcpClient.Send(TagSendInfo.TurnOffTag(Convert.ToByte(tagInfo.TagId)), tagInfo.ControllerDisplayName!); + } + } + + + /// + /// 向标签发送一个显示值 + /// + /// + /// + /// + /// + /// + /// + public Exception? ShowMsg(string? controllerDisplayName, int? tagId, int? value, LedColor ledColor = LedColor.Green, LedStatus ledStatus = LedStatus._0_5secblinking) + => ShowMsg(controllerDisplayName, tagId, value.ToString(), ledColor, ledStatus); + + + + /// + /// 向标签发送一个显示值 + /// + /// + /// + /// + /// + /// + /// + public Exception? ShowMsg(string? controllerDisplayName, int? tagId, string? value, LedColor ledColor = LedColor.Green, LedStatus ledStatus = LedStatus._0_5secblinking) + { + if (controllerDisplayName == default || tagId == default || value == default) + { + return new Exception("必须的值为空,无法发送电子标签"); + } + byte[] elTagData = TagSendInfo.ShowValue(Convert.ToByte(tagId), value); + var sendResult = CommonTool.OprTcpClient.Send(elTagData, controllerDisplayName); // 显示字符 + //CommonTool.OprTcpClient.Send(TagSendInfo.SetValueKeepAlive(Convert.ToByte(tagId)), controllerDisplayName); // 使标签确认后仍然显示字符 + CommonTool.OprTcpClient.Send(TagSendInfo.LedColor(Convert.ToByte(tagId), ledColor), controllerDisplayName); // 显示颜色 + CommonTool.OprTcpClient.Send(TagSendInfo.LedStatus(Convert.ToByte(tagId), ledStatus), controllerDisplayName); // 显示闪烁频率 + if (sendResult.Success) return default; + return sendResult.Exception; + } + + /// + /// 仅显示数据,不亮灯 + /// + /// + /// + /// + /// + public Exception? ShowMsgOnly(string? controllerDisplayName, int? tagId, string? value) + { + if (controllerDisplayName == default || tagId == default || value == default) + { + return new Exception("必须的值为空,无法发送电子标签"); + } + byte[] elTagData = TagSendInfo.ShowValue(Convert.ToByte(tagId), value); + var sendResult = CommonTool.OprTcpClient.Send(elTagData, controllerDisplayName); // 显示字符 + CommonTool.OprTcpClient.Send(TagSendInfo.SetValueKeepAlive(Convert.ToByte(tagId)), controllerDisplayName); // 使标签确认后仍然显示字符 + CommonTool.OprTcpClient.Send(TagSendInfo.LedStatus(Convert.ToByte(tagId), LedStatus.LEDOff), controllerDisplayName); // 显示闪烁频率 -- 关闭按钮 + if (sendResult.Success) return default; + return sendResult.Exception; + } + + + +} diff --git a/WcsMain/EquipOperation/Entity/ConveyPLCTask.cs b/WcsMain/EquipOperation/Entity/ConveyPLCTask.cs new file mode 100644 index 0000000..dae6174 --- /dev/null +++ b/WcsMain/EquipOperation/Entity/ConveyPLCTask.cs @@ -0,0 +1,23 @@ +namespace WcsMain.EquipOperation.Entity; + +/// +/// 给输送机任务 +/// +public class ConveyPLCTask +{ + /// + /// Plc任务号 + /// + public int PlcId { get; set; } + + /// + /// 路向 + /// + public short Router { get; set; } + + + public override string ToString() + { + return $"<{PlcId} | {Router}>"; + } +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/Entity/Stacker/StackerInfo.cs b/WcsMain/EquipOperation/Entity/Stacker/StackerInfo.cs new file mode 100644 index 0000000..17e343c --- /dev/null +++ b/WcsMain/EquipOperation/Entity/Stacker/StackerInfo.cs @@ -0,0 +1,118 @@ +using WcsMain.Enum.Stacker; + +namespace WcsMain.EquipOperation.Entity.Stacker; + +/// +/// 堆垛机信息,读取的时候赋值 +/// +public class StackerInfo +{ + + /// + /// 任务号 + /// + public int PlcId { get; set; } + + /// + /// 控制方式,int16 + /// + public StackerControlModeEnum ControlModel { get; set; } + + /// + /// 设备状态,int16 + /// + public StackerStatusEnum StackerStatus { get; set; } + + + + /// + /// 当前巷道 + /// + public short TunnelId { get; set; } + + /// + /// 当前排 + /// + public short Row { get; set; } + + + /// + /// 当前列 + /// + public short Line { get; set; } + + + /// + /// 当前层 + /// + public short Layer { get; set; } + + + /// + /// 当前深 + /// + public short Depth { get; set; } + + /// + /// 条码值 + /// + public int Code { get; set; } + + /// + /// 故障号 + /// + public short ErrCode { get; set; } + + /// + /// 行走里程 + /// + public float WorkLength { get; set; } + + /// + /// 提升里程 + /// + public float UpLength { get; set; } + + /// + /// 货叉次数计数 + /// + public int ForkCount { get; set; } + + /// + /// 提交任务 + /// + /// + /// 当前已经弃用 + /// + public int SubmitPlcId { get; set; } + + /// + /// 删除任务 + /// + /// + /// 当前已经弃用 + /// + public int DeletePlcId { get; set; } + + /// + /// 备用1 + /// + public int Spare1 { get; set; } + + /// + /// 备用2 + /// + public short Spare2 { get; set; } + + /// + /// 判断堆垛机是否可用 + /// + /// + public bool CanUse() + { + return ControlModel == StackerControlModeEnum.online + && StackerStatus == StackerStatusEnum.free + && ErrCode == 0; + } + +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/Entity/Stacker/StackerPlcTask.cs b/WcsMain/EquipOperation/Entity/Stacker/StackerPlcTask.cs new file mode 100644 index 0000000..8957b0c --- /dev/null +++ b/WcsMain/EquipOperation/Entity/Stacker/StackerPlcTask.cs @@ -0,0 +1,145 @@ +using System.Collections; +using WcsMain.Enum.TaskEnum; + +namespace WcsMain.EquipOperation.Entity.Stacker; + +/// +/// 写入PLC的任务 ---- 堆垛机 +/// +public class StackerPlcTask +{ + /// + /// 设备号,用于识别写入哪个堆垛机 + /// + public int? StackerId { get; set; } + + + + /// + /// 任务号 + /// + public int? PlcId { get; set; } = 0; + + /// + /// 任务类型 + /// + public short? TaskType { get; set; } = 0; + + /// + /// 取货站台 + /// + public short? GetStand { get; set; } = 0; + + /// + /// 入库巷道号 + /// + public short? InTunnelId { get; set; } = 0; + + /// + /// 出库巷道号 + /// + public short? OutTunnelId { get; set; } = 0; + + /// + /// 卸货站台 + /// + public short? SetStand { get; set; } = 0; + + /// + /// 取货排 + /// + public short? GetQueue { get; set; } = 0; + + /// + /// 取货列 + /// + public short? GetLine { get; set; } = 0; + + /// + /// 取货层 + /// + public short? GetLayer { get; set; } = 0; + + /// + /// 卸货排 + /// + public short? SetQueue { get; set; } = 0; + + /// + /// 卸货列 + /// + public short? SetLine { get; set; } = 0; + + /// + /// 卸货层 + /// + public short? SetLayer { get; set; } = 0; + + /// + /// 取货深度 + /// + public short? GetDeep { get; set; } = 0; + + /// + /// 卸货深度 + /// + public short? SetDeep { get; set; } = 0; + + /// + /// 尺寸 + /// + public short? Size { get; set; } = 0; + + /// + /// 重量 + /// + public short? Weight { get; set; } = 0; + + + /// + /// 料箱码 + /// + public int Code { get; set; } + + + public override string ToString() + { + return $"PlcId:{PlcId},{GetQueue}排{GetLine}列{GetLayer}层{GetDeep}深 --> {SetQueue}排{SetLine}列{SetLayer}层{SetDeep}深"; + } + + + /// + /// 默认的直接从入口搬出去的任务 + /// + /// + /// + /// + /// + /// + public static StackerPlcTask DefaultErrTask(int plcId, int stackerId, int forkId, int vehicleNo = 999999999) + { + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = plcId, + TaskType = Convert.ToInt16(WcsTaskTypeEnum.moveTask), + GetStand = 0, + InTunnelId = Convert.ToInt16(stackerId), + OutTunnelId = Convert.ToInt16(stackerId), + SetStand = 0, + GetQueue = 2, + GetLine = Convert.ToInt16(forkId), + GetLayer = 1, + GetDeep = 1, + SetQueue = 2, + SetLine = Convert.ToInt16(forkId), + SetLayer = 2, + SetDeep = 1, + Size = 0, + Weight = 0, + Code = vehicleNo, + }; + return stackerTask; + } + +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/Entity/StackerConvey/StackerConveyInfo.cs b/WcsMain/EquipOperation/Entity/StackerConvey/StackerConveyInfo.cs new file mode 100644 index 0000000..7b78e8a --- /dev/null +++ b/WcsMain/EquipOperation/Entity/StackerConvey/StackerConveyInfo.cs @@ -0,0 +1,76 @@ +using WcsMain.Enum.Plc; + +namespace WcsMain.EquipOperation.Entity.StackerConvey; + +/// +/// 库前输送机反馈的状态信息 +/// +public class StackerConveyInfo +{ + /// + /// 输送机编号 + /// + public short? ConveyNo { get; set; } + + /// + /// 任务类型 + /// + public short? TaskType { get; set; } + + /// + /// 堆垛机动作禁止 + /// + public short? StackerForbidden { get; set; } + + /// + /// 当前任务号 + /// + public int? PlcId { get; set; } + + /// + /// 控制方式 + /// + public StackerConveyModelEnum? ControlModel { get; set; } + + /// + /// 库前输送机状态 + /// + public StackerConveyStatusEnum? StackerConveyStatus { get; set; } + + /// + /// 载具尺寸 + /// + public short? VehicleSize { get; set; } + + /// + /// 重量 + /// + public short? Weight { get; set; } + + /// + /// 传感器状态 + /// + public uint? SensorStatus { get; set; } + + /// + /// 申请条码 + /// + public int? ApplyCode { get; set; } + + /// + /// 正在运行的条码 + /// + public int? RunningCode { get; set; } + + /// + /// 错误信息 + /// + public short? ErrCode { get; set; } + + /// + /// 备注 + /// + public int? Remark { get; set; } + + +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/Entity/StackerConvey/StackerConveyPlcTask.cs b/WcsMain/EquipOperation/Entity/StackerConvey/StackerConveyPlcTask.cs new file mode 100644 index 0000000..6e698d2 --- /dev/null +++ b/WcsMain/EquipOperation/Entity/StackerConvey/StackerConveyPlcTask.cs @@ -0,0 +1,97 @@ +namespace WcsMain.EquipOperation.Entity.StackerConvey; + +public class StackerConveyPlcTask +{ + /// + /// 设备号,用于识别写入哪个输送机 + /// + public string? StackerConveyId { get; set; } + + + + /// + /// 任务号 + /// + public int? PlcId { get; set; } = 0; + + /// + /// 任务类型 + /// + public short? TaskType { get; set; } = 0; + + /// + /// 取货站台 + /// + public short? GetStand { get; set; } = 0; + + /// + /// 入库巷道号 + /// + public short? InTunnelId { get; set; } = 0; + + /// + /// 出库巷道号 + /// + public short? OutTunnelId { get; set; } = 0; + + /// + /// 卸货站台 + /// + public short? SetStand { get; set; } = 0; + + /// + /// 取货排 + /// + public short? GetQueue { get; set; } = 0; + + /// + /// 取货列 + /// + public short? GetLine { get; set; } = 0; + + /// + /// 取货层 + /// + public short? GetLayer { get; set; } = 0; + + /// + /// 卸货排 + /// + public short? SetQueue { get; set; } = 0; + + /// + /// 卸货列 + /// + public short? SetLine { get; set; } = 0; + + /// + /// 卸货层 + /// + public short? SetLayer { get; set; } = 0; + + /// + /// 取货深度 + /// + public short? GetDeep { get; set; } = 0; + + /// + /// 卸货深度 + /// + public short? SetDeep { get; set; } = 0; + + /// + /// 尺寸 + /// + public short? Size { get; set; } = 0; + + /// + /// 重量 + /// + public short? Weight { get; set; } = 0; + + + /// + /// 料箱码 + /// + public int Code { get; set; } +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/Entity/StackerFeedbackStatus.cs b/WcsMain/EquipOperation/Entity/StackerFeedbackStatus.cs new file mode 100644 index 0000000..2588563 --- /dev/null +++ b/WcsMain/EquipOperation/Entity/StackerFeedbackStatus.cs @@ -0,0 +1,15 @@ +namespace WcsMain.EquipOperation.Entity; + +/// +/// 堆垛机状态反馈实体 +/// +public class StackerFeedbackStatus +{ + /// + /// plc 任务号 + /// + public int PlcId { get; set; } + + + +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/Entity/TaskFeedBackEntity.cs b/WcsMain/EquipOperation/Entity/TaskFeedBackEntity.cs new file mode 100644 index 0000000..293284c --- /dev/null +++ b/WcsMain/EquipOperation/Entity/TaskFeedBackEntity.cs @@ -0,0 +1,27 @@ +namespace WcsMain.EquipOperation.Entity; + +/// +/// 过账数据实体 +/// +public class TaskFeedBackEntity +{ + /// + /// 设备号 + /// + public int StackerId { get; set; } + + /// + /// 过账任务号 + /// + public int PlcId { get; set; } + + /// + /// 过账类型 + /// + public short FeedBackType { get; set; } + + /// + /// 序列号 + /// + public int CountNo { get; set; } +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/Stacker/StackerOperation.cs b/WcsMain/EquipOperation/Stacker/StackerOperation.cs new file mode 100644 index 0000000..6064278 --- /dev/null +++ b/WcsMain/EquipOperation/Stacker/StackerOperation.cs @@ -0,0 +1,304 @@ +using WcsMain.Common; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.Stacker; +using WcsMain.EquipOperation.Entity; +using WcsMain.EquipOperation.Entity.Stacker; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.EquipOperation.Stacker; + +/// +/// 堆垛机操作 +/// +[Component] +public class StackerOperation +{ + + + /********************************** 总结方法 *******************************************/ + + /// + /// 判断堆垛机状态,若是等待(二次申请)状态则带出当前的PlcId + /// + /// + /// + /// + /// + public StackerUseStatusEnum StackerCanUse(int? stackerId, out int plcId1, out int spare1) + { + plcId1 = 0; + spare1 = 0; + if (stackerId == default) return StackerUseStatusEnum.BusyOrErr; + /* 获取堆垛机状态 */ + var (errMsg, stackerInfo) = GetStackerInfo((int)stackerId!); + if (!string.IsNullOrEmpty(errMsg) || stackerInfo == default) return StackerUseStatusEnum.BusyOrErr; + /* 堆垛机空闲条件 */ + var canUse = stackerInfo.ControlModel == StackerControlModeEnum.online + && stackerInfo.StackerStatus == StackerStatusEnum.free && stackerInfo.ErrCode == 0; + if (canUse) return StackerUseStatusEnum.Free; + /* 堆垛机等待任务条件(二次申请) */ + var waitTask = stackerInfo.ControlModel == StackerControlModeEnum.online && stackerInfo.StackerStatus == StackerStatusEnum.applyTask + && stackerInfo.ErrCode == 0; + if (waitTask) + { + plcId1 = stackerInfo.PlcId; + spare1 = stackerInfo.Spare1; + return StackerUseStatusEnum.waitTask; + } + return StackerUseStatusEnum.BusyOrErr; + } + + + + + + + + + + + + + + /********************************** 子方法 *******************************************/ + + + + /// + /// 读取堆垛机反馈信息 + /// + /// + /// + public (string? errText, StackerInfo? stackerInfo) GetStackerInfo(int stackerId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return ("设备尚未连接", default); // 未连接PLC + } + var readResult = CommonTool.Siemens.ReadByteWithName($"堆垛机状态反馈{stackerId}", 28); + if (!readResult.Success || readResult.Value == default) + { + return (readResult.Message, default); // 读取失败 + } + var data = readResult.Value; + StackerInfo stackerInfo = new() + { + PlcId = Convert.ToInt32(CommonTool.Siemens.Trans(data, 0)), // 当前运行任务号 + ControlModel = (StackerControlModeEnum)Convert.ToInt16(CommonTool.Siemens.Trans(data, 4)), // 控制方式 + StackerStatus = (StackerStatusEnum)Convert.ToInt16(CommonTool.Siemens.Trans(data, 6)), // 堆垛机状态 + TunnelId = Convert.ToInt16(CommonTool.Siemens.Trans(data, 8)), // 当前巷道 + Row = Convert.ToInt16(CommonTool.Siemens.Trans(data, 10)), // 当前排 + Line = Convert.ToInt16(CommonTool.Siemens.Trans(data, 12)), // 当前列 + Layer = Convert.ToInt16(CommonTool.Siemens.Trans(data, 14)), // 当前层 + Depth = Convert.ToInt16(CommonTool.Siemens.Trans(data, 16)), // 当前深度 + Code = Convert.ToInt32(CommonTool.Siemens.Trans(data, 18)), // 料箱码 + ErrCode = Convert.ToInt16(CommonTool.Siemens.Trans(data, 26)), // 错误码 + //WorkLength = Convert.ToSingle(CommonTool.Siemens.Trans(data, 28)), // 行走距离 + //UpLength = Convert.ToSingle(CommonTool.Siemens.Trans(data, 32)), // 提升距离 + //ForkCount = Convert.ToInt32(CommonTool.Siemens.Trans(data, 36)), // 货叉动作次数 + // SubmitPlcId = Convert.ToInt32(CommonTool.Siemens.Trans(data, 36)), // 提交的任务 + // DeletePlcId = Convert.ToInt32(CommonTool.Siemens.Trans(data, 40)), // 删除的任务 + // Spare1 = Convert.ToInt32(CommonTool.Siemens.Trans(data, 40)), // 备用1 + // Spare2 = Convert.ToInt16(CommonTool.Siemens.Trans(data, 48)), // 备用2 + }; + return (string.Empty, stackerInfo); + } + + /// + /// 读取所有故障 + /// + /// + /// + public bool[]? GetStackerErrInfo(int stackerId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return default; // 未连接PLC + } + var readResult = CommonTool.Siemens.ReadBoolWithName($"堆垛机当前报警{stackerId}", 96); + if (!readResult.Success || readResult.Value == default) + { + return default; + } + var data = readResult.Value; + return [.. data]; + } + + + /// + /// 复位堆垛机 + /// + /// + /// + public string ResetStacker(string? stackerId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return "PLC尚未连接"; + } + var (readResult, _) = CommonTool.Siemens.WritePlcWhithName($"堆垛机复位{stackerId}", (short)1); + if (readResult.Success) + { + return string.Empty; + } + return readResult.Message ?? "请求失败,请检查网络"; + } + + /// + /// 堆垛机继续运行 + /// + /// + /// + public string StackerContinue(string? stackerId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return "PLC尚未连接"; + } + var (readResult, _) = CommonTool.Siemens.WritePlcWhithName($"堆垛机继续运行{stackerId}", (short)1); + if (readResult.Success) + { + return string.Empty; + } + return readResult.Message ?? "请求失败,请检查网络"; + } + + /// + /// 获取报警编号 + /// + /// + /// + public int GetErrCode(int? stackerNo) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return -1; // 未连接PLC + } + var readResult = CommonTool.Siemens.ReadInt16WithName($"报警编号{stackerNo}"); + return readResult.Success ? readResult.Value : -1; + } + + /// + /// 堆垛机任务写入确认 + /// + /// + /// + public string WriteTaskConfirm(int? stackerId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) return "PLC尚未连接。"; + var (writeResult, _) = CommonTool.Siemens.WritePlcWhithName($"堆垛机写任务确认{stackerId}", (short)1); + return writeResult.Success ? string.Empty : writeResult.Message ?? "写入失败"; + } + + + /// + /// 写入堆垛机任务,返回错误信息 + /// + /// 任务数据 + /// 货叉编号 + /// + public string WriteTask(StackerPlcTask task, int forkId = 0) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) return "PLC尚未连接。"; // 未连接PLC + var dbAddressName = $"堆垛机写任务{task.StackerId}"; + if (forkId != 0) { dbAddressName += $"-{forkId}"; } + var (writeResult, _) = CommonTool.Siemens.WritePlcWhithName(dbAddressName, + task.PlcId!, + task.TaskType!, + task.GetStand!, + task.InTunnelId!, + task.OutTunnelId!, + task.SetStand!, + task.GetQueue!, + task.GetLine!, + task.GetLayer!, + task.SetQueue!, + task.SetLine!, + task.SetLayer!, + task.GetDeep!, + task.SetDeep!, + task.Size!, + task.Weight!, + task.Code + ); + return writeResult.Success ? string.Empty : writeResult.Message ?? "写入失败,未知原因"; + } + + + /// + /// 获取过账数据 + /// + /// + /// + /// + public List? GetTaskFeedBackData(int dataNum, List stackerList) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return default; // 未连接PLC + } + List taskFeedBackEntities = []; + foreach (var stacker in stackerList) + { + var readResult = CommonTool.Siemens.ReadByteWithName($"过账区{stacker.StackerId}", (ushort)(6 * dataNum)); + if (!readResult.Success) { continue; } + var readData = readResult.Value; + if (readData == default) { continue; } + for (var i = 0; i < dataNum; i++) + { + TaskFeedBackEntity taskFeedBackEntity = new() + { + StackerId = stacker.StackerId ?? 0, + PlcId = Convert.ToInt32(CommonTool.Siemens.Trans(readData, 0 + i * 6)), + FeedBackType = Convert.ToInt16(CommonTool.Siemens.Trans(readData, 4 + i * 6)), + CountNo = i + }; + taskFeedBackEntities.Add(taskFeedBackEntity); + } + } + return taskFeedBackEntities; + } + + /// + /// 重置过账缓冲区 --- 写反馈区间接清除 + /// + /// + public string? ResetFeedBackData(TaskFeedBackEntity taskFeedBackData) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return "PLC 未连接"; // 未连接PLC + } + var dbAddress = CommonTool.Siemens.GetAddressWithNameAndBit($"过账区反馈区{taskFeedBackData.StackerId}", 4 * taskFeedBackData.CountNo); + if (string.IsNullOrEmpty(dbAddress)) + { + return "无法计算偏移量"; + } + var plcNo = taskFeedBackData.StackerId; + var writeResult = CommonTool.Siemens.WritePlcWhithAddress(plcNo, dbAddress, taskFeedBackData.PlcId); + return writeResult.Success ? default : $"清除过账失败,异常信息:{writeResult.Message}"; + } + + + /// + /// 重置过账缓冲区 --- 直接清除 + /// + /// + public string? ClearFeedBackData(TaskFeedBackEntity taskFeedBackData) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return "PLC 未连接"; // 未连接PLC + } + var dbAddress = CommonTool.Siemens.GetAddressWithNameAndBit($"过账区{taskFeedBackData.StackerId}", 6 * taskFeedBackData.CountNo); + if (string.IsNullOrEmpty(dbAddress)) + { + return "无法计算偏移量"; + } + var writeResult = CommonTool.Siemens.WritePlcWhithAddress(1, dbAddress, 0); + return !writeResult.Success ? $"清除过账失败,异常信息:{writeResult.Message}" : default; + } + +} \ No newline at end of file diff --git a/WcsMain/EquipOperation/StackerConvey/StackerConveyOperation.cs b/WcsMain/EquipOperation/StackerConvey/StackerConveyOperation.cs new file mode 100644 index 0000000..4de4b6e --- /dev/null +++ b/WcsMain/EquipOperation/StackerConvey/StackerConveyOperation.cs @@ -0,0 +1,277 @@ +using WcsMain.Common; +using WcsMain.Enum.Plc; +using WcsMain.EquipOperation.Entity; +using WcsMain.EquipOperation.Entity.StackerConvey; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.EquipOperation.StackerConvey; + +[Component] +public class StackerConveyOperation +{ + + /// + /// 读取输送机状态 + /// + /// + /// + public (string? errText, StackerConveyInfo? stackerConveyInfo) GetStackerConveyInfo(string standId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return ("设备尚未连接", default); // 未连接PLC + } + var readResult = CommonTool.Siemens.ReadByteWithName($"输送机状态反馈{standId}", 36); + if (!readResult.Success || readResult.Value == default) + { + return (readResult.Message, default); // 读取失败 + } + var data = readResult.Value; + StackerConveyInfo stackerConveyInfo = new() + { + ConveyNo = Convert.ToInt16(CommonTool.Siemens.Trans(data, 0)), // 输送机编号 + TaskType = Convert.ToInt16(CommonTool.Siemens.Trans(data, 2)), // 任务类型 + StackerForbidden = Convert.ToInt16(CommonTool.Siemens.Trans(data, 4)), // 堆垛机动作禁止 + PlcId = Convert.ToInt32(CommonTool.Siemens.Trans(data, 6)), // 当前任务号 + ControlModel = (StackerConveyModelEnum)Convert.ToInt16(CommonTool.Siemens.Trans(data, 10)), // 控制方式 + StackerConveyStatus = (StackerConveyStatusEnum)Convert.ToInt16(CommonTool.Siemens.Trans(data, 12)), // 库前输送机状态 + VehicleSize = Convert.ToInt16(CommonTool.Siemens.Trans(data, 14)), // 载具尺寸 + Weight = Convert.ToInt16(CommonTool.Siemens.Trans(data, 16)), // 重量 + SensorStatus = Convert.ToUInt32(CommonTool.Siemens.Trans(data, 18)), // 传感器状态 + ApplyCode = Convert.ToInt32(CommonTool.Siemens.Trans(data, 22)), // 申请条码 + RunningCode = Convert.ToInt32(CommonTool.Siemens.Trans(data, 26)), // 正在运行的条码 + ErrCode = Convert.ToInt16(CommonTool.Siemens.Trans(data, 30)), // 错误信息 + Remark = Convert.ToInt32(CommonTool.Siemens.Trans(data, 32)), // 备注 + }; + return (string.Empty, stackerConveyInfo); + } + + + /// + /// 获取输送机任务号 + /// + /// 输送机编号 + /// + public int GetConveyPlcId(string? conveyNo) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return -1; + } + var readResult = CommonTool.Siemens.ReadInt32WithName($"输送机当前任务号{conveyNo}"); + if (readResult.Success) + { + return readResult.Value; + } + return -1; + } + + /// + /// 获取输送机传感器状态 + /// + /// 输送机传感器状态 + /// + public (bool isSuccess, uint value) ReadSenserStatus(string? stackerConveyId) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return (false, 0); + } + var readResult = CommonTool.Siemens.ReadUInt32WithName($"输送机传感器状态{stackerConveyId}"); + if (readResult.Success) + { + return (true, readResult.Value); + } + return (false, 0); + } + + + + /// + /// 读取输送机条码 + /// + /// + /// + public string? ReadConveyCode(string? conveyNo) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return default; + } + var readResult = CommonTool.Siemens.ReadInt32WithName($"输送机读码{conveyNo}"); + if (readResult.Success) + { + return readResult.Value.ToString(); + } + return default; + } + + /// + /// 输送机卸货完成 + /// + /// + /// + public string? SetConveyUnloadSuccess(string? conveyNo) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return "PLC尚未连接"; + } + var writeResult = CommonTool.Siemens.WriteBoolWhithName($"输送机卸货完成{conveyNo}", true); + if (writeResult.Success) + { + return string.Empty; + } + return writeResult.Message; + } + + + + /// + /// 获取输送机是否空闲 + /// + /// 输送机编号 + /// + public int GetConveyIsEasy(string? conveyNo) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return -1; + } + var readResult = CommonTool.Siemens.ReadInt16WithName($"输送机空闲{conveyNo}"); + if (readResult.Success) + { + return readResult.Value; + } + return -1; + } + + /// + /// 获取输送机是否有货 + /// + /// 输送机编号 + /// + public int GetConveyIsHaveGoods(string? conveyNo) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + // 未连接PLC + return -1; + } + var readResult = CommonTool.Siemens.ReadInt32WithName($"输送机是否有货{conveyNo}"); + if (readResult.Success) + { + return readResult.Value; + } + return -1; + } + + /// + /// 写入输送机任务,返回错误信息 + /// + /// + /// + public string WriteTask(StackerConveyPlcTask task) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return "PLC尚未连接。"; // 未连接PLC + } + var (writeResult, _) = CommonTool.Siemens.WritePlcWhithName($"输送机写任务{task.StackerConveyId}", + task.PlcId!, + task.TaskType!, + task.GetStand!, + task.InTunnelId!, + task.OutTunnelId!, + task.SetStand!, + task.GetQueue!, + task.GetLine!, + task.GetLayer!, + task.SetQueue!, + task.SetLine!, + task.SetLayer!, + task.GetDeep!, + task.SetDeep!, + task.Size!, + task.Weight!, + task.Code + ); + return writeResult.Success ? string.Empty : "写入失败"; + } + + + + + /// + /// 获取过账数据 ---- 地面站控制 + /// + /// + /// + public List? GetConveyTaskFeedBackData(int dataNum) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return default; // 未连接PLC + } + List taskFeedBackEntities = []; + var readResult = CommonTool.Siemens.ReadByteWithName($"过账区", (ushort)(6 * dataNum)); + if (!readResult.Success || readResult.Value == default) { return default; } + var readData = readResult.Value; + for (var i = 0; i < dataNum; i++) + { + TaskFeedBackEntity taskFeedBackEntity = new() + { + PlcId = Convert.ToInt32(CommonTool.Siemens.Trans(readData, 0 + i * 6)), + FeedBackType = Convert.ToInt16(CommonTool.Siemens.Trans(readData, 4 + i * 6)), + CountNo = i + }; + taskFeedBackEntities.Add(taskFeedBackEntity); + } + return taskFeedBackEntities; + } + + /// + /// 重置过账缓冲区 --- 写反馈区间接清除 ---- 地面站控制 + /// + /// + public string? ResetFeedBackData(TaskFeedBackEntity taskFeedBackData) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return "PLC 未连接"; // 未连接PLC + } + var dbAddress = CommonTool.Siemens.GetAddressWithNameAndBit($"过账区反馈区", 4 * taskFeedBackData.CountNo); + if (string.IsNullOrEmpty(dbAddress)) + { + return "无法计算偏移量"; + } + var writeResult = CommonTool.Siemens.WritePlcWhithAddress(1, dbAddress, taskFeedBackData.PlcId); + return writeResult.Success ? default : $"清除过账失败,异常信息:{writeResult.Message}"; + } + + + /// + /// 重置过账缓冲区 --- 直接清除 ---- 地面站控制 + /// + /// + public string? ClearFeedBackData(TaskFeedBackEntity taskFeedBackData) + { + if (!CommonData.IsConnectPlc || CommonTool.Siemens == default) + { + return "PLC 未连接"; // 未连接PLC + } + var dbAddress = CommonTool.Siemens.GetAddressWithNameAndBit($"过账区", 6 * taskFeedBackData.CountNo); + if (string.IsNullOrEmpty(dbAddress)) + { + return "无法计算偏移量"; + } + var writeResult = CommonTool.Siemens.WritePlcWhithAddress(1, dbAddress, 0); + return !writeResult.Success ? $"清除过账失败,异常信息:{writeResult.Message}" : default; + } + +} \ No newline at end of file diff --git a/WcsMain/ExtendMethod/AppLocationExtendMethod.cs b/WcsMain/ExtendMethod/AppLocationExtendMethod.cs new file mode 100644 index 0000000..8ed38db --- /dev/null +++ b/WcsMain/ExtendMethod/AppLocationExtendMethod.cs @@ -0,0 +1,49 @@ +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ExtendMethod; + +/// +/// 库位的扩展方法 +/// +public static class AppLocationExtendMethod +{ + /// + /// 检验是否存在Wmslocation + /// + /// + /// + /// + public static bool ExistWmsLocation(this List? value, params string?[] wmsLocations) + { + if(value == default || wmsLocations == default || wmsLocations.Length < 1) return false; + foreach (var wmsLocation in wmsLocations) + { + if(!value.Exists(e => e.WmsLocation == wmsLocation)) return false; + } + return true; + } + + /// + /// 根据 wmsLocation 获取库位的详细信息 + /// + /// + /// + /// + public static AppLocation? DetailWithWmsLocation(this List? value, string? wmsLocation) + { + if(value == default || wmsLocation == default) return default; + return value.Find(f => f.WmsLocation == wmsLocation); + } + + /// + /// 根据 wcsLocation 获取库位的详细信息 + /// + /// + /// + /// + public static AppLocation? DetailWithWcsLocation(this List? value, string? wcsLocation) + { + if (value == default || wcsLocation == default) return default; + return value.Find(f => f.WcsLocation == wcsLocation); + } +} diff --git a/WcsMain/ExtendMethod/AppStackerExtendMethod.cs b/WcsMain/ExtendMethod/AppStackerExtendMethod.cs new file mode 100644 index 0000000..831eafb --- /dev/null +++ b/WcsMain/ExtendMethod/AppStackerExtendMethod.cs @@ -0,0 +1,22 @@ +using WcsMain.DataBase.TableEntity; + +namespace WcsMain.ExtendMethod; + +/// +/// 堆垛机操作扩展方法 +/// +public static class AppStackerExtendMethod +{ + /// + /// 查找运行使用的 PLC + /// + /// + /// + public static List Open(this List? value) + { + if(value == default) return []; + return value.FindAll(f => f.StackerStatus == 1); + } + + +} diff --git a/WcsMain/ExtendMethod/AppWcsTaskExtendMethod.cs b/WcsMain/ExtendMethod/AppWcsTaskExtendMethod.cs new file mode 100644 index 0000000..61822e6 --- /dev/null +++ b/WcsMain/ExtendMethod/AppWcsTaskExtendMethod.cs @@ -0,0 +1,145 @@ +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.TaskEnum; +using WcsMain.EquipOperation.Entity.Stacker; + +namespace WcsMain.ExtendMethod; + +/// +/// wcs 任务表的扩展方法 +/// +public static class AppWcsTaskExtendMethod +{ + /// + /// 是否是第一个任务 + /// + /// + /// + /// 序号是 1 表示第一个任务 + /// + /// + public static bool IsFirstTask(this AppWcsTask wcsTask) + { + if (wcsTask == default) return false; + return wcsTask.TaskSort == 1; + } + + /// + /// 是否是最后一个任务 + /// + /// + /// + /// 最后一个任务下一任务号是空值 + /// + /// + public static bool IsLastTask(this AppWcsTask wcsTask) + { + if (wcsTask == default) return false; + return wcsTask.NextPlcId == default; + } + + /// + /// 生成一个入库任务 + /// + /// + /// + /// + /// + /// + public static StackerPlcTask ToStackerInTask(this AppWcsTask wcsTask, int stackerId, int forkId, AppLocation destinationDetail) + { + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = wcsTask.PlcId, + TaskType = (int)WcsTaskTypeEnum.inTask, + GetStand = 0, + InTunnelId = Convert.ToInt16(stackerId), + OutTunnelId = Convert.ToInt16(stackerId), + SetStand = 0, + GetQueue = 2, + GetLine = Convert.ToInt16(forkId), + GetLayer = 1, + GetDeep = 1, + SetQueue = Convert.ToInt16(destinationDetail.Queue), + SetLine = Convert.ToInt16(destinationDetail.Line), + SetLayer = Convert.ToInt16(destinationDetail.Layer), + SetDeep = Convert.ToInt16(destinationDetail.Depth), + Size = Convert.ToInt16(wcsTask.VehicleSize), + Weight = Convert.ToInt16(wcsTask.Weight), + Code = wcsTask.PlcVehicleNo ?? 0, + }; + return stackerTask; + } + + + /// + /// 生成一个入库任务 + /// + /// + /// + /// + /// + /// + public static StackerPlcTask ToStackerOutTask(this AppWcsTask wcsTask, int stackerId, int forkId, AppLocation originDetail) + { + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = wcsTask.PlcId, + TaskType = (int)WcsTaskTypeEnum.outTask, + GetStand = 0, + InTunnelId = Convert.ToInt16(stackerId), + OutTunnelId = Convert.ToInt16(stackerId), + SetStand = 0, + GetQueue = Convert.ToInt16(originDetail.Queue), + GetLine = Convert.ToInt16(originDetail.Queue), + GetLayer = Convert.ToInt16(originDetail.Queue), + GetDeep = Convert.ToInt16(originDetail.Queue), + SetQueue = 2, + SetLine = Convert.ToInt16(forkId), + SetLayer = 2, + SetDeep = 1, + Size = Convert.ToInt16(wcsTask.VehicleSize), + Weight = Convert.ToInt16(wcsTask.Weight), + Code = wcsTask.PlcVehicleNo ?? 0, + }; + return stackerTask; + } + + /// + /// 生成一个移库任务 + /// + /// + /// + /// + /// + /// + public static StackerPlcTask ToStackerMoveTask(this AppWcsTask wcsTask, int stackerId, AppLocation originDetail, AppLocation destinationDetail) + { + StackerPlcTask stackerTask = new() + { + StackerId = stackerId, + PlcId = wcsTask.PlcId, + TaskType = (int)WcsTaskTypeEnum.moveTask, + GetStand = 0, + InTunnelId = Convert.ToInt16(stackerId), + OutTunnelId = Convert.ToInt16(stackerId), + SetStand = 0, + GetQueue = Convert.ToInt16(originDetail.Queue), + GetLine = Convert.ToInt16(originDetail.Queue), + GetLayer = Convert.ToInt16(originDetail.Queue), + GetDeep = Convert.ToInt16(originDetail.Queue), + SetQueue = Convert.ToInt16(destinationDetail.Queue), + SetLine = Convert.ToInt16(destinationDetail.Line), + SetLayer = Convert.ToInt16(destinationDetail.Layer), + SetDeep = Convert.ToInt16(destinationDetail.Depth), + Size = Convert.ToInt16(wcsTask.VehicleSize), + Weight = Convert.ToInt16(wcsTask.Weight), + Code = wcsTask.PlcVehicleNo ?? 0, + }; + return stackerTask; + } + + + +} diff --git a/WcsMain/ExtendMethod/StackerExtendMethod.cs b/WcsMain/ExtendMethod/StackerExtendMethod.cs new file mode 100644 index 0000000..8530fbc --- /dev/null +++ b/WcsMain/ExtendMethod/StackerExtendMethod.cs @@ -0,0 +1,53 @@ +using WcsMain.Enum.Stacker; + +namespace WcsMain.ExtendMethod; + +public static class StackerExtendMethod +{ + /// + /// 堆垛机状态转换为文字 + /// + /// + /// + public static string ToMsg(this StackerStatusEnum value) + { + return value switch + { + StackerStatusEnum.offline => "脱机", + StackerStatusEnum.free => "空闲", + StackerStatusEnum.acceptTask => "任务接收", + StackerStatusEnum.getMove => "取货移动", + StackerStatusEnum.getting => "取货中", + StackerStatusEnum.getComplete => "取货完成", + StackerStatusEnum.setMove => "卸货移动", + StackerStatusEnum.setting => "卸货中", + StackerStatusEnum.setComplete => "卸货完成", + StackerStatusEnum.taskComplete => "任务完成", + StackerStatusEnum.deleteTask => "删除任务", + StackerStatusEnum.checking => "盘点中", + StackerStatusEnum.applyTask => "二次预约申请", + _ => "未知状态" + }; + } + + + /// + /// 堆垛机控制方式 + /// + /// + /// + public static string ToMsg(this StackerControlModeEnum value) + { + return value switch + { + StackerControlModeEnum.offline => "离线", + StackerControlModeEnum.selfLearning => "自学习", + StackerControlModeEnum.debug => "调试", + StackerControlModeEnum.manual => "手动", + StackerControlModeEnum.standAlone => "单机", + StackerControlModeEnum.online => "联机", + _ => "未知" + }; + } + +} \ No newline at end of file diff --git a/WcsMain/ExtendMethod/StringExtendMethod.cs b/WcsMain/ExtendMethod/StringExtendMethod.cs new file mode 100644 index 0000000..966cc43 --- /dev/null +++ b/WcsMain/ExtendMethod/StringExtendMethod.cs @@ -0,0 +1,83 @@ +using System.Text.RegularExpressions; + +namespace WcsMain.ExtendMethod; + +/// +/// 扩展方法,检查基础数据 +/// +public static partial class StringExtendMethod +{ + /// + /// 检测一个字符串是不是 NoRead + /// + /// + /// + public static bool IsNoRead(this string? value) + { + if (string.IsNullOrEmpty(value)) return false; + return value.Replace(" ", "").Equals("noread", StringComparison.CurrentCultureIgnoreCase); + } + + /// + /// 检查一个字符串是否是数字 + /// + /// + /// + public static bool IsNumber(this string? value) + { + if (string.IsNullOrEmpty(value)) return false; + return IsNumberRegex().IsMatch(value); + } + + /// + /// 将字符串转换为 bool,只有等于null和0的情况下是false + /// + /// + /// + public static bool ToBool(this string? value) + { + if(value == null) return false; + return value != "0"; + } + + /// + /// 检测一个字符串是不是时间格式,如果是返回时间,如果不是返回 default + /// + /// + /// + public static DateTime? ToDateTime(this string? value) + { + try + { + DateTime time = Convert.ToDateTime(value); + return time; + } + catch + { + return default; + } + } + + + + /// + /// 将载具号转换为 plc 可识别的载具号 + /// + /// + /// + public static int ToPlcVehicleNo(this string? value) + { + if (string.IsNullOrEmpty(value)) return 0; + try + { + return Convert.ToInt32(Regex.Replace(value, "\\D", "")); + } + catch + { + return 0; + } + } + + [GeneratedRegex("^\\d+$")] + private static partial Regex IsNumberRegex(); +} \ No newline at end of file diff --git a/WcsMain/ExtendMethod/TaskExtendMethod.cs b/WcsMain/ExtendMethod/TaskExtendMethod.cs new file mode 100644 index 0000000..bad40bd --- /dev/null +++ b/WcsMain/ExtendMethod/TaskExtendMethod.cs @@ -0,0 +1,25 @@ +namespace WcsMain.ExtendMethod; + +/// +/// 任务扩展方法 +/// +public static class TaskExtendMethod +{ + /// + /// 转换任务类型为文字 + /// + /// + /// + public static string ToTaskTypeStr(this int? task) + { + return task switch + { + 1 => "入库任务", + 2 => "出库任务", + 4 => "拣选任务", + 9 => "移库任务", + -1 => "输送任务", + _ => "未知任务" + }; + } +} \ No newline at end of file diff --git a/WcsMain/Language/Readme.txt b/WcsMain/Language/Readme.txt new file mode 100644 index 0000000..74d9903 --- /dev/null +++ b/WcsMain/Language/Readme.txt @@ -0,0 +1 @@ +多语言版本尚未添加 \ No newline at end of file diff --git a/WcsMain/Plugins/WcsCirculation.cs b/WcsMain/Plugins/WcsCirculation.cs new file mode 100644 index 0000000..8a5bd89 --- /dev/null +++ b/WcsMain/Plugins/WcsCirculation.cs @@ -0,0 +1,54 @@ +using Autofac; +using CirculateTool; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Plugins; + +/// +/// 新的 WCS 定时任务类,添加 IOC 容器支持,添加非静态变量支持 +/// +[Component] +public class WcsCirculation(IComponentContext componentContext) : StartCirculation +{ + private readonly IComponentContext _componentContext = componentContext; + + public override void StartTask(Type type, object[]? instanceParams = null) + { + object? instance = null; + var methods = type.GetMethods(); + foreach (var method in methods) + { + var attributes = method.GetCustomAttributes(false); + foreach (var attribute in attributes) + { + if (attribute is not CirculationAttribute needDurable) continue; + string methodDescription = needDurable.MethodDescription ?? $"{type.Name}.{method.Name}"; + instance ??= CreateInstance(type); + bool Action() => (bool)(method.Invoke(instance, []) ?? false); + StartTask(Action, methodDescription, needDurable.CirculationTime); + break; + } + } + } + + + private object? CreateInstance(Type type) + { + var constructors = type.GetConstructors(); + var constructorList = constructors.ToList(); + constructorList = [.. constructorList.OrderByDescending(s => s.GetParameters().Length)]; + foreach (var constructor in constructorList) + { + var parameters = constructor.GetParameters(); + if (parameters.Length == 0) return Activator.CreateInstance(type); + List arguments = []; + foreach (var parameter in parameters) + { + object par = _componentContext.Resolve(parameter.ParameterType); + arguments.Add(par); + } + return Activator.CreateInstance(type, [.. arguments]); + } + return Activator.CreateInstance(type); + } +} diff --git a/WcsMain/Plugins/WmsWebApiPost.cs b/WcsMain/Plugins/WmsWebApiPost.cs new file mode 100644 index 0000000..97f01a5 --- /dev/null +++ b/WcsMain/Plugins/WmsWebApiPost.cs @@ -0,0 +1,19 @@ +using ApiTool; +using WcsMain.Business.CommonAction; +using WcsMain.Common; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Plugins; + +/// +/// Wms 的API工具 +/// +[Component] +public class WmsWebApiPost : WebApiPost +{ + public WmsWebApiPost(WMSApiResponseAction wmsApiResponseAction) + { + SetResponseAction(wmsApiResponseAction.WMSApiResponse); + SetBaseUrl(CommonData.AppConfig.WmsBaseAddressApiAddress ?? ""); + } +} diff --git a/WcsMain/Program.cs b/WcsMain/Program.cs new file mode 100644 index 0000000..ae92182 --- /dev/null +++ b/WcsMain/Program.cs @@ -0,0 +1,65 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; +using System.Text; +using WcsMain; +using WcsMain.Common; +using WcsMain.StartAction; + +Console.Title = "WCS豸ϵͳ"; +Console.OutputEncoding = Encoding.UTF8; +ConsoleLog.DisbleQuickEditMode(); + +var builder = WebApplication.CreateBuilder(args); + +// лز +var env = builder.Environment; +LoadingRunningData.Loading(env.IsDevelopment()); // ϵͳϢ˲豨 + +// Add services to the container. +//builder.Services.Replace(ServiceDescriptor.Transient()); +builder.Services.AddControllers(options => +{ + //options.Filters.Add(); +}).AddJsonOptions(options => +{ + // ޸ķãԭʵ + options.JsonSerializerOptions.PropertyNamingPolicy = null; +}); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddHostedService(); +// ӿκ˷ +builder.Services.AddCors(options => +{ + options.AddPolicy("any", policyBuilder => + { + policyBuilder.WithOrigins("*").AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); + }); +}); + +builder.WebHost.UseUrls(CommonData.Settings.UseUrls ?? ["http://*:890"]); // ַ appSettings.json ãĬֵ + +// ʹ autoFac 滻ע +builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); +builder.Host.ConfigureContainer(builder => +{ + builder.RegisterModule(); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseCors("any"); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/WcsMain/Properties/PublishProfiles/FolderProfile.pubxml b/WcsMain/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..9f4658d --- /dev/null +++ b/WcsMain/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,23 @@ + + + + + true + false + true + Release + Any CPU + FileSystem + bin\Release\net8.0\publish\ + FileSystem + <_TargetId>Folder + + net8.0 + win-x64 + ed59f010-b3e5-4e19-be65-18053645dfc5 + true + true + + \ No newline at end of file diff --git a/WcsMain/Properties/PublishProfiles/FolderProfile.pubxml.user b/WcsMain/Properties/PublishProfiles/FolderProfile.pubxml.user new file mode 100644 index 0000000..2075663 --- /dev/null +++ b/WcsMain/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -0,0 +1,11 @@ + + + + + <_PublishTargetUrl>D:\file4\2024-5-6 苏州卡特\应用程序\WcsService\WcsMain\bin\Release\net8.0\publish\ + True|2024-06-19T01:50:26.9671568Z;False|2024-06-19T09:46:12.9177219+08:00; + + + \ No newline at end of file diff --git a/WcsMain/Properties/launchSettings.json b/WcsMain/Properties/launchSettings.json new file mode 100644 index 0000000..bcadfed --- /dev/null +++ b/WcsMain/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:29065", + "sslPort": 0 + } + }, + "profiles": { + "WcsMain": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:890", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WcsMain/Socket/SocketOperation.cs b/WcsMain/Socket/SocketOperation.cs new file mode 100644 index 0000000..8e86bc3 --- /dev/null +++ b/WcsMain/Socket/SocketOperation.cs @@ -0,0 +1,76 @@ +using System.Text; +using SocketTool; +using SocketTool.Entity; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.Business.CommonAction; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Socket; + +/// +/// socket 连接类 +/// +[Component] +public class SocketOperation(AppTcpDao socketDao) +{ + private readonly AppTcpDao _socketDao = socketDao; + + /// + /// 连接 socket + /// + public void Connect() + { + /* 查找所有待连接的 socket 信息 */ + List? sockets = _socketDao.GetNeedUseSocket(1); + if (sockets == default || sockets.Count < 1) + { + ConsoleLog.Info($"[提示]没有找到需要连接的 socket 信息,将不会进行连接。"); + return; + } + List socketInfo = []; + foreach (var socket in sockets) + { + socketInfo.Add(socket.TcpIp); + } + /* 注册事件,连接 socket */ + SocketClient socketClient = new(Encoding.UTF8, [.. socketInfo]); + socketClient.SocketConnecting += address => + { + ConsoleLog.Info($"socket {address} 正在连接..."); + }; + socketClient.SocketOffline += address => + { + ConsoleLog.Error($"[警告]socket {address} 失去连接..."); + }; + socketClient.SocketOnline += address => + { + ConsoleLog.Info($"socket {address} 连接成功。"); + }; + socketClient.SocketConnectFail += (address, exception) => + { + ConsoleLog.Error($"[警告]socket {address} 连接失败,异常信息:{exception.Message}"); + }; + socketClient.RecevErrEvent += (address, exception) => + { + ConsoleLog.Error($"[警告]socket {address} 接收数据异常,异常信息:{exception.Message}"); + }; + socketClient.HandleCodeInfo += SocketClientOnHandleCodeInfo; + socketClient.Connect(); + } + + //private ScanCodeAction? scanCodeAction; + + /// + /// 处理收到的数据 + /// + /// + /// + /// 扫码器方法所在的类为 + /// + private void SocketClientOnHandleCodeInfo(ScanCodeClass codeEntity) + { + // 后面单独表配置 + // TODO + } +} \ No newline at end of file diff --git a/WcsMain/StartAction/AutofacModule.cs b/WcsMain/StartAction/AutofacModule.cs new file mode 100644 index 0000000..c1df4a9 --- /dev/null +++ b/WcsMain/StartAction/AutofacModule.cs @@ -0,0 +1,31 @@ +using Autofac; +using System.Reflection; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.StartAction; + +public class AutofacModule : Autofac.Module +{ + /* + * 此处为依赖注入,注入的是单例模式 + */ + + protected override void Load(ContainerBuilder builder) + { + var assembly = Assembly.GetExecutingAssembly(); + // 注册 Service + builder.RegisterAssemblyTypes(assembly) + .Where(w => w.GetCustomAttribute(typeof(ServiceAttribute)) != default) + .SingleInstance(); + // 注册 Component + builder.RegisterAssemblyTypes(assembly) + .Where(w => w.GetCustomAttribute(typeof(ComponentAttribute)) != default) + .SingleInstance(); + // 添加属性注入 --- 一般不建议使用 + builder.RegisterAssemblyTypes(assembly) + .Where(w => w.GetCustomAttribute(typeof(AutowiredAttribute)) != default) + .PropertiesAutowired(new AutowiredSelector()); + + + } +} diff --git a/WcsMain/StartAction/AutowiredSelector.cs b/WcsMain/StartAction/AutowiredSelector.cs new file mode 100644 index 0000000..b2bb043 --- /dev/null +++ b/WcsMain/StartAction/AutowiredSelector.cs @@ -0,0 +1,13 @@ +using Autofac.Core; +using System.Reflection; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.StartAction; + +public class AutowiredSelector : IPropertySelector +{ + public bool InjectProperty(PropertyInfo propertyInfo, object instance) + { + return propertyInfo.GetCustomAttribute(typeof(AutowiredAttribute)) != default; + } +} diff --git a/WcsMain/StartAction/HostService.cs b/WcsMain/StartAction/HostService.cs new file mode 100644 index 0000000..e4b5e09 --- /dev/null +++ b/WcsMain/StartAction/HostService.cs @@ -0,0 +1,76 @@ +using WcsMain.Common; + +namespace WcsMain.StartAction; + +/// +/// 一个类,用于触发启动关闭事件 +/// +public class HostService(ServiceStart serverStart) : IHostedLifecycleService +{ + private readonly ServiceStart _serverStart = serverStart; + + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StartingAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StartedAsync(CancellationToken cancellationToken) + { + return Task.Run(() => + { + _serverStart.LoadingData(); // 加载必要参数 + string? loadingResult = LoadingRunningData.GetResult(); + if (!string.IsNullOrEmpty(loadingResult)) + { + ConsoleLog.Error($"【异常】启动加载运行文件出错,WCS功能受到限制,您可以检查网络连接后重新启动或者联系我们,参考信息:{loadingResult}"); + return; + } + ConsoleLog.Info("WCS服务启动中,请稍后..."); + var apiOnly = CommonData.Settings.ApplicationConfig.ApiOnly; + if (apiOnly == null) + { + ConsoleLog.Error("【异常】配置文件加载失败,WCS 启动失败"); + return; + } + if ((bool)apiOnly) + { + ConsoleLog.Warning("【警告】当前运行在 【仅 API】 模式下"); + } + else + { + _serverStart.Start(); + } + ConsoleLog.Success("WCS服务启动完成"); + }, cancellationToken); + } + + + + /// + /// 关闭事件 + /// + /// + /// + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StoppedAsync(CancellationToken cancellationToken) + { + return Task.Run(() => + { + ConsoleLog.Info("WCS服务已经关闭"); + }, cancellationToken); + } + + public Task StoppingAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/WcsMain/StartAction/LoadingRunningData.cs b/WcsMain/StartAction/LoadingRunningData.cs new file mode 100644 index 0000000..993e305 --- /dev/null +++ b/WcsMain/StartAction/LoadingRunningData.cs @@ -0,0 +1,116 @@ +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using WcsMain.Common; +using SqlSugar; +using WcsMain.AppEntity.SystemData; + +namespace WcsMain.StartAction; + +/// +/// WCS 程序启动前加载运行数据 ---- 链式调用,返回错误信息 +/// +public class LoadingRunningData +{ + /// + /// 错误信息 + /// + private static string _errMsg = string.Empty; + + /// + /// 是否是开发环境 + /// + private static bool _isDevelopment = true; + + /// + /// 加载系统运行数据数据 + /// + /// + public static void Loading(bool isDevelopment) + { + _isDevelopment = isDevelopment;// 将开发环境暴露到此类全局 + + LoadingAppSetting(); // 加载 Appsettings.json 文件里的设置项 + LoadingWcsDataBase(); // 加载 Wcs 数据库连接字符串 + + // 加载数据库中的 + + } + + /// + /// 返回加载结果 + /// + /// + public static string? GetResult() => _errMsg; + + + /// + /// 加载 Appsettings.json 文件里的设置项 + /// + public static void LoadingAppSetting() + { + if (!string.IsNullOrEmpty(_errMsg)) return; + ConsoleLog.Info("正在加载配置文件..."); + string setStr; + try + { + using (StreamReader sr = new("appsettings.json")) + { + setStr = sr.ReadToEnd(); + } + JObject jsonObj = JObject.Parse(setStr); + JObject? settinsOnj = (JObject?)jsonObj["Settings"]; + if (settinsOnj == default) + { + _errMsg = "加载系统设置文件失败,请检查是否存在 Settings 键"; + return; + } + CommonData.Settings = JsonConvert.DeserializeObject(settinsOnj.ToString()); + } + catch(Exception ex) + { + _ = ex; + _errMsg = "加载系统设置文件失败,请检查是否存在 appsettings.json 文件"; + } + } + + + + /// + /// 加载数据库文件 + /// + private static void LoadingWcsDataBase() + { + if (!string.IsNullOrEmpty(_errMsg)) return; + ConsoleLog.Info("正在加载数据库配置..."); + string wcsDataBaseConnectString; + if (_isDevelopment) + { + if(string.IsNullOrEmpty(CommonData.Settings.DBMysqlLocal)) + { + _errMsg = "调试环境下加载WCS数据库连接字符串失败"; + return; + } + wcsDataBaseConnectString = CommonData.Settings.DBMysqlLocal; + } + else + { + if (string.IsNullOrEmpty(CommonData.Settings.DBMysql)) + { + _errMsg = "运行环境下加载WCS数据库连接字符串失败"; + return; + } + wcsDataBaseConnectString = CommonData.Settings.DBMysql; + } + CommonTool.DbServe = new SqlSugarScope(new ConnectionConfig() + { + IsAutoCloseConnection = true, + ConfigId = "0", + DbType = DbType.MySql, + ConnectionString = wcsDataBaseConnectString + }); + } + + + + +} diff --git a/WcsMain/StartAction/ServiceStart.cs b/WcsMain/StartAction/ServiceStart.cs new file mode 100644 index 0000000..7c3f4bf --- /dev/null +++ b/WcsMain/StartAction/ServiceStart.cs @@ -0,0 +1,216 @@ +using WcsMain.Socket; +using WcsMain.Common; +using WcsMain.WcsAttribute.AutoFacAttribute; +using WcsMain.Plugins; +using System.Reflection; +using WcsMain.AppEntity.SystemData; +using WcsMain.DataBase.Dao; +using WcsMain.DataBase.TableEntity; +using WcsMain.WcsAttribute.AppConfig; +using WcsMain.Business.Convey; +using WcsMain.ElTag.Atop; +using WcsMain.EquipOperation; + +namespace WcsMain.StartAction; + +/// +/// 服务启动主体类 +/// +[Component] +public class ServiceStart(WcsCirculation wcsCirculation, ConnectPLCs connectPLCs, SocketOperation socketOperation, + AppStackerDao appStackerDao, AppLocationDao appLocationDao, AppConfigDao appConfigDao, ConnectPlcServe connectPlcServe, + ConnectOprServe connectOprServe, AppElTagBaseDao appElTagBaseDao) +{ + private readonly AppConfigDao _appconfigDao = appConfigDao; + private readonly AppLocationDao _applocationDao = appLocationDao; + private readonly AppStackerDao _appStackerDao = appStackerDao; + private readonly SocketOperation _socketOperation = socketOperation; + private readonly ConnectPLCs _connectPlcs = connectPLCs; + private readonly WcsCirculation _wcsCirculation = wcsCirculation; + private readonly ConnectPlcServe _connectPlcServe = connectPlcServe; + private readonly ConnectOprServe _connectOprServe = connectOprServe; + private readonly AppElTagBaseDao _appElTagBaseDao = appElTagBaseDao; + + private static string _errMsg = string.Empty; + + /// + /// 加载必要参数 + /// + public void LoadingData() + { + LoadingConfig(); // 加载数据库中的配置项 (config 表) + LoadingStackerData(); // 加载数据库中的堆垛机信息 + LoadingLocationData(); // 加载数据库中的库位信息 + LoadElTagBase(); // 加载电子标签基础资料 + + if (!string.IsNullOrEmpty(_errMsg)) + { + ConsoleLog.Error($"【异常】启动加载运行文件出错,WCS功能受到限制,您可以检查网络连接后重新启动或者联系我们,参考信息:{_errMsg}"); + return; + } + } + + /// + /// 启动 ——-- 主方法 + /// + public void Start() + { + /* 指定线程池规格 */ + ThreadPool.SetMinThreads(30, 10); + + CreatePlcClient(); // 连接 PLC 客户端 + CreateSocketClient(); // Socket客户端 + ConnectOprServe(); // 连接 Opr 电子标签客户端 + CreateCieculateTask();// 创建并启用定时器 + } + + /// + /// 创建并连接 Plc + /// + public void CreatePlcClient() + { + if (CommonData.AppConfig.UseConnectPlc == "1") // 1 表示允许连接PLC + { + _connectPlcs.ConnectPlc(); + } + } + + /// + /// 创建并运行 Socket 客户端 ---- 待更新 + /// + public void CreateSocketClient() + { + if (CommonData.AppConfig.UseSocket == "1") // 1 表示允许开启Socket + { + _socketOperation.Connect(); + } + } + + /// + /// 连接Opr电子标签客户端 + /// + public void ConnectOprServe() + { + if (CommonData.AppConfig.UseOpr == "1") // 1 表示允许连接电子标签 + { + _connectOprServe.SetBaseAction(); + _connectOprServe.Connect(); + } + } + + + + public void CreateTcpClient() + { + _connectPlcServe.SetBaseAction(); + _connectPlcServe.ConnectPlc(); + } + + + /// + /// 创建并启用定时器 + /// + public void CreateCieculateTask() + { + if (CommonData.AppConfig.UseCirculation == "1") // 1 表示允许开启 定时器 + { + _wcsCirculation.ExceptionHandler += (methodDescription, ex) => + { + ConsoleLog.Error($"定时器:{methodDescription} 发生异常,异常信息:{ex}"); + }; + _wcsCirculation.MessageHandler += (msg) => + { + //ConsoleLog.Tip(msg); + }; + _wcsCirculation.StartAssemblyCirculation(GetType().Assembly); + } + } + + + /// + /// 加载数据库内的 Config 配置信息 + /// + public void LoadingConfig() + { + if (!string.IsNullOrEmpty(_errMsg)) return; + ConsoleLog.Info("正在加载数据库配置信息..."); + List? Configs = _appconfigDao.Query(); + if (Configs == default) + { + _errMsg = "加载 Config 资源失败,请检查数据库服务器连接是否正常"; + return; + } + AppConfigEntity? appConfigEntity = new(); + var type = appConfigEntity.GetType(); + var propertys = type.GetProperties(); + foreach (var property in propertys) + { + string? propertyName = property.Name; + var attribute = property.GetCustomAttribute(typeof(ConfigKeyAttribute)); + if (attribute != default) + { + var configKeyAttribute = attribute as ConfigKeyAttribute; + if (configKeyAttribute != default && configKeyAttribute.KeyName != default) + { + propertyName = configKeyAttribute.KeyName; + } + } + AppConfig? appConfig = Configs.Find(f => f.ConfigKey == propertyName); + if (appConfig == default) continue; + property.SetValue(appConfigEntity, appConfig.ConfigValue); + } + CommonData.AppConfig = appConfigEntity; + } + + /// + /// 加载堆垛机数据 + /// + public void LoadingStackerData() + { + if (!string.IsNullOrEmpty(_errMsg)) return; + ConsoleLog.Info("正在加载堆垛机参数信息..."); + List? appStackers = _appStackerDao.Select(); + if (appStackers == default) + { + _errMsg = "加载堆垛机参数信息失败,请检查数据库服务器连接是否正常"; + CommonData.AppStackers = []; + return; + } + CommonData.AppStackers = appStackers; + } + + + /// + /// 加载库位信息 + /// + public void LoadingLocationData() + { + if (!string.IsNullOrEmpty(_errMsg)) return; + ConsoleLog.Info("正在加载库位信息..."); + List? appLocations = _applocationDao.Select(); + if (appLocations == default) + { + _errMsg = "加载库位信息失败,请检查数据库服务器连接是否正常"; + CommonData.AppLocations = []; + return; + } + CommonData.AppLocations = appLocations; + } + + /// + /// 加载电子标签基础资料 + /// + public void LoadElTagBase() + { + if (!string.IsNullOrEmpty(_errMsg)) return; + ConsoleLog.Info("正在加载库位信息..."); + List? eltags = _appElTagBaseDao.Query(); + if (eltags == default) + { + _errMsg = "加载电子标签基础信息失败,请检查数据库服务器连接是否正常"; + CommonData.AppElTags = []; + return; + } + CommonData.AppElTags = eltags; + } +} \ No newline at end of file diff --git a/WcsMain/StaticData/StaticString.cs b/WcsMain/StaticData/StaticString.cs new file mode 100644 index 0000000..5182285 --- /dev/null +++ b/WcsMain/StaticData/StaticString.cs @@ -0,0 +1,13 @@ +namespace WcsMain.StaticData; + +/// +/// 常量字符串 +/// +public static class StaticString +{ + public const string WMS = "WMS"; + public const string WCS = "WCS"; + public const string PLC = "PLC"; + + +} diff --git a/WcsMain/Tcp/Client/BaseTcpClient.cs b/WcsMain/Tcp/Client/BaseTcpClient.cs new file mode 100644 index 0000000..afe9d2a --- /dev/null +++ b/WcsMain/Tcp/Client/BaseTcpClient.cs @@ -0,0 +1,314 @@ +using System.Text; +using System.Text.RegularExpressions; +using WcsMain.DataBase.TableEntity; +using WcsMain.Tcp.Entity; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Tcp.Client; + +/// +/// Wcs 的Tcp 客户端 +/// +public class BaseTcpClient +{ + /// + /// 连接中事件 + /// + private Action? _connecting; + + /// + /// 连接失败事件 + /// + private Action? _connectFail; + + /// + /// 连接成功事件 + /// + private Action? _connectSuccess; + + /// + /// 连接断开事件 + /// + private Action? _connectOffline; + + /// + /// 获取数据事件 + /// + private Action? _getMessage; + + /// + /// 获取数据事件 + /// + private Action? _getData; + + + public void SetConnecting(Action action) => _connecting = action; + public void SetConnectFailAction(Action action) => _connectFail = action; + public void SetConnectSuccess(Action action) => _connectSuccess = action; + public void SetConnectOffline(Action action) => _connectOffline = action; + public void GetMessage(Action action) => _getMessage = action; + public void SetGetData(Action action) => _getData = action; + + /// + /// 当前需要连接的服务端数量 + /// + protected List tcpServeConnectionDatas = []; + + /// + /// 添加服务端基础数据 + /// + /// + public void SetBaseTcpServe(List? tcps) + { + if (tcps == default || tcps.Count == 0) return; + foreach (var tcp in tcps) + { + if (!CheckIpAndPort(tcp.TcpIp, tcp.TcpPort)) + { + ConsoleLog.Warning($"【警告】TCP服务端地址:{tcp.TcpIp}:{tcp.TcpPort} 格式不满足要求"); + continue; + } + tcpServeConnectionDatas.Add(new() + { + DisplayName = tcp.DisplayName, + TcpServe = tcp + }); + } + } + + /// + /// 开始连接服务端 + /// + public void Connect() + { + if (tcpServeConnectionDatas.Count < 1) return; + tcpServeConnectionDatas.ForEach(ConnectAsync); + ReConnectAsync(); // 开启重连属性 + MonitorServeConnectedAsync(); // 检测客户端是否断开 + } + + /// + /// 连接到服务端并接收数据 + /// + /// + private async void ConnectAsync(TcpServeConnectionData serveData) + { + try + { + if(serveData.IsConnected == Enum.General.TrueFalseEnum.TRUE) return; + _connecting?.Invoke(serveData); + serveData.TcpClient = new System.Net.Sockets.TcpClient(); + serveData.TcpClient.Connect(serveData.TcpServe!.TcpIp!, (int)serveData.TcpServe.TcpPort!); + tcpServeConnectionDatas.Find(tcp => tcp.DisplayName == serveData.DisplayName)!.IsConnected = Enum.General.TrueFalseEnum.TRUE; + _connectSuccess?.Invoke(serveData); + while (true) + { + var networkStream = serveData.TcpClient.GetStream(); + byte[] bytes = new byte[1024]; + int readLength = await networkStream.ReadAsync(bytes); + if (readLength > 0) + { + bytes = bytes.Take(readLength).ToArray(); + serveData.RecvMsgTime = DateTime.Now; + _getData?.Invoke(serveData, bytes); + _getMessage?.Invoke(serveData, Encoding.ASCII.GetString(bytes)); + continue; + } + _connectOffline?.Invoke(serveData); + tcpServeConnectionDatas.Find(tcp => tcp.DisplayName == serveData.DisplayName)!.IsConnected = Enum.General.TrueFalseEnum.FALSE; + break; + } + } + catch (Exception ex) + { + _ = ex; + tcpServeConnectionDatas.Find(tcp => tcp.DisplayName == serveData.DisplayName)!.IsConnected = Enum.General.TrueFalseEnum.FALSE; + _connectFail?.Invoke(serveData, ex); + } + } + + /// + /// 重连 + /// + private async void ReConnectAsync() + { + if (tcpServeConnectionDatas.Count < 1) return; + CancellationTokenSource cts = new(); + PeriodicTimer timer = new(new TimeSpan(0, 0, 0, 1, 0)); + while (await timer.WaitForNextTickAsync(cts.Token)) + { + foreach (var tcpServe in tcpServeConnectionDatas) + { + if (tcpServe.IsConnected != Enum.General.TrueFalseEnum.FALSE) continue; + tcpServe.IsConnected = default; + ConnectAsync(tcpServe); + } + } + } + + /// + /// 监控客户端是否断开 + /// + public virtual async void MonitorServeConnectedAsync() + { + if (tcpServeConnectionDatas.Count < 1) return; + CancellationTokenSource cts = new(); + PeriodicTimer timer = new(new TimeSpan(0, 0, 0, 1, 0)); + while (await timer.WaitForNextTickAsync(cts.Token)) + { + List checkTasks = []; + foreach (var tcpServe in tcpServeConnectionDatas) + { + if (tcpServe.IsConnected == Enum.General.TrueFalseEnum.FALSE || tcpServe.IsConnected == default) continue; + // 检测与服务端是否断开 ---- 多种检测方式并存 + checkTasks.Add(Task.Factory.StartNew(() => + { + // Ping 地址 + bool pingok = false; + for(int i = 0; i < 4; i++) + { + System.Net.NetworkInformation.Ping ping = new(); + var pingResult = ping.Send(tcpServe.TcpServe!.TcpIp!, 100); + if (pingResult.Status != System.Net.NetworkInformation.IPStatus.Success) continue; + pingok = true; + break; + } + if(!pingok) + { + tcpServe.TcpClient?.Close(); + //tcpServe.IsConnected = Enum.General.TrueFalseEnum.FALSE; + return; + } + if (tcpServe.TcpClient == default) return; + // 尝试发送消息 + try + { + var networkStream = tcpServe.TcpClient.GetStream(); + byte[] bytes = []; + networkStream.Write(bytes); + } + catch + { + tcpServe.TcpClient?.Close(); + //tcpServe.IsConnected = Enum.General.TrueFalseEnum.FALSE; + return; + } + })); + } + Task.WaitAll([.. checkTasks]); + } + } + + /// + /// 向指定别称的客户端发送消息 + /// + /// + /// + /// + public TcpClientSendResult Send(byte[] value, params string[] displayNames) + { + TcpClientSendResult tcpClientSendResult = new() { Success = true }; + // 指定发送 + List sendTasks = []; + foreach (var tcpServe in tcpServeConnectionDatas) + { + if (displayNames.Length > 0 && !displayNames.Contains(tcpServe.DisplayName)) { continue; } + if (tcpServe.TcpClient == default || tcpServe.IsConnected != Enum.General.TrueFalseEnum.TRUE) + { + tcpClientSendResult = new() { Success = false, Exception = new Exception($"别称:{tcpServe.DisplayName} 的Tcp连接不可用") }; + } + else + { + sendTasks.Add(Task.Factory.StartNew(() => + { + var sendNetworkStream = tcpServe.TcpClient.GetStream(); + try + { + sendNetworkStream.Write(value); + } + catch (Exception ex) + { + tcpClientSendResult = new() { Success = false, Exception = ex }; + } + })); + } + } + if (sendTasks.Count == 0) return new() { Success = false, Exception = new Exception("没有要发送的客户端") }; + Task.WaitAll([.. sendTasks]); + return tcpClientSendResult; + } + + /// + /// 向指定别称的客户端发送消息 + /// + /// + /// + /// + public TcpClientSendResult Send(string? value, params string[] displayNames) + { + if(string.IsNullOrEmpty(value)) + { + return new() { Success = false, Exception = new Exception("传入的值为空") }; + } + TcpClientSendResult tcpClientSendResult = new() { Success = true }; + // 指定发送 + List sendTasks = []; + foreach (var tcpServe in tcpServeConnectionDatas) + { + if (displayNames.Length > 0 && !displayNames.Contains(tcpServe.DisplayName)) { continue; } + if (tcpServe.TcpClient == default || tcpServe.IsConnected != Enum.General.TrueFalseEnum.TRUE) + { + tcpClientSendResult = new() { Success = false, Exception = new Exception($"别称:{tcpServe.DisplayName} 的Tcp连接不可用") }; + } + else + { + sendTasks.Add(Task.Factory.StartNew(() => + { + var sendNetworkStream = tcpServe.TcpClient.GetStream(); + try + { + sendNetworkStream.Write(Encoding.ASCII.GetBytes(value)); + } + catch (Exception ex) + { + tcpClientSendResult = new() { Success = false, Exception = ex }; + } + })); + } + } + if(sendTasks.Count == 0) return new() { Success = false, Exception = new Exception("没有要发送的客户端") }; + Task.WaitAll([.. sendTasks]); + return tcpClientSendResult; + } + + + + + + + + + + + + + + /// + /// 检查IP地址和端口号是否满足格式 + /// 如果格式错误返回0,否则返回Int格式的端口号 + /// + /// + /// + /// + private static bool CheckIpAndPort(string? ip, int? port) + { + /* 检测 ip 格式是否正确 */ + if (string.IsNullOrEmpty(ip)) return false; + string ipRegexString = "^((1[0-9][0-9]\\.)|(2[0-4][0-9]\\.)|(25[0-5]\\.)|([1-9][0-9]\\.)|([0-9]\\.)){3}((1[0-9][0-9])|(2[0-4][0-9])|(25[0-5])|([1-9][0-9])|([0-9]))$"; + bool isIpOk = Regex.IsMatch(ip, ipRegexString); + if (!isIpOk) return false; + /* 检测 port 端口是否正确 */ + return port >= 1 && port < 65535; + } + +} diff --git a/WcsMain/Tcp/Client/PlcTcpClient.cs b/WcsMain/Tcp/Client/PlcTcpClient.cs new file mode 100644 index 0000000..766a0e6 --- /dev/null +++ b/WcsMain/Tcp/Client/PlcTcpClient.cs @@ -0,0 +1,101 @@ +using System.Diagnostics; +using System.Text.RegularExpressions; +using WcsMain.Tcp.Entity; +using WcsMain.Tcp.Entity.Message; +using WcsMain.WcsAttribute.AutoFacAttribute; + +namespace WcsMain.Tcp.Client; + +/// +/// 专用与连接 PLC 的Tcp客户端 +/// +public class PlcTcpClient : BaseTcpClient +{ + + /// + /// 存储收到的消息 + /// + private Dictionary msgInfos = []; + + /// + /// 重写检测离线方法 ---- 上一次传输数据的时间和当前时间差 3 秒判断断开 + /// + public async override void MonitorServeConnectedAsync() + { + if (tcpServeConnectionDatas.Count < 1) return; + CancellationTokenSource cts = new(); + PeriodicTimer timer = new(new TimeSpan(0, 0, 0, 1, 0)); + while (await timer.WaitForNextTickAsync(cts.Token)) + { + foreach (var tcpServe in tcpServeConnectionDatas) + { + if (tcpServe.IsConnected == Enum.General.TrueFalseEnum.FALSE || tcpServe.IsConnected == default) continue; + // 计算上一次接收消息的时间和当前的时间差,超过三秒判断断开 + var lastRevMsgTime = tcpServe.RecvMsgTime; + var timespan = DateTime.Now - lastRevMsgTime; + var timespanSeconds = timespan.TotalSeconds; + if (timespanSeconds > 3) // 心跳超时时间 + { + tcpServe.TcpClient?.Close(); + tcpServe.IsConnected = Enum.General.TrueFalseEnum.FALSE; + continue; + } + } + } + } + + /// + /// 发送信息到指定别称的客户端 + /// + /// + /// + /// + /// + public PlcTcpClientSendResult SendWithResult(string? value, string? displayName, int timeout) + { + if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(displayName)) + { + return new() { Success = false, Exception = new Exception("传入的参数为空") }; + } + MsgHeader? header = GetMsgHeader(value); + if(header == default || string.IsNullOrEmpty(header.MsgId)) return new() { Success = false, Exception = new Exception("发送的信息不是标准报文格式") }; + Stopwatch stopwatch = Stopwatch.StartNew(); + var sendResult = Send(value, displayName); // 发送消息 + if(!sendResult.Success) return new() { Success = sendResult.Success, Exception = sendResult.Exception }; // 发送失败 + MsgInfo? msgInfo = default; + while (stopwatch.ElapsedMilliseconds < timeout) + { + bool exist = msgInfos.TryGetValue(header.MsgId, out msgInfo); + if (exist) break; + } + if(msgInfo == default) return new() { Success = false, Exception = new Exception("发送的信息不是标准报文格式"), UseTime = stopwatch.ElapsedMilliseconds}; + return new() + { + Success = true, + ResponseData = msgInfo, + UseTime = stopwatch.ElapsedMilliseconds + }; + } + + /// + /// 获取msg的Header + /// + /// + /// + public MsgHeader? GetMsgHeader(string? value) + { + if (string.IsNullOrEmpty(value)) return default; + if (!Regex.IsMatch(value, "^<.+\\>$")) return default; + string[] msgDatas = value.Split(';'); + if(msgDatas.Length < 4 ) return default; + var msgHeader = new MsgHeader() + { + MsgType = msgDatas[0].Replace("<", ""), + ClientId = msgDatas[1], + MsgId = msgDatas[2], + MsgTag = msgDatas[3], + }; + return msgHeader; + } + +} diff --git a/WcsMain/Tcp/Entity/Convey/GetRouterData.cs b/WcsMain/Tcp/Entity/Convey/GetRouterData.cs new file mode 100644 index 0000000..171343a --- /dev/null +++ b/WcsMain/Tcp/Entity/Convey/GetRouterData.cs @@ -0,0 +1,63 @@ +using WcsMain.Tcp.Entity.Message; + +namespace WcsMain.Tcp.Entity.Convey; + +/// +/// PLC 向WCS请求分拣口时的请求数据 +/// +public class GetRouterData : MsgHeader +{ + /// + /// 请求位置 + /// + public string? Area { get; set; } + + /// + /// 分拣口庄涛 + /// + public string? SortStatus { get; set; } + + /// + /// 条码 + /// + public string? Code { get; set; } + + /// + /// 尺寸 + /// + public string? Size { get; set; } + + /// + /// 重量 + /// + public decimal? Weight { get; set; } + + /// + /// 长 + /// + public decimal? Length { get; set; } + + /// + /// 宽 + /// + public decimal? Width { get; set; } + + /// + /// 高 + /// + public decimal? Hight { get; set; } + + /// + /// 备用1 + /// + public string? Spare1 { get; set; } + + /// + /// 备用2 + /// + public string? Spare2 { get; set; } + + + + +} diff --git a/WcsMain/Tcp/Entity/Convey/SetRouterData.cs b/WcsMain/Tcp/Entity/Convey/SetRouterData.cs new file mode 100644 index 0000000..57c9c62 --- /dev/null +++ b/WcsMain/Tcp/Entity/Convey/SetRouterData.cs @@ -0,0 +1,78 @@ +using WcsMain.Tcp.Entity.Message; + +namespace WcsMain.Tcp.Entity.Convey; + +/// +/// 回复 PLC 路向的实体 +/// +public class SetRouterData : MsgHeader +{ + /// + /// 消息标记 + /// + public string MagTag { get; set; } = "STROUTER"; + + /// + /// 请求位置 + /// + public string? Area { get; set; } + + /// + /// 条码 + /// + public string? Code { get; set; } + + /// + /// 尺寸 + /// + public string? Size { get; set; } = "0"; + + /// + /// 重量 + /// + public decimal Weight { get; set; } = decimal.Zero; + + /// + /// 长 + /// + public decimal Length { get; set; } = decimal.Zero; + + /// + /// 宽 + /// + public decimal Width { get; set; } = decimal.Zero; + + /// + /// 高 + /// + public decimal Hight { get; set; } = decimal.Zero; + + /// + /// 任务号 + /// + public int? PlcId { get; set; } + + /// + /// 路向 + /// + public int? Router { get; set; } + + /// + /// 备用1 + /// + public string? Spare1 { get; set; } + + /// + /// 备用2 + /// + public string? Spare2 { get; set; } + + + public override string ToString() + { + string body = string.Join(';', MsgType, ClientId, MsgId, MsgTag, Area, Code, Size, Weight.ToString("N4"), + Length.ToString("N4"), Width.ToString("N4"), Hight.ToString("N4"), PlcId, Router, Spare1, Spare2); + return string.Concat("<", body, ">"); + } + +} diff --git a/WcsMain/Tcp/Entity/Message/MsgHeader.cs b/WcsMain/Tcp/Entity/Message/MsgHeader.cs new file mode 100644 index 0000000..1d8729f --- /dev/null +++ b/WcsMain/Tcp/Entity/Message/MsgHeader.cs @@ -0,0 +1,29 @@ +namespace WcsMain.Tcp.Entity.Message; + +/// +/// PLC 报文的消息头 +/// +public class MsgHeader +{ + /// + /// 消息类型 + /// + public string? MsgType { get; set; } + + /// + /// 客户端编号 + /// + public string? ClientId { get; set; } + + /// + /// 消息号 + /// + public string? MsgId { get; set; } + + /// + /// 消息标记 + /// + public string? MsgTag { get; set; } + + +} diff --git a/WcsMain/Tcp/Entity/Message/MsgInfo.cs b/WcsMain/Tcp/Entity/Message/MsgInfo.cs new file mode 100644 index 0000000..84002fc --- /dev/null +++ b/WcsMain/Tcp/Entity/Message/MsgInfo.cs @@ -0,0 +1,35 @@ +namespace WcsMain.Tcp.Entity.Message; + +/// +/// PLC报文的详细信息 +/// +public class MsgInfo +{ + /// + /// 收到消息的服务端的别称 + /// + public string? DisplayName { get; set; } + + /// + /// 消息编号 + /// + public string? MsgId { get; set; } + + /// + /// 消息头 + /// + public MsgHeader? Header { get; set; } + + /// + /// 完整消息内容 + /// + public string? Message { get; set; } + + /// + /// 收到消息的时间 + /// + public DateTime RecvDatetime { get; set; } + + + +} diff --git a/WcsMain/Tcp/Entity/PlcTcpClientSendResult.cs b/WcsMain/Tcp/Entity/PlcTcpClientSendResult.cs new file mode 100644 index 0000000..00cb4fe --- /dev/null +++ b/WcsMain/Tcp/Entity/PlcTcpClientSendResult.cs @@ -0,0 +1,16 @@ +using WcsMain.Tcp.Entity.Message; + +namespace WcsMain.Tcp.Entity; + +public class PlcTcpClientSendResult : TcpClientSendResult +{ + /// + /// 返回的信息 + /// + public MsgInfo? ResponseData { get; set; } + + /// + /// 耗时 + /// + public long? UseTime { get; set; } +} diff --git a/WcsMain/Tcp/Entity/TcpClientSendResult.cs b/WcsMain/Tcp/Entity/TcpClientSendResult.cs new file mode 100644 index 0000000..2fd0933 --- /dev/null +++ b/WcsMain/Tcp/Entity/TcpClientSendResult.cs @@ -0,0 +1,19 @@ +namespace WcsMain.Tcp.Entity; + +/// +/// 作为 Tcp客户端发送时的返回信息 +/// +public class TcpClientSendResult +{ + /// + /// 发送是否成功 + /// + public bool Success { get; set; } + + /// + /// 发送时的异常 + /// + public Exception? Exception { get; set; } + + +} diff --git a/WcsMain/Tcp/Entity/TcpServeConnectionData.cs b/WcsMain/Tcp/Entity/TcpServeConnectionData.cs new file mode 100644 index 0000000..5e93bea --- /dev/null +++ b/WcsMain/Tcp/Entity/TcpServeConnectionData.cs @@ -0,0 +1,49 @@ +using System.Net.Sockets; +using WcsMain.DataBase.TableEntity; +using WcsMain.Enum.General; + +namespace WcsMain.Tcp.Entity; + + +/// +/// 用于存储 TCP 连接的服务端信息 +/// +public class TcpServeConnectionData +{ + + /// + /// 基础信息 + /// + public AppTcp? TcpServe { get; set; } + + /// + /// 别称 + /// + public string? DisplayName { get; set; } + + /// + /// Tcp客户端连接 + /// + public TcpClient? TcpClient { get; set; } + + /// + /// 是否连接 + /// + /// + /// 刚创建时是null,断开是false,连接正常是true + /// + public TrueFalseEnum? IsConnected { get; set; } + + /// + /// 上次接收消息的事件 + /// + public DateTime RecvMsgTime { get; set; } = DateTime.Now; + + + public override string ToString() + { + return $"{TcpServe?.TcpIp}:{TcpServe?.TcpPort}"; + } + + +} diff --git a/WcsMain/WcsAttribute/AppConfig/ConfigKeyAttribute.cs b/WcsMain/WcsAttribute/AppConfig/ConfigKeyAttribute.cs new file mode 100644 index 0000000..d585ca7 --- /dev/null +++ b/WcsMain/WcsAttribute/AppConfig/ConfigKeyAttribute.cs @@ -0,0 +1,16 @@ +namespace WcsMain.WcsAttribute.AppConfig; + +/// +/// 数据库配置键的映射 +/// +/// +[AttributeUsage(AttributeTargets.Property)] +public class ConfigKeyAttribute(string? keyName) : Attribute +{ + /// + /// 配置键名称 + /// + public string? KeyName { get; set; } = keyName; + + +} diff --git a/WcsMain/WcsAttribute/AutoFacAttribute/AutowiredAttribute.cs b/WcsMain/WcsAttribute/AutoFacAttribute/AutowiredAttribute.cs new file mode 100644 index 0000000..b757e9c --- /dev/null +++ b/WcsMain/WcsAttribute/AutoFacAttribute/AutowiredAttribute.cs @@ -0,0 +1,10 @@ +namespace WcsMain.WcsAttribute.AutoFacAttribute; + + +/// +/// 属性注入标记 +/// +[AttributeUsage(AttributeTargets.All)] +public class AutowiredAttribute : Attribute +{ +} diff --git a/WcsMain/WcsAttribute/AutoFacAttribute/ComponentAttribute.cs b/WcsMain/WcsAttribute/AutoFacAttribute/ComponentAttribute.cs new file mode 100644 index 0000000..93f4e3e --- /dev/null +++ b/WcsMain/WcsAttribute/AutoFacAttribute/ComponentAttribute.cs @@ -0,0 +1,7 @@ +namespace WcsMain.WcsAttribute.AutoFacAttribute; + +/// +/// 组件 +/// +[AttributeUsage(AttributeTargets.Class)] +public class ComponentAttribute : Attribute { } diff --git a/WcsMain/WcsAttribute/AutoFacAttribute/ServiceAttribute.cs b/WcsMain/WcsAttribute/AutoFacAttribute/ServiceAttribute.cs new file mode 100644 index 0000000..bf7478c --- /dev/null +++ b/WcsMain/WcsAttribute/AutoFacAttribute/ServiceAttribute.cs @@ -0,0 +1,10 @@ +namespace WcsMain.WcsAttribute.AutoFacAttribute; + + +/// +/// 服务特性标记,添加该特性的类会被IOC容器接管 +/// +[AttributeUsage(AttributeTargets.Class)] +public class ServiceAttribute : Attribute +{ +} diff --git a/WcsMain/WcsMain.csproj b/WcsMain/WcsMain.csproj new file mode 100644 index 0000000..9c233f6 --- /dev/null +++ b/WcsMain/WcsMain.csproj @@ -0,0 +1,56 @@ + + + + net8.0 + enable + enable + Exe + AnyCPU;x64;x86 + + + + + + + + + + + + + + + Always + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WcsMain/WcsMain.csproj.user b/WcsMain/WcsMain.csproj.user new file mode 100644 index 0000000..f198f3b --- /dev/null +++ b/WcsMain/WcsMain.csproj.user @@ -0,0 +1,8 @@ + + + + D:\file4\2024-5-6 苏州卡特\应用程序\WcsService\WcsMain\Properties\PublishProfiles\FolderProfile.pubxml + ApiControllerEmptyScaffolder + root/Common/Api + + \ No newline at end of file diff --git a/WcsMain/WebSocket/WebSocketOperation.cs b/WcsMain/WebSocket/WebSocketOperation.cs new file mode 100644 index 0000000..637ecda --- /dev/null +++ b/WcsMain/WebSocket/WebSocketOperation.cs @@ -0,0 +1,63 @@ +namespace WcsMain.WebSocket; + +public class WebSocketOperation +{ + + //private static WebSocketOperation? _instance; + + //public static WebSocketOperation Instance() => _instance ??= new WebSocketOperation(); + + + //private string websocketServe => DataService.DataBaseData.Instance().GetConfig("webSocketAddress") ?? ""; + + //private List _connection = []; + + //public void OpenWebSocket() + //{ + // if (string.IsNullOrEmpty(websocketServe)) + // { + // return; + // } + // CommonTool.Websocket = new WebSocketServer(websocketServe) + // { + // RestartAfterListenError = true + // }; + + // CommonTool.Websocket.Start(serve => + // { + // serve.OnError += obj => + // { + // ConsoleLog.Error($"websocket 发生异常,{obj}"); + // }; + // serve.OnOpen += () => + // { + // _connection.Add(serve); + // ConsoleLog.Info($"webSocket 已经连接,在:{serve.ConnectionInfo.ClientIpAddress}"); + // }; + // serve.OnMessage += _ => + // { + // // 收到消息 + // }; + // serve.OnClose += () => + // { + // _connection.Remove(serve); + // ConsoleLog.Info($"webSocket 已经断开,在:{serve.ConnectionInfo.ClientIpAddress}"); + // }; + + // }); + //} + + + + //public void Send(string msg) + //{ + // if (_connection.Count < 1) + // { + // return; + // } + // foreach (var con in _connection) + // { + // con.Send(msg); + // } + //} +} \ No newline at end of file diff --git a/WcsMain/appsettings.Development.json b/WcsMain/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/WcsMain/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WcsMain/appsettings.json b/WcsMain/appsettings.json new file mode 100644 index 0000000..660921f --- /dev/null +++ b/WcsMain/appsettings.json @@ -0,0 +1,23 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Settings": { + "DBMysql": "server=192.168.3.254;port=3306;user=user;password=user;database=app_wcs_s7miniload;", + "DBMysqlLocal": "server=192.168.3.254;port=3306;user=user;password=user;database=app_wcs_s7miniload;", + + "DBMssql": "Data Source=192.168.142.131;Initial Catalog=wcs;User Id=sa;Password=Sa123;", + "DBMssqlLocal": "Data Source=192.168.142.131;Initial Catalog=wcs_stacker;User Id=sa;Password=Sa123;", + + "ApplicationConfig": { + "ApiOnly": false, + "Language": "zh-CN" + }, + "UseUrls": [ "http://*:890" ] + + } +} diff --git a/WcsService.sln b/WcsService.sln new file mode 100644 index 0000000..64824da --- /dev/null +++ b/WcsService.sln @@ -0,0 +1,176 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33213.308 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WcsMain", "WcsMain\WcsMain.csproj", "{ED59F010-B3E5-4E19-BE65-18053645DFC5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LogTool", "Tools\LogTool\LogTool.csproj", "{7CF69CDD-413B-465C-9299-AA56B27F15BD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataCheck", "Tools\DataCheck\DataCheck.csproj", "{BF5AEA66-624D-485E-AB10-7DC1627E953D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiTool", "Tools\ApiTool\ApiTool.csproj", "{1E37935C-5FE4-4D8E-90A8-95A007758231}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CirculateTool", "Tools\CirculateTool\CirculateTool.csproj", "{9A2E14E7-527B-4FA4-8D03-04C14E638879}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocketTool", "Tools\SocketTool\SocketTool.csproj", "{8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlcTool", "Tools\PlcTool\PlcTool.csproj", "{65E9651B-9F78-408F-A3CB-B30348CDF263}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HkCamera", "Tools\HkCamera\HkCamera.csproj", "{E2359626-2C4E-4B90-ABC3-1813E9C695F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LedSimple", "Tools\LedSimple\LedSimple.csproj", "{00AF8F06-22FA-4482-B143-6775EEB6D1F4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EncryptTool", "Tools\EncryptTool\EncryptTool.csproj", "{95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{0EB56869-36A2-48C3-80E7-C50E6D1BF8EB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Debug|x64.ActiveCfg = Debug|x64 + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Debug|x64.Build.0 = Debug|x64 + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Debug|x86.ActiveCfg = Debug|x86 + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Debug|x86.Build.0 = Debug|x86 + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Release|Any CPU.Build.0 = Release|Any CPU + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Release|x64.ActiveCfg = Release|x64 + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Release|x64.Build.0 = Release|x64 + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Release|x86.ActiveCfg = Release|x86 + {ED59F010-B3E5-4E19-BE65-18053645DFC5}.Release|x86.Build.0 = Release|x86 + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Debug|x64.ActiveCfg = Debug|x64 + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Debug|x64.Build.0 = Debug|x64 + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Debug|x86.ActiveCfg = Debug|x86 + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Debug|x86.Build.0 = Debug|x86 + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Release|Any CPU.Build.0 = Release|Any CPU + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Release|x64.ActiveCfg = Release|x64 + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Release|x64.Build.0 = Release|x64 + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Release|x86.ActiveCfg = Release|x86 + {7CF69CDD-413B-465C-9299-AA56B27F15BD}.Release|x86.Build.0 = Release|x86 + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Debug|x64.ActiveCfg = Debug|x64 + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Debug|x64.Build.0 = Debug|x64 + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Debug|x86.ActiveCfg = Debug|x86 + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Debug|x86.Build.0 = Debug|x86 + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Release|Any CPU.Build.0 = Release|Any CPU + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Release|x64.ActiveCfg = Release|x64 + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Release|x64.Build.0 = Release|x64 + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Release|x86.ActiveCfg = Release|x86 + {BF5AEA66-624D-485E-AB10-7DC1627E953D}.Release|x86.Build.0 = Release|x86 + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Debug|x64.ActiveCfg = Debug|x64 + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Debug|x64.Build.0 = Debug|x64 + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Debug|x86.ActiveCfg = Debug|x86 + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Debug|x86.Build.0 = Debug|x86 + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Release|Any CPU.Build.0 = Release|Any CPU + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Release|x64.ActiveCfg = Release|x64 + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Release|x64.Build.0 = Release|x64 + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Release|x86.ActiveCfg = Release|x86 + {1E37935C-5FE4-4D8E-90A8-95A007758231}.Release|x86.Build.0 = Release|x86 + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Debug|x64.ActiveCfg = Debug|x64 + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Debug|x64.Build.0 = Debug|x64 + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Debug|x86.ActiveCfg = Debug|x86 + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Debug|x86.Build.0 = Debug|x86 + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Release|Any CPU.Build.0 = Release|Any CPU + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Release|x64.ActiveCfg = Release|x64 + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Release|x64.Build.0 = Release|x64 + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Release|x86.ActiveCfg = Release|x86 + {9A2E14E7-527B-4FA4-8D03-04C14E638879}.Release|x86.Build.0 = Release|x86 + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Debug|x64.ActiveCfg = Debug|x64 + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Debug|x64.Build.0 = Debug|x64 + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Debug|x86.ActiveCfg = Debug|x86 + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Debug|x86.Build.0 = Debug|x86 + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Release|Any CPU.Build.0 = Release|Any CPU + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Release|x64.ActiveCfg = Release|x64 + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Release|x64.Build.0 = Release|x64 + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Release|x86.ActiveCfg = Release|x86 + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240}.Release|x86.Build.0 = Release|x86 + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Debug|x64.ActiveCfg = Debug|x64 + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Debug|x64.Build.0 = Debug|x64 + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Debug|x86.ActiveCfg = Debug|x86 + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Debug|x86.Build.0 = Debug|x86 + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Release|Any CPU.Build.0 = Release|Any CPU + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Release|x64.ActiveCfg = Release|x64 + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Release|x64.Build.0 = Release|x64 + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Release|x86.ActiveCfg = Release|x86 + {65E9651B-9F78-408F-A3CB-B30348CDF263}.Release|x86.Build.0 = Release|x86 + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Debug|x64.ActiveCfg = Debug|x64 + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Debug|x64.Build.0 = Debug|x64 + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Debug|x86.ActiveCfg = Debug|x86 + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Debug|x86.Build.0 = Debug|x86 + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Release|Any CPU.Build.0 = Release|Any CPU + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Release|x64.ActiveCfg = Release|x64 + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Release|x64.Build.0 = Release|x64 + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Release|x86.ActiveCfg = Release|x86 + {E2359626-2C4E-4B90-ABC3-1813E9C695F7}.Release|x86.Build.0 = Release|x86 + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Debug|x64.ActiveCfg = Debug|x64 + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Debug|x64.Build.0 = Debug|x64 + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Debug|x86.ActiveCfg = Debug|x86 + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Debug|x86.Build.0 = Debug|x86 + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Release|Any CPU.Build.0 = Release|Any CPU + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Release|x64.ActiveCfg = Release|x64 + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Release|x64.Build.0 = Release|x64 + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Release|x86.ActiveCfg = Release|x86 + {00AF8F06-22FA-4482-B143-6775EEB6D1F4}.Release|x86.Build.0 = Release|x86 + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Debug|x64.ActiveCfg = Debug|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Debug|x64.Build.0 = Debug|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Debug|x86.ActiveCfg = Debug|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Debug|x86.Build.0 = Debug|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Release|Any CPU.Build.0 = Release|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Release|x64.ActiveCfg = Release|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Release|x64.Build.0 = Release|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Release|x86.ActiveCfg = Release|Any CPU + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7CF69CDD-413B-465C-9299-AA56B27F15BD} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + {BF5AEA66-624D-485E-AB10-7DC1627E953D} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + {1E37935C-5FE4-4D8E-90A8-95A007758231} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + {9A2E14E7-527B-4FA4-8D03-04C14E638879} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + {8320A0F8-6A9E-49AE-BAE7-4B9A3590A240} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + {65E9651B-9F78-408F-A3CB-B30348CDF263} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + {E2359626-2C4E-4B90-ABC3-1813E9C695F7} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + {00AF8F06-22FA-4482-B143-6775EEB6D1F4} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + {95FDB297-9B97-490A-9DDF-F03DBEE0EC8C} = {0EB56869-36A2-48C3-80E7-C50E6D1BF8EB} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D6F2CF8D-8897-4230-BF2F-17629B5FE425} + EndGlobalSection +EndGlobal