From 9c9d54b0f14eeb4600dde530cf18f9ea37e0755d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=A2=A6=E8=BF=9C?= <1063331231@qq.com> Date: Sun, 25 May 2025 17:55:50 +0800 Subject: [PATCH] first commit! --- build.gradle | 56 ++ build.gradle.kts | 28 - lib/modbus4j-3.1.0.jar | Bin 0 -> 190012 bytes .../iflytop/sgs/app/cmd/AnnealStartCommand.java | 50 ++ .../com/iflytop/sgs/app/cmd/AnnealStopCommand.java | 42 ++ .../com/iflytop/sgs/app/cmd/CleanStartCommand.java | 44 ++ .../com/iflytop/sgs/app/cmd/CleanStopCommand.java | 44 ++ .../com/iflytop/sgs/app/cmd/DoorCloseCommand.java | 35 ++ .../com/iflytop/sgs/app/cmd/DoorOpenCommand.java | 40 ++ .../com/iflytop/sgs/app/cmd/DoorOriginCommand.java | 38 ++ .../com/iflytop/sgs/app/cmd/DryStartCommand.java | 50 ++ .../com/iflytop/sgs/app/cmd/DryStopCommand.java | 43 ++ .../com/iflytop/sgs/app/cmd/FanStartCommand.java | 40 ++ .../com/iflytop/sgs/app/cmd/FanStopCommand.java | 40 ++ .../iflytop/sgs/app/cmd/GantryXOriginCommand.java | 33 + .../iflytop/sgs/app/cmd/GantryZOriginCommand.java | 33 + .../com/iflytop/sgs/app/cmd/HeatStartCommand.java | 50 ++ .../com/iflytop/sgs/app/cmd/HeatStopCommand.java | 41 ++ .../iflytop/sgs/app/cmd/MoveToHeatAreaCommand.java | 77 +++ .../sgs/app/cmd/MoveToSolutionAreaCommand.java | 81 +++ .../iflytop/sgs/app/cmd/SolutionAddCommand.java | 66 ++ .../iflytop/sgs/app/cmd/SolutionReduceCommand.java | 67 ++ .../sgs/app/cmd/debug/DebugDoorCloseCommand.java | 35 ++ .../sgs/app/cmd/debug/DebugDoorOpenCommand.java | 40 ++ .../sgs/app/cmd/debug/DebugDoorStopCommand.java | 35 ++ .../sgs/app/cmd/debug/DebugFanStartCommand.java | 45 ++ .../sgs/app/cmd/debug/DebugFanStopCommand.java | 45 ++ .../sgs/app/cmd/debug/DebugHeaterStartCommand.java | 46 ++ .../sgs/app/cmd/debug/DebugHeaterStopCommand.java | 45 ++ .../app/cmd/debug/DebugLiquidPumpStartCommand.java | 46 ++ .../app/cmd/debug/DebugLiquidPumpStopCommand.java | 41 ++ .../cmd/debug/DebugMotorLiquidMoveByCommand.java | 75 +++ .../cmd/debug/DebugMotorLiquidMoveToCommand.java | 37 ++ .../cmd/debug/DebugMotorLiquidResetCommand.java | 35 ++ .../app/cmd/debug/DebugMotorLiquidStopCommand.java | 35 ++ .../app/cmd/debug/DebugMotorXMoveByCommand.java | 78 +++ .../app/cmd/debug/DebugMotorXMoveToCommand.java | 41 ++ .../app/cmd/debug/DebugMotorXOriginCommand.java | 39 ++ .../sgs/app/cmd/debug/DebugMotorXStopCommand.java | 35 ++ .../app/cmd/debug/DebugMotorZMoveByCommand.java | 76 +++ .../app/cmd/debug/DebugMotorZMoveToCommand.java | 37 ++ .../app/cmd/debug/DebugMotorZOriginCommand.java | 35 ++ .../sgs/app/cmd/debug/DebugMotorZStopCommand.java | 35 ++ .../cmd/debug/DebugValveOpenThickWayCommand.java | 35 ++ .../cmd/debug/DebugValveOpenThinWayCommand.java | 35 ++ .../cmd/debug/DebugValveOpenVacantWayCommand.java | 35 ++ .../cmd/debug/DebugValveOpenWasteWayCommand.java | 35 ++ .../cmd/debug/DebugValveOpenWaterWayCommand.java | 35 ++ .../debug/step/DebugDisabledAllMotorCommand.java | 31 + .../cmd/debug/step/DebugEnableAllMotorCommand.java | 31 + .../cmd/debug/step/DebugStopAllMotorCommand.java | 30 + .../sgs/app/config/A8kCanBusConnectionConfig.java | 32 + .../com/iflytop/sgs/app/config/AsyncConfig.java | 21 + .../sgs/app/config/CraftsStateMachineConfig.java | 76 +++ .../iflytop/sgs/app/config/MybatisPlusConfig.java | 61 ++ .../com/iflytop/sgs/app/config/SwaggerConfig.java | 68 ++ .../java/com/iflytop/sgs/app/config/WebConfig.java | 16 + .../iflytop/sgs/app/controller/AuthController.java | 57 ++ .../iflytop/sgs/app/controller/CmdController.java | 74 +++ .../sgs/app/controller/CmdDebugController.java | 67 ++ .../sgs/app/controller/ContainerController.java | 34 + .../sgs/app/controller/CraftsController.java | 112 ++++ .../sgs/app/controller/DeviceParamController.java | 121 ++++ .../sgs/app/controller/DevicePointController.java | 59 ++ .../sgs/app/controller/HeatModuleController.java | 33 + .../iflytop/sgs/app/controller/OresController.java | 74 +++ .../sgs/app/controller/SelfTestController.java | 59 ++ .../sgs/app/controller/SolutionsController.java | 74 +++ .../sgs/app/controller/StepCommandController.java | 72 +++ .../sgs/app/controller/SystemController.java | 51 ++ .../sgs/app/controller/SystemLogController.java | 48 ++ .../sgs/app/controller/TasksController.java | 83 +++ .../iflytop/sgs/app/controller/TrayController.java | 47 ++ .../iflytop/sgs/app/controller/UserController.java | 72 +++ .../iflytop/sgs/app/core/BaseCommandHandler.java | 33 + .../sgs/app/core/CommandHandlerRegistry.java | 58 ++ .../iflytop/sgs/app/core/CommandPoolManager.java | 31 + .../com/iflytop/sgs/app/core/CraftsContext.java | 151 +++++ .../iflytop/sgs/app/core/CraftsDebugGenerator.java | 15 + .../com/iflytop/sgs/app/core/DebugGenerator.java | 22 + .../iflytop/sgs/app/core/device/CommandState.java | 35 ++ .../iflytop/sgs/app/core/device/CraftsState.java | 23 + .../iflytop/sgs/app/core/device/DeviceState.java | 36 ++ .../com/iflytop/sgs/app/core/device/DoorState.java | 11 + .../sgs/app/core/device/GantryArmState.java | 11 + .../sgs/app/core/device/HeatModuleState.java | 47 ++ .../iflytop/sgs/app/core/device/SelfTestState.java | 49 ++ .../app/core/device/SolutionContainerState.java | 34 + .../sgs/app/core/device/SolutionModuleState.java | 23 + .../com/iflytop/sgs/app/core/device/TrayState.java | 37 ++ .../com/iflytop/sgs/app/core/device/TubeState.java | 17 + .../sgs/app/core/event/CommandFeedbackEvent.java | 16 + .../app/core/listener/CommandFeedbackListener.java | 18 + .../sgs/app/core/listener/DeviceStateListener.java | 33 + .../iflytop/sgs/app/mapper/ContainerMapper.java | 15 + .../com/iflytop/sgs/app/mapper/CraftsMapper.java | 19 + .../sgs/app/mapper/DeviceParamConfigMapper.java | 12 + .../sgs/app/mapper/DevicePositionMapper.java | 12 + .../com/iflytop/sgs/app/mapper/OresMapper.java | 15 + .../iflytop/sgs/app/mapper/SolutionsMapper.java | 13 + .../iflytop/sgs/app/mapper/SystemConfigMapper.java | 13 + .../iflytop/sgs/app/mapper/SystemLogMapper.java | 13 + .../iflytop/sgs/app/mapper/TaskStepsMapper.java | 12 + .../com/iflytop/sgs/app/mapper/TasksMapper.java | 13 + .../com/iflytop/sgs/app/mapper/UserMapper.java | 13 + .../com/iflytop/sgs/app/model/bo/CraftsStep.java | 10 + .../sgs/app/model/bo/DeviceInitializationData.java | 11 + .../com/iflytop/sgs/app/model/bo/Notification.java | 102 +++ .../java/com/iflytop/sgs/app/model/bo/Point2D.java | 20 + .../java/com/iflytop/sgs/app/model/bo/Point3D.java | 22 + .../java/com/iflytop/sgs/app/model/bo/TubeSol.java | 23 + .../sgs/app/model/dto/AddDevicePositionDTO.java | 20 + .../java/com/iflytop/sgs/app/model/dto/CmdDTO.java | 67 ++ .../iflytop/sgs/app/model/dto/GetAllTasksDTO.java | 13 + .../com/iflytop/sgs/app/model/dto/LoginDTO.java | 22 + .../iflytop/sgs/app/model/dto/PauseCraftsDto.java | 13 + .../iflytop/sgs/app/model/dto/ResumeCraftsDTO.java | 13 + .../iflytop/sgs/app/model/dto/SetCraftsDTO.java | 19 + .../sgs/app/model/dto/SetSystemDatetimeDTO.java | 15 + .../iflytop/sgs/app/model/dto/StartCraftsDTO.java | 13 + .../iflytop/sgs/app/model/dto/StopCraftsDTO.java | 13 + .../com/iflytop/sgs/app/model/dto/StopTaskDTO.java | 10 + .../com/iflytop/sgs/app/model/dto/TaskDTO.java | 10 + .../iflytop/sgs/app/model/dto/WebsocketResult.java | 18 + .../iflytop/sgs/app/model/entity/Container.java | 47 ++ .../com/iflytop/sgs/app/model/entity/Crafts.java | 32 + .../sgs/app/model/entity/DeviceParamConfig.java | 30 + .../sgs/app/model/entity/DevicePosition.java | 57 ++ .../com/iflytop/sgs/app/model/entity/Ores.java | 20 + .../iflytop/sgs/app/model/entity/Solutions.java | 20 + .../iflytop/sgs/app/model/entity/SystemConfig.java | 22 + .../iflytop/sgs/app/model/entity/SystemLog.java | 22 + .../iflytop/sgs/app/model/entity/TaskSteps.java | 30 + .../com/iflytop/sgs/app/model/entity/Tasks.java | 40 ++ .../com/iflytop/sgs/app/model/entity/User.java | 45 ++ .../com/iflytop/sgs/app/model/vo/ContainerVO.java | 41 ++ .../iflytop/sgs/app/model/vo/CraftStatusVO.java | 39 ++ .../sgs/app/model/vo/DeviceParamGroupVO.java | 36 ++ .../iflytop/sgs/app/model/vo/DevicePositionVO.java | 39 ++ .../com/iflytop/sgs/app/model/vo/ModuleIdVO.java | 19 + .../iflytop/sgs/app/model/vo/OresCraftsListVO.java | 30 + .../com/iflytop/sgs/app/model/vo/RegIndexVO.java | 16 + .../com/iflytop/sgs/app/model/vo/SetCraftsVO.java | 22 + .../sgs/app/model/vo/SetTargetTemperatureVO.java | 32 + .../iflytop/sgs/app/model/vo/SetTrayTubeVO.java | 18 + .../com/iflytop/sgs/app/model/vo/TaskStepsVO.java | 16 + .../iflytop/sgs/app/service/ContainerService.java | 49 ++ .../com/iflytop/sgs/app/service/CraftsService.java | 191 ++++++ .../iflytop/sgs/app/service/CraftsStepService.java | 86 +++ .../sgs/app/service/DeviceCommandService.java | 180 ++++++ .../sgs/app/service/DeviceCommandUtilService.java | 567 +++++++++++++++++ .../iflytop/sgs/app/service/DeviceInitService.java | 171 +++++ .../sgs/app/service/DeviceParamConfigService.java | 119 ++++ .../sgs/app/service/DevicePositionService.java | 69 ++ .../sgs/app/service/DeviceStateService.java | 381 ++++++++++++ .../iflytop/sgs/app/service/GantryArmService.java | 62 ++ .../iflytop/sgs/app/service/HeatModuleService.java | 18 + .../com/iflytop/sgs/app/service/OresService.java | 102 +++ .../iflytop/sgs/app/service/SelfTestService.java | 16 + .../iflytop/sgs/app/service/SolutionsService.java | 39 ++ .../sgs/app/service/StepCommandService.java | 84 +++ .../sgs/app/service/SystemConfigService.java | 83 +++ .../iflytop/sgs/app/service/SystemLogService.java | 28 + .../iflytop/sgs/app/service/TaskStepsService.java | 16 + .../com/iflytop/sgs/app/service/TasksService.java | 98 +++ .../com/iflytop/sgs/app/service/TrayService.java | 47 ++ .../com/iflytop/sgs/app/service/UserService.java | 35 ++ .../iflytop/sgs/app/service/WebSocketService.java | 37 ++ .../scheduled/FetchTemperatureScheduledTask.java | 50 ++ .../app/ws/client/DeviceEmergencyStopClient.java | 61 ++ .../app/ws/client/DeviceEmergencyStopConfig.java | 49 ++ .../iflytop/sgs/app/ws/server/WebSocketServer.java | 52 ++ .../sgs/app/ws/server/WebSocketServerConfig.java | 14 + .../sgs/common/annotation/CheckedRunnable.java | 6 + .../sgs/common/annotation/CommandMapping.java | 12 + .../com/iflytop/sgs/common/base/BaseEntity.java | 44 ++ .../com/iflytop/sgs/common/base/BasePageQuery.java | 26 + .../com/iflytop/sgs/common/base/IBaseEnum.java | 84 +++ .../com/iflytop/sgs/common/cmd/CommandFuture.java | 55 ++ .../com/iflytop/sgs/common/cmd/CommandHandler.java | 9 + .../sgs/common/cmd/CyclicNumberGenerator.java | 39 ++ .../com/iflytop/sgs/common/cmd/DeviceCommand.java | 34 + .../sgs/common/cmd/DeviceCommandBundle.java | 19 + .../sgs/common/cmd/DeviceCommandGenerator.java | 532 ++++++++++++++++ .../sgs/common/cmd/DeviceCommandParams.java | 26 + .../iflytop/sgs/common/constant/CommandStatus.java | 47 ++ .../sgs/common/constant/WebSocketMessageType.java | 42 ++ .../sgs/common/enums/AcidPumpDeviceCode.java | 12 + .../iflytop/sgs/common/enums/ContainerCode.java | 19 + .../iflytop/sgs/common/enums/ContainerType.java | 18 + .../iflytop/sgs/common/enums/HeatModuleCode.java | 15 + .../sgs/common/enums/automaton/CraftEvents.java | 22 + .../sgs/common/enums/automaton/CraftStates.java | 19 + .../iflytop/sgs/common/enums/cmd/CmdAction.java | 9 + .../com/iflytop/sgs/common/enums/cmd/CmdAxis.java | 5 + .../com/iflytop/sgs/common/enums/cmd/CmdColor.java | 5 + .../iflytop/sgs/common/enums/cmd/CmdDevice.java | 47 ++ .../iflytop/sgs/common/enums/cmd/CmdDirection.java | 5 + .../com/iflytop/sgs/common/enums/data/Deleted.java | 12 + .../sgs/common/enums/data/DevicePositionCode.java | 186 ++++++ .../sgs/common/enums/data/DevicePositionType.java | 31 + .../iflytop/sgs/common/enums/data/FixedUser.java | 12 + .../com/iflytop/sgs/common/enums/data/UsrRole.java | 7 + .../sgs/common/enums/data/ValveWaysCode.java | 15 + .../iflytop/sgs/common/exception/AppException.java | 21 + .../exception/CommandExecTimeoutException.java | 22 + .../common/exception/HardwareErrorException.java | 22 + .../exception/UnSupportCommandException.java | 25 + .../sgs/common/handler/GlobalExceptionHandler.java | 82 +++ .../sgs/common/handler/MyMetaObjectHandler.java | 36 ++ .../com/iflytop/sgs/common/result/IResultCode.java | 12 + .../com/iflytop/sgs/common/result/PageResult.java | 43 ++ .../java/com/iflytop/sgs/common/result/Result.java | 75 +++ .../com/iflytop/sgs/common/result/ResultCode.java | 90 +++ .../com/iflytop/sgs/common/utils/ByteArray.java | 125 ++++ .../com/iflytop/sgs/common/utils/LambdaUtil.java | 15 + .../com/iflytop/sgs/hardware/HardwareService.java | 95 +++ .../sgs/hardware/comm/can/A8kCanBusConnection.java | 360 +++++++++++ .../sgs/hardware/comm/can/A8kCanBusService.java | 132 ++++ .../hardware/comm/modbus/JSerialCommWrapper.java | 85 +++ .../hardware/comm/modbus/ModbusMasterFactory.java | 13 + .../hardware/comm/modbus/ModbusMasterService.java | 283 +++++++++ .../sgs/hardware/command/CommandHandler.java | 70 +++ .../sgs/hardware/command/DeviceResponse.java | 12 + .../hardware/command/checker/SupportMethod.java | 31 + .../hardware/command/handlers/AcidPumpHandler.java | 92 +++ .../hardware/command/handlers/CameraHandler.java | 50 ++ .../sgs/hardware/command/handlers/ClawHandler.java | 64 ++ .../hardware/command/handlers/ColdTrapHandler.java | 79 +++ .../sgs/hardware/command/handlers/DoorHandler.java | 72 +++ .../command/handlers/DualRobotHandler.java | 90 +++ .../sgs/hardware/command/handlers/FanHandler.java | 59 ++ .../command/handlers/FillLightHandler.java | 60 ++ .../sgs/hardware/command/handlers/HBotHandler.java | 135 ++++ .../hardware/command/handlers/HeatRodHandler.java | 77 +++ .../command/handlers/HeaterMotorHandler.java | 73 +++ .../command/handlers/ShakeMotorHandler.java | 69 ++ .../command/handlers/TrayMotorHandler.java | 77 +++ .../command/handlers/TricolorLightHandler.java | 83 +++ .../command/handlers/VentilatorHandler.java | 52 ++ .../command/handlers/WaterPumpHandler.java | 52 ++ .../hardware/config/A8kSubModuleInitRegConfig.java | 112 ++++ .../sgs/hardware/config/StepMotorConfig.java | 261 ++++++++ .../hardware/constants/ActionOvertimeConstant.java | 61 ++ .../sgs/hardware/constants/FilePathConstant.java | 5 + .../sgs/hardware/constants/MiniServoConstant.java | 51 ++ .../hardware/controller/AcidPumpController.java | 84 +++ .../hardware/controller/DualRobotController.java | 91 +++ .../hardware/controller/HeaterMotorController.java | 70 +++ .../hardware/controller/HeaterRodController.java | 61 ++ .../sgs/hardware/controller/IOController.java | 32 + .../sgs/hardware/controller/ServoController.java | 161 +++++ .../hardware/controller/StepMotorController.java | 312 ++++++++++ .../hardware/dao/SubModuleRegInitialValueDao.java | 38 ++ .../iflytop/sgs/hardware/drivers/CameraDriver.java | 13 + .../sgs/hardware/drivers/ColdTrapDriver.java | 52 ++ .../drivers/DIDriver/InputDetectDriver.java | 37 ++ .../sgs/hardware/drivers/DODriver/FanDriver.java | 24 + .../drivers/DODriver/OutputIOCtrlDriver.java | 30 + .../drivers/DODriver/VentilatorDriver.java | 22 + .../hardware/drivers/DODriver/WaterPumpDriver.java | 22 + .../sgs/hardware/drivers/FillLightDriver.java | 23 + .../sgs/hardware/drivers/HeaterRodDriver.java | 122 ++++ .../sgs/hardware/drivers/LeisaiServoDriver.java | 98 +++ .../drivers/LiquidDistributionArmDriver.java | 98 +++ .../drivers/MiniServoDriver/ClawDriver.java | 108 ++++ .../drivers/MiniServoDriver/DualRobotDriver.java | 178 ++++++ .../drivers/MiniServoDriver/MiniServoDriver.java | 162 +++++ .../hardware/drivers/ModuleEnableCtrlDriver.java | 78 +++ .../drivers/StepMotorDriver/AcidPumpDriver.java | 100 +++ .../drivers/StepMotorDriver/DoorDriver.java | 78 +++ .../drivers/StepMotorDriver/HBotDriver.java | 99 +++ .../drivers/StepMotorDriver/HeaterMotorDriver.java | 78 +++ .../drivers/StepMotorDriver/ShakeMotorDriver.java | 89 +++ .../StepMotorDriver/StepMotorCtrlDriver.java | 181 ++++++ .../drivers/StepMotorDriver/TrayMotorDriver.java | 97 +++ .../sgs/hardware/drivers/TricolorLightDriver.java | 28 + .../sgs/hardware/exception/HardwareException.java | 62 ++ .../sgs/hardware/factory/A8kPacketFactory.java | 53 ++ .../sgs/hardware/service/AppEventBusService.java | 93 +++ .../hardware/service/GDDeviceStatusService.java | 91 +++ .../iflytop/sgs/hardware/service/IOService.java | 4 + .../iflytop/sgs/hardware/service/ServoService.java | 129 ++++ .../sgs/hardware/service/StepMotorService.java | 262 ++++++++ .../service/setup/A8kSubModuleRegInitService.java | 136 ++++ .../setup/SubModuleRegInitialValueMgrService.java | 85 +++ .../com/iflytop/sgs/hardware/type/A8kPacket.java | 227 +++++++ .../java/com/iflytop/sgs/hardware/type/CmdId.java | 177 ++++++ .../iflytop/sgs/hardware/type/IO/InputIOMId.java | 34 + .../iflytop/sgs/hardware/type/IO/OutputIOMId.java | 34 + .../java/com/iflytop/sgs/hardware/type/MId.java | 109 ++++ .../iflytop/sgs/hardware/type/ModuleStatus.java | 25 + .../com/iflytop/sgs/hardware/type/ModuleType.java | 33 + .../com/iflytop/sgs/hardware/type/RegIndex.java | 192 ++++++ .../sgs/hardware/type/Servo/DeviceServoId.java | 33 + .../sgs/hardware/type/Servo/LeisaiRegIndex.java | 19 + .../sgs/hardware/type/Servo/LeisaiServoMId.java | 17 + .../hardware/type/Servo/LeisaiServoSpeedLevel.java | 9 + .../sgs/hardware/type/Servo/LiquidArmMId.java | 16 + .../sgs/hardware/type/Servo/LiquidArmRegIndex.java | 47 ++ .../sgs/hardware/type/Servo/MiniServoMId.java | 19 + .../sgs/hardware/type/Servo/MiniServoRegIndex.java | 71 +++ .../hardware/type/StepMotor/DeviceStepMotorId.java | 51 ++ .../hardware/type/StepMotor/StepMotorDirect.java | 17 + .../sgs/hardware/type/StepMotor/StepMotorMId.java | 32 + .../hardware/type/StepMotor/StepMotorRegIndex.java | 56 ++ .../type/StepMotor/StepMotorSpeedLevel.java | 9 + .../type/appevent/A8kCanBusOnConnectEvent.java | 7 + .../sgs/hardware/type/appevent/AppEvent.java | 13 + .../hardware/type/db/SubModuleRegInitialValue.java | 23 + .../hardware/type/driver/HeaterRodSlavedId.java | 51 ++ .../iflytop/sgs/hardware/type/error/A8kEcode.java | 255 ++++++++ .../sgs/hardware/type/error/AECodeError.java | 37 ++ .../sgs/hardware/type/error/AEHardwareError.java | 29 + .../iflytop/sgs/hardware/type/error/AppError.java | 38 ++ .../sgs/hardware/type/error/AppErrorCode.java | 21 + .../com/iflytop/sgs/hardware/utils/CommonPage.java | 25 + .../utils/Math/ServoPositionConverter.java | 84 +++ .../hardware/utils/Math/StepMotorConverter.java | 81 +++ .../java/com/iflytop/sgs/hardware/utils/OS.java | 24 + .../com/iflytop/sgs/hardware/utils/ZCSVUtils.java | 78 +++ .../java/com/iflytop/sgs/hardware/utils/ZList.java | 11 + .../com/iflytop/sgs/hardware/utils/ZSqlite.java | 209 +++++++ .../sgs/hardware/utils/ZSqliteJdbcHelper.java | 284 +++++++++ .../java/com/iflytop/sgs/monitor/collect/d.txt | 1 + .../controller/CraftsMonitorController.java | 46 ++ src/main/resources/application-dev.yml | 51 ++ src/main/resources/application-test.yml | 41 ++ src/main/resources/application.properties | 1 - src/main/resources/application.yml | 3 + .../init/zapp_sub_module_reg_initial_value.csv | 691 +++++++++++++++++++++ src/main/resources/logback.xml | 58 ++ src/main/resources/sql/init.sql | 164 +++++ src/main/resources/static/favicon.ico | Bin 0 -> 5648 bytes 334 files changed, 20115 insertions(+), 29 deletions(-) create mode 100644 build.gradle delete mode 100644 build.gradle.kts create mode 100644 lib/modbus4j-3.1.0.jar create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/AnnealStartCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/AnnealStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/CleanStartCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/CleanStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/DoorCloseCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/DoorOpenCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/DoorOriginCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/DryStartCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/DryStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/FanStartCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/FanStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/GantryXOriginCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/GantryZOriginCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/HeatStartCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/HeatStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/MoveToHeatAreaCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/MoveToSolutionAreaCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/SolutionAddCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/SolutionReduceCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorCloseCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorOpenCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugFanStartCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugFanStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugHeaterStartCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugHeaterStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugLiquidPumpStartCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugLiquidPumpStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidMoveByCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidMoveToCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidResetCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXMoveByCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXMoveToCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXOriginCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZMoveByCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZMoveToCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZOriginCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZStopCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenThickWayCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenThinWayCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenVacantWayCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenWasteWayCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenWaterWayCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugDisabledAllMotorCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugEnableAllMotorCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugStopAllMotorCommand.java create mode 100644 src/main/java/com/iflytop/sgs/app/config/A8kCanBusConnectionConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/config/AsyncConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/config/CraftsStateMachineConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/config/MybatisPlusConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/config/SwaggerConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/config/WebConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/AuthController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/CmdController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/CmdDebugController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/ContainerController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/CraftsController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/DeviceParamController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/DevicePointController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/HeatModuleController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/OresController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/SelfTestController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/SolutionsController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/StepCommandController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/SystemController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/SystemLogController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/TasksController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/TrayController.java create mode 100644 src/main/java/com/iflytop/sgs/app/controller/UserController.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/BaseCommandHandler.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/CommandHandlerRegistry.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/CommandPoolManager.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/CraftsContext.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/CraftsDebugGenerator.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/DebugGenerator.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/CommandState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/CraftsState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/DeviceState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/DoorState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/GantryArmState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/HeatModuleState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/SelfTestState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/SolutionContainerState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/SolutionModuleState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/TrayState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/device/TubeState.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/event/CommandFeedbackEvent.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/listener/CommandFeedbackListener.java create mode 100644 src/main/java/com/iflytop/sgs/app/core/listener/DeviceStateListener.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/ContainerMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/CraftsMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/DeviceParamConfigMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/DevicePositionMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/OresMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/SolutionsMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/SystemConfigMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/SystemLogMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/TaskStepsMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/TasksMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/mapper/UserMapper.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/bo/CraftsStep.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/bo/DeviceInitializationData.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/bo/Notification.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/bo/Point2D.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/bo/Point3D.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/bo/TubeSol.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/AddDevicePositionDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/CmdDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/GetAllTasksDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/LoginDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/PauseCraftsDto.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/ResumeCraftsDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/SetCraftsDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/SetSystemDatetimeDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/StartCraftsDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/StopCraftsDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/StopTaskDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/TaskDTO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/dto/WebsocketResult.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/Container.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/Crafts.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/DeviceParamConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/DevicePosition.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/Ores.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/Solutions.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/SystemConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/SystemLog.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/TaskSteps.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/Tasks.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/entity/User.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/ContainerVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/CraftStatusVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/DeviceParamGroupVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/DevicePositionVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/ModuleIdVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/OresCraftsListVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/RegIndexVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/SetCraftsVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/SetTargetTemperatureVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/SetTrayTubeVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/model/vo/TaskStepsVO.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/ContainerService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/CraftsService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/CraftsStepService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/DeviceCommandService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/DeviceCommandUtilService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/DeviceInitService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/DeviceParamConfigService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/DevicePositionService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/DeviceStateService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/GantryArmService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/HeatModuleService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/OresService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/SelfTestService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/SolutionsService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/StepCommandService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/SystemConfigService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/SystemLogService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/TaskStepsService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/TasksService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/TrayService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/UserService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/WebSocketService.java create mode 100644 src/main/java/com/iflytop/sgs/app/service/scheduled/FetchTemperatureScheduledTask.java create mode 100644 src/main/java/com/iflytop/sgs/app/ws/client/DeviceEmergencyStopClient.java create mode 100644 src/main/java/com/iflytop/sgs/app/ws/client/DeviceEmergencyStopConfig.java create mode 100644 src/main/java/com/iflytop/sgs/app/ws/server/WebSocketServer.java create mode 100644 src/main/java/com/iflytop/sgs/app/ws/server/WebSocketServerConfig.java create mode 100644 src/main/java/com/iflytop/sgs/common/annotation/CheckedRunnable.java create mode 100644 src/main/java/com/iflytop/sgs/common/annotation/CommandMapping.java create mode 100644 src/main/java/com/iflytop/sgs/common/base/BaseEntity.java create mode 100644 src/main/java/com/iflytop/sgs/common/base/BasePageQuery.java create mode 100644 src/main/java/com/iflytop/sgs/common/base/IBaseEnum.java create mode 100644 src/main/java/com/iflytop/sgs/common/cmd/CommandFuture.java create mode 100644 src/main/java/com/iflytop/sgs/common/cmd/CommandHandler.java create mode 100644 src/main/java/com/iflytop/sgs/common/cmd/CyclicNumberGenerator.java create mode 100644 src/main/java/com/iflytop/sgs/common/cmd/DeviceCommand.java create mode 100644 src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandBundle.java create mode 100644 src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandGenerator.java create mode 100644 src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandParams.java create mode 100644 src/main/java/com/iflytop/sgs/common/constant/CommandStatus.java create mode 100644 src/main/java/com/iflytop/sgs/common/constant/WebSocketMessageType.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/AcidPumpDeviceCode.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/ContainerCode.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/ContainerType.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/HeatModuleCode.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/automaton/CraftEvents.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/automaton/CraftStates.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/cmd/CmdAction.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/cmd/CmdAxis.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/cmd/CmdColor.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/cmd/CmdDevice.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/cmd/CmdDirection.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/data/Deleted.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/data/DevicePositionCode.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/data/DevicePositionType.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/data/FixedUser.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/data/UsrRole.java create mode 100644 src/main/java/com/iflytop/sgs/common/enums/data/ValveWaysCode.java create mode 100644 src/main/java/com/iflytop/sgs/common/exception/AppException.java create mode 100644 src/main/java/com/iflytop/sgs/common/exception/CommandExecTimeoutException.java create mode 100644 src/main/java/com/iflytop/sgs/common/exception/HardwareErrorException.java create mode 100644 src/main/java/com/iflytop/sgs/common/exception/UnSupportCommandException.java create mode 100644 src/main/java/com/iflytop/sgs/common/handler/GlobalExceptionHandler.java create mode 100644 src/main/java/com/iflytop/sgs/common/handler/MyMetaObjectHandler.java create mode 100644 src/main/java/com/iflytop/sgs/common/result/IResultCode.java create mode 100644 src/main/java/com/iflytop/sgs/common/result/PageResult.java create mode 100644 src/main/java/com/iflytop/sgs/common/result/Result.java create mode 100644 src/main/java/com/iflytop/sgs/common/result/ResultCode.java create mode 100644 src/main/java/com/iflytop/sgs/common/utils/ByteArray.java create mode 100644 src/main/java/com/iflytop/sgs/common/utils/LambdaUtil.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/HardwareService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/comm/can/A8kCanBusConnection.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/comm/can/A8kCanBusService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/comm/modbus/JSerialCommWrapper.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/comm/modbus/ModbusMasterFactory.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/comm/modbus/ModbusMasterService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/CommandHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/DeviceResponse.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/checker/SupportMethod.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/AcidPumpHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/CameraHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/ClawHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/ColdTrapHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/DoorHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/DualRobotHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/FanHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/FillLightHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/HBotHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/HeatRodHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/HeaterMotorHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/ShakeMotorHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/TrayMotorHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/TricolorLightHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/VentilatorHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/command/handlers/WaterPumpHandler.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/config/A8kSubModuleInitRegConfig.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/config/StepMotorConfig.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/constants/ActionOvertimeConstant.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/constants/FilePathConstant.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/constants/MiniServoConstant.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/controller/AcidPumpController.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/controller/DualRobotController.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/controller/HeaterMotorController.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/controller/HeaterRodController.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/controller/IOController.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/controller/ServoController.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/controller/StepMotorController.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/dao/SubModuleRegInitialValueDao.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/CameraDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/ColdTrapDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/DIDriver/InputDetectDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/FanDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/OutputIOCtrlDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/VentilatorDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/WaterPumpDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/FillLightDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/HeaterRodDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/LeisaiServoDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/LiquidDistributionArmDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/ClawDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/DualRobotDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/MiniServoDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/ModuleEnableCtrlDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/AcidPumpDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/DoorDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/HBotDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/HeaterMotorDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/ShakeMotorDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/StepMotorCtrlDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/TrayMotorDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/drivers/TricolorLightDriver.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/exception/HardwareException.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/factory/A8kPacketFactory.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/service/AppEventBusService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/service/GDDeviceStatusService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/service/IOService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/service/ServoService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/service/StepMotorService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/service/setup/A8kSubModuleRegInitService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/service/setup/SubModuleRegInitialValueMgrService.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/A8kPacket.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/CmdId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/IO/InputIOMId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/IO/OutputIOMId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/MId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/ModuleStatus.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/ModuleType.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/RegIndex.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/Servo/DeviceServoId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiRegIndex.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiServoMId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiServoSpeedLevel.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/Servo/LiquidArmMId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/Servo/LiquidArmRegIndex.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/Servo/MiniServoMId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/Servo/MiniServoRegIndex.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/StepMotor/DeviceStepMotorId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorDirect.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorMId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorRegIndex.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorSpeedLevel.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/appevent/A8kCanBusOnConnectEvent.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/appevent/AppEvent.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/db/SubModuleRegInitialValue.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/driver/HeaterRodSlavedId.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/error/A8kEcode.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/error/AECodeError.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/error/AEHardwareError.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/error/AppError.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/type/error/AppErrorCode.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/utils/CommonPage.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/utils/Math/ServoPositionConverter.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/utils/Math/StepMotorConverter.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/utils/OS.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/utils/ZCSVUtils.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/utils/ZList.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/utils/ZSqlite.java create mode 100644 src/main/java/com/iflytop/sgs/hardware/utils/ZSqliteJdbcHelper.java create mode 100644 src/main/java/com/iflytop/sgs/monitor/collect/d.txt create mode 100644 src/main/java/com/iflytop/sgs/monitor/controller/CraftsMonitorController.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-test.yml delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/init/zapp_sub_module_reg_initial_value.csv create mode 100644 src/main/resources/logback.xml create mode 100644 src/main/resources/sql/init.sql create mode 100644 src/main/resources/static/favicon.ico diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..5b60c4d --- /dev/null +++ b/build.gradle @@ -0,0 +1,56 @@ +// build.gradle +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.5' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'com.iflytop' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + maven { url 'https://maven.aliyun.com/repository/public' } + maven { url 'https://maven.aliyun.com/repository/central' } + maven { url 'https://maven.aliyun.com/repository/spring' } +} + +dependencies { + implementation files('lib/modbus4j-3.1.0.jar') + + implementation 'ch.qos.logback:logback-core:1.5.16' + implementation 'cn.hutool:hutool-all:5.8.35' + implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.10.1' + implementation 'com.baomidou:mybatis-plus-generator:3.5.10.1' + implementation 'com.baomidou:mybatis-plus-jsqlparser:3.5.10.1' + implementation 'com.github.xiaoymin:knife4j-openapi3-jakarta-spring-boot-starter:4.5.0' + implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6' + implementation 'org.freemarker:freemarker:2.3.34' + implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4' + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-websocket:3.4.2' + implementation 'org.springframework.statemachine:spring-statemachine-core:4.0.0' + implementation 'org.xerial:sqlite-jdbc:3.48.0.0' + implementation 'org.springframework.boot:spring-boot-starter-validation:3.4.5' + implementation 'org.java-websocket:Java-WebSocket:1.6.0' + implementation 'com.fazecast:jSerialComm:2.11.0' + implementation 'com.opencsv:opencsv:5.11' + + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + enabled = false + useJUnitPlatform() +} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 099398a..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - java - id("org.springframework.boot") version "3.4.6" - id("io.spring.dependency-management") version "1.1.7" -} - -group = "com.iflytop" -version = "0.0.1-SNAPSHOT" - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation("org.springframework.boot:spring-boot-starter") - testImplementation("org.springframework.boot:spring-boot-starter-test") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") -} - -tasks.withType { - useJUnitPlatform() -} diff --git a/lib/modbus4j-3.1.0.jar b/lib/modbus4j-3.1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..ab89c8519050a1d51782920893f29bc6dc1030fb GIT binary patch literal 190012 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

t~Ru9UG~ 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 literal 0 HcmV?d00001 diff --git a/src/main/java/com/iflytop/sgs/app/cmd/AnnealStartCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/AnnealStartCommand.java new file mode 100644 index 0000000..3a805c8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/AnnealStartCommand.java @@ -0,0 +1,50 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.core.device.HeatModuleState; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 开始退火 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heat_start")//业务指令注解 +public class AnnealStartCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + //获取当前加热区 + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + HeatModuleState heatModuleState = deviceStateService.getHeatModuleState(heatModuleId); + //设置加热区目标温度 + deviceStateService.setHeatModuleStateTargetTemperature(heatModuleId,heatModuleState.getAnnealTemperature()); + //从系统状态中获取指定加热区设定的退火温度数值 + double temperature = deviceStateService.getHeatModuleState(heatModuleId).getAnnealTemperature(); + //开启退火 + deviceCommandUtilService.heatRodOpen(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId, temperature); + //设置加热区状态 正在退火 + deviceStateService.setHeatModuleStateAnnealing(heatModuleId,true); + + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/AnnealStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/AnnealStopCommand.java new file mode 100644 index 0000000..fe28904 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/AnnealStopCommand.java @@ -0,0 +1,42 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 结束退火 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heat_start")//业务指令注解 +public class AnnealStopCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + //关闭加热 + deviceCommandUtilService.heatRodClose(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId); + //设置加热区状态 退火结束 + deviceStateService.setHeatModuleStateAnnealing(heatModuleId,false); + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/CleanStartCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/CleanStartCommand.java new file mode 100644 index 0000000..26f4afe --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/CleanStartCommand.java @@ -0,0 +1,44 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 开始清洗 todo wmy 需要改 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heat_start")//业务指令注解 +public class CleanStartCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + //从系统状态中获取指定加热区设定的温度数值 + double temperature = deviceStateService.getHeatModuleState(heatModuleId).getTemperature(); + //开启加热 + deviceCommandUtilService.heatRodOpen(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId, temperature); + deviceStateService.setHeatModuleStateHeating(heatModuleId, true);//设置状态加热中 + + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/CleanStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/CleanStopCommand.java new file mode 100644 index 0000000..f23a8bf --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/CleanStopCommand.java @@ -0,0 +1,44 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 结束清洗 todo wmy 需要改 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heat_start")//业务指令注解 +public class CleanStopCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + //从系统状态中获取指定加热区设定的温度数值 + double temperature = deviceStateService.getHeatModuleState(heatModuleId).getTemperature(); + //开启加热 + deviceCommandUtilService.heatRodOpen(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId, temperature); + deviceStateService.setHeatModuleStateHeating(heatModuleId, true);//设置状态加热中 + + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/DoorCloseCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/DoorCloseCommand.java new file mode 100644 index 0000000..13c09a8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/DoorCloseCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 关门 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("door_close")//业务指令注解 +public class DoorCloseCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + //门电机回原点 + deviceCommandUtilService.doorOrigin(cmdDTO.getCommandId(), cmdDTO.getCommand()); + //将门状态设置为false + deviceStateService.setDoorStatus(false); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/DoorOpenCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/DoorOpenCommand.java new file mode 100644 index 0000000..92cfc1d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/DoorOpenCommand.java @@ -0,0 +1,40 @@ +package com.iflytop.sgs.app.cmd; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DevicePositionService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.data.DevicePositionCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 开门 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("door_open")//业务指令注解 +public class DoorOpenCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DevicePositionService devicePositionService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + //从数据库获取开门距离 + Double doorOpenDistance = devicePositionService.getPosition(DevicePositionCode.doorOpen).getDistance(); + //门电机移动开门距离 + deviceCommandUtilService.doorMove(cmdDTO.getCommandId(), cmdDTO.getCommand(), doorOpenDistance); + //将门状态设置为true + deviceStateService.setDoorStatus(true); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/DoorOriginCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/DoorOriginCommand.java new file mode 100644 index 0000000..a2e613d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/DoorOriginCommand.java @@ -0,0 +1,38 @@ +package com.iflytop.sgs.app.cmd; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.app.service.SelfTestService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 门回原点 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("door_origin")//业务指令注解 +public class DoorOriginCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + private final SelfTestService selfTestService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + //门电机回原点 + deviceCommandUtilService.doorOrigin(cmdDTO.getCommandId(), cmdDTO.getCommand()); + selfTestService.getSelfTestState().setDoorOrigin(true);//设置是否在原点状态 + //将门状态设置为false + deviceStateService.setDoorStatus(false); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/DryStartCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/DryStartCommand.java new file mode 100644 index 0000000..7ee171d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/DryStartCommand.java @@ -0,0 +1,50 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.core.device.HeatModuleState; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 开始烘干 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heat_start")//业务指令注解 +public class DryStartCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + //获取当前加热区 + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + HeatModuleState heatModuleState = deviceStateService.getHeatModuleState(heatModuleId); + //设置加热区目标温度 + deviceStateService.setHeatModuleStateTargetTemperature(heatModuleId,heatModuleState.getDryTemperature()); + //从系统状态中获取指定加热区设定的烘干温度数值 + double temperature = deviceStateService.getHeatModuleState(heatModuleId).getDryTemperature(); + //开启退火 + deviceCommandUtilService.heatRodOpen(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId, temperature); + //设置加热区状态 正在烘干 + deviceStateService.setHeatModuleStateAnnealing(heatModuleId,true); + + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/DryStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/DryStopCommand.java new file mode 100644 index 0000000..ad4dd54 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/DryStopCommand.java @@ -0,0 +1,43 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 结束烘干 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heat_start")//业务指令注解 +public class DryStopCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + //关闭加热 + deviceCommandUtilService.heatRodClose(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId); + //设置加热区状态 退火结束 + deviceStateService.setHeatModuleStateDrying(heatModuleId,false); + + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/FanStartCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/FanStartCommand.java new file mode 100644 index 0000000..1587168 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/FanStartCommand.java @@ -0,0 +1,40 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 散热开 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("fan_start") +public class FanStartCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + deviceCommandUtilService.fanStart(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId); + deviceStateService.setHeatModuleStateFanOpen(heatModuleId, true); + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/FanStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/FanStopCommand.java new file mode 100644 index 0000000..02c8161 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/FanStopCommand.java @@ -0,0 +1,40 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 散热关 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("fan_stop") +public class FanStopCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + deviceCommandUtilService.fanClose(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId); + deviceStateService.setHeatModuleStateFanOpen(heatModuleId, false); + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/GantryXOriginCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/GantryXOriginCommand.java new file mode 100644 index 0000000..8b7e4be --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/GantryXOriginCommand.java @@ -0,0 +1,33 @@ +package com.iflytop.sgs.app.cmd; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.SelfTestService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 龙门架机械臂x轴回原点 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_x_origin")//业务指令注解 +public class GantryXOriginCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final SelfTestService selfTestService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + deviceCommandUtilService.gantryXMoveOrigin(cmdDTO.getCommandId(), cmdDTO.getCommand()); + selfTestService.getSelfTestState().setGantryXOrigin(true);//设置是否在原点状态 + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/GantryZOriginCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/GantryZOriginCommand.java new file mode 100644 index 0000000..32bf276 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/GantryZOriginCommand.java @@ -0,0 +1,33 @@ +package com.iflytop.sgs.app.cmd; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.SelfTestService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 龙门架机械臂z轴回原点 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_z_origin")//业务指令注解 +public class GantryZOriginCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final SelfTestService selfTestService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + deviceCommandUtilService.gantryZMoveOrigin(cmdDTO.getCommandId(), cmdDTO.getCommand()); + selfTestService.getSelfTestState().setGantryZOrigin(true);//设置是否在原点状态 + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/HeatStartCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/HeatStartCommand.java new file mode 100644 index 0000000..a411053 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/HeatStartCommand.java @@ -0,0 +1,50 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.core.device.HeatModuleState; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 开始加热 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heat_start")//业务指令注解 +public class HeatStartCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + //获取当前加热区 + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + HeatModuleState heatModuleState = deviceStateService.getHeatModuleState(heatModuleId); + //设置加热区目标温度 + deviceStateService.setHeatModuleStateTargetTemperature(heatModuleId,heatModuleState.getHeatTemperature()); + //从系统状态中获取指定加热区设定的烘干温度数值 + double temperature = deviceStateService.getHeatModuleState(heatModuleId).getHeatTemperature(); + //开启加热 + deviceCommandUtilService.heatRodOpen(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId, temperature); + //设置加热区状态 正在加热 + deviceStateService.setHeatModuleStateHeating(heatModuleId,true); + + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/HeatStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/HeatStopCommand.java new file mode 100644 index 0000000..2509cfe --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/HeatStopCommand.java @@ -0,0 +1,41 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 停止加热 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heat_stop")//业务指令注解 +public class HeatStopCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + JSONArray heatIdJsonArray = cmdDTO.getJSONArrayParam("heatId"); + return runAsync(() -> { + for (int i = 0; i < heatIdJsonArray.size(); i++) { + String heatId = heatIdJsonArray.getStr(i); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + //关闭加热 + deviceCommandUtilService.heatRodClose(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatModuleId); + deviceStateService.setHeatModuleStateHeating(heatModuleId, false);//设置状态停止加热 + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/MoveToHeatAreaCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/MoveToHeatAreaCommand.java new file mode 100644 index 0000000..5ec4a82 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/MoveToHeatAreaCommand.java @@ -0,0 +1,77 @@ +package com.iflytop.sgs.app.cmd; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.bo.Point3D; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DevicePositionService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.app.service.GantryArmService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import com.iflytop.sgs.common.enums.data.DevicePositionCode; +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.result.ResultCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 移至加热 todo wmy 需要改 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("move_to_heat_area")//业务指令注解 +public class MoveToHeatAreaCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DevicePositionService devicePositionService; + private final GantryArmService gantryArmService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) throws Exception { + if (deviceStateService.getCommandState().get().isMoveToHeatAreaCommandExecuting()) { + throw new AppException(ResultCode.COMMAND_ALREADY_EXECUTING); + } + if (deviceStateService.getCommandState().get().isMoveToSolutionAreaCommandExecuting()) { + throw new AppException(ResultCode.CMD_BUSY); + } + try { + deviceStateService.getCommandState().get().setMoveToHeatAreaCommandExecuting(true); + } catch (Exception e) { + deviceStateService.getCommandState().get().setMoveToHeatAreaCommandExecuting(false); + throw e; + } + String heatId = cmdDTO.getStringParam("heatId"); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + //加液电机升起的安全高度 + double liquidMotorSafeDistance = devicePositionService.getPosition(DevicePositionCode.clawTrayGrip).getDistance(); + //获取机械臂夹取托盘的横向距离 + double clawTrayGrip = devicePositionService.getPosition(DevicePositionCode.clawTrayGrip).getDistance(); + //获取机械臂夹取托盘的纵向高度 + double clawTrayHeight = devicePositionService.getPosition(DevicePositionCode.clawTrayHeight).getDistance(); + //获取指定加热模块上方点位 + Point3D heatAreaTrayClawPoint3D = deviceCommandUtilService.getHeatAreaTrayClawPoint3D(heatModuleId); + return runAsync(() -> { + try { + //升高加液电机高度 + deviceCommandUtilService.motorLiquidMove(cmdDTO.getCommandId(),cmdDTO.getCommand(),liquidMotorSafeDistance); + //移动机械臂到加热区上方 此时机械臂夹着托盘 + deviceCommandUtilService.gantryMove(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatAreaTrayClawPoint3D); //将机械臂移动至加热模块上方 + //下降z轴,夹爪脱离机械臂 + deviceCommandUtilService.gantryZMove(clawTrayHeight); + //移动x轴向右脱离托盘 + deviceCommandUtilService.gantryXMoveBy(cmdDTO.getCommandId(), cmdDTO.getCommand(), -clawTrayGrip); + //z轴返回原点 + deviceCommandUtilService.motorLiquidMove(0); + } finally { + deviceStateService.setGantryArmStateIdle(true); + deviceStateService.getCommandState().get().setMoveToHeatAreaCommandExecuting(false); + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/MoveToSolutionAreaCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/MoveToSolutionAreaCommand.java new file mode 100644 index 0000000..2e3d9a0 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/MoveToSolutionAreaCommand.java @@ -0,0 +1,81 @@ +package com.iflytop.sgs.app.cmd; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.bo.Point3D; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DevicePositionService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import com.iflytop.sgs.common.enums.data.DevicePositionCode; +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.result.ResultCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 移至加液 todo wmy 需要改 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("move_to_solution_area")//业务指令注解 +public class MoveToSolutionAreaCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DevicePositionService devicePositionService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) throws Exception { + if (deviceStateService.getCommandState().get().isMoveToSolutionAreaCommandExecuting()) { + throw new AppException(ResultCode.COMMAND_ALREADY_EXECUTING); + } + if (deviceStateService.getCommandState().get().isMoveToHeatAreaCommandExecuting()) { + throw new AppException(ResultCode.CMD_BUSY); + } + String heatId = cmdDTO.getStringParam("heatId"); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(heatId); + //加液电机升起的安全高度 + double liquidMotorSafeDistance = devicePositionService.getPosition(DevicePositionCode.clawTrayGrip).getDistance(); + //获取机械臂夹取托盘的横向距离 + double clawTrayGrip = devicePositionService.getPosition(DevicePositionCode.clawTrayGrip).getDistance(); + //获取机械臂夹取托盘的纵向高度 + double clawTrayHeight = devicePositionService.getPosition(DevicePositionCode.clawTrayHeight).getDistance();//获取z轴夹取托盘的下降高度 + //获取加液模块上方点位 + Point3D liquidAreaTrayPoint3D = devicePositionService.getPosition(DevicePositionCode.liquidAreaTrayPoint).getPoint3D(); + //获取指定加热模块上方点位 + Point3D heatAreaTrayPoint3D = deviceCommandUtilService.getHeatAreaTrayClawPoint3D(heatModuleId); + + //校验目标加热位是否有托盘 + try { + deviceStateService.getCommandState().get().setMoveToSolutionAreaCommandExecuting(true); + } catch (Exception e) { + deviceStateService.getCommandState().get().setMoveToSolutionAreaCommandExecuting(false); + throw e; + } + return runAsync(() -> { + try { + //升高加液电机高度 + deviceCommandUtilService.motorLiquidMove(cmdDTO.getCommandId(),cmdDTO.getCommand(),liquidMotorSafeDistance); + //机械臂移动到加热位上方或者上料位上方 + deviceCommandUtilService.gantryMove(cmdDTO.getCommandId(), cmdDTO.getCommand(), heatAreaTrayPoint3D); + //下降z轴 + deviceCommandUtilService.gantryZMove(clawTrayHeight); + //移动x轴向左夹紧托盘 + deviceCommandUtilService.gantryXMoveBy(cmdDTO.getCommandId(), cmdDTO.getCommand(), clawTrayGrip); + //z轴上升到0点 + deviceCommandUtilService.gantryZMove(0); + //机械臂移动到加液区 + deviceCommandUtilService.gantryMove(cmdDTO.getCommandId(), cmdDTO.getCommand(), liquidAreaTrayPoint3D); + } finally { + deviceStateService.setGantryArmStateIdle(true); + deviceStateService.getCommandState().get().setMoveToSolutionAreaCommandExecuting(false); + } + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/SolutionAddCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/SolutionAddCommand.java new file mode 100644 index 0000000..9371c90 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/SolutionAddCommand.java @@ -0,0 +1,66 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.ContainerService; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.AcidPumpDeviceCode; +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.result.ResultCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +/** + * 添加溶液 todo wmy 需要记录托盘每一行的坐标 机械臂移动成功后 调动泵机加液 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("solution_add")//业务指令注解 +public class SolutionAddCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final ContainerService containerService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + deviceStateService.setSolutionModuleStatePumping(true); + return runAsync(() -> { + JSONArray dataList = JSONUtil.parseArray(cmdDTO.getParams().get("dataList")); + for (int i = 0; i < dataList.size(); i++) {//遍历前端传入的加液配置 + JSONObject tubeSol = dataList.getJSONObject(i); + String tubeNum = tubeSol.getStr("tubeNum");//获取试管编号 + int[] tubeNums = Arrays.stream(tubeNum.split(",")) + .mapToInt(s -> Integer.parseInt(s.trim())) + .toArray(); + JSONArray solutionList = tubeSol.getJSONArray("solutionList"); + for (int num : tubeNums) { + for (int k = 0; k < solutionList.size(); k++) { + JSONObject addSolution = solutionList.getJSONObject(k); + Long solId = addSolution.getLong("solutionId"); + Double volume = addSolution.getDouble("volume"); + AcidPumpDeviceCode acidPumpDevice = containerService.getPumpBySolutionId(solId);//获取溶液对应的泵 + if (acidPumpDevice == null) { + throw new AppException(ResultCode.CRAFT_CONTAINER_NOT_FOUND);//未找到对应溶液容器 + } + deviceCommandUtilService.dualRobotMovePoint(num);//移动加液机械臂到指定试管点位 + Thread.sleep(3000); + deviceCommandUtilService.acidPumpMoveBy(cmdDTO.getCommandId(), cmdDTO.getCommand(), acidPumpDevice, volume);//添加溶液 + } + } + } + deviceCommandUtilService.dualRobotOrigin(); + deviceStateService.setSolutionModuleStatePumping(false); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/SolutionReduceCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/SolutionReduceCommand.java new file mode 100644 index 0000000..8e3d386 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/SolutionReduceCommand.java @@ -0,0 +1,67 @@ +package com.iflytop.sgs.app.cmd; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.ContainerService; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.enums.AcidPumpDeviceCode; +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.result.ResultCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +/** + * 抽取溶液 todo wmy 需要记录托盘每一行的坐标 机械臂移动成功后 调动泵机抽液体 + * + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("solution_add")//业务指令注解 +public class SolutionReduceCommand extends BaseCommandHandler { + private final DeviceCommandUtilService deviceCommandUtilService; + private final ContainerService containerService; + private final DeviceStateService deviceStateService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + deviceStateService.setSolutionModuleStatePumping(true); + return runAsync(() -> { + JSONArray dataList = JSONUtil.parseArray(cmdDTO.getParams().get("dataList")); + for (int i = 0; i < dataList.size(); i++) {//遍历前端传入的加液配置 + JSONObject tubeSol = dataList.getJSONObject(i); + String tubeNum = tubeSol.getStr("tubeNum");//获取试管编号 + int[] tubeNums = Arrays.stream(tubeNum.split(",")) + .mapToInt(s -> Integer.parseInt(s.trim())) + .toArray(); + JSONArray solutionList = tubeSol.getJSONArray("solutionList"); + for (int num : tubeNums) { + for (int k = 0; k < solutionList.size(); k++) { + JSONObject addSolution = solutionList.getJSONObject(k); + Long solId = addSolution.getLong("solutionId"); + Double volume = addSolution.getDouble("volume"); + AcidPumpDeviceCode acidPumpDevice = containerService.getPumpBySolutionId(solId);//获取溶液对应的泵 + if (acidPumpDevice == null) { + throw new AppException(ResultCode.CRAFT_CONTAINER_NOT_FOUND);//未找到对应溶液容器 + } + deviceCommandUtilService.dualRobotMovePoint(num);//移动加液机械臂到指定试管点位 + Thread.sleep(3000); + deviceCommandUtilService.acidPumpMoveBy(cmdDTO.getCommandId(), cmdDTO.getCommand(), acidPumpDevice, volume);//添加溶液 + } + } + } + deviceCommandUtilService.dualRobotOrigin(); + deviceStateService.setSolutionModuleStatePumping(false); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorCloseCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorCloseCommand.java new file mode 100644 index 0000000..26228fa --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorCloseCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 处理关门指令 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("door_close") +public class DebugDoorCloseCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.doorOrigin(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorOpenCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorOpenCommand.java new file mode 100644 index 0000000..b180635 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorOpenCommand.java @@ -0,0 +1,40 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.model.entity.DevicePosition; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.app.service.DevicePositionService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import com.iflytop.sgs.common.enums.data.DevicePositionCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 处理开门指令 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("door_open") +public class DebugDoorOpenCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + private final DevicePositionService devicePositionService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DevicePosition devicePosition = devicePositionService.getPosition(DevicePositionCode.doorOpen); + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.doorMove(devicePosition.getDistance()); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorStopCommand.java new file mode 100644 index 0000000..5b4df1e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugDoorStopCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 处理停止门运动指令处理器 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("door_stop") +public class DebugDoorStopCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.doorStop(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugFanStartCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugFanStartCommand.java new file mode 100644 index 0000000..60779d1 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugFanStartCommand.java @@ -0,0 +1,45 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 打开冷却风扇 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("fan_start") +public class DebugFanStartCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + String index = cmdDTO.getStringParam("index"); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(index); + DeviceCommandBundle deviceCommand; + switch (heatModuleId) { + case heat_module_01 -> deviceCommand = DeviceCommandGenerator.fan1Open(); + case heat_module_02 -> deviceCommand = DeviceCommandGenerator.fan2Open(); + case heat_module_03 -> deviceCommand = DeviceCommandGenerator.fan3Open(); + case heat_module_04 -> deviceCommand = DeviceCommandGenerator.fan4Open(); + default -> throw new RuntimeException("index 未找到"); + } + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugFanStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugFanStopCommand.java new file mode 100644 index 0000000..e1d464d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugFanStopCommand.java @@ -0,0 +1,45 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 关闭冷却风扇 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("fan_stop") +public class DebugFanStopCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + String index = cmdDTO.getStringParam("index"); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(index); + DeviceCommandBundle deviceCommand; + switch (heatModuleId) { + case heat_module_01 -> deviceCommand = DeviceCommandGenerator.fan1Close(); + case heat_module_02 -> deviceCommand = DeviceCommandGenerator.fan2Close(); + case heat_module_03 -> deviceCommand = DeviceCommandGenerator.fan3Close(); + case heat_module_04 -> deviceCommand = DeviceCommandGenerator.fan4Close(); + default -> throw new RuntimeException("index 未找到"); + } + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugHeaterStartCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugHeaterStartCommand.java new file mode 100644 index 0000000..7f95773 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugHeaterStartCommand.java @@ -0,0 +1,46 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 处理启动加热器指令 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heater_start") +public class DebugHeaterStartCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + String index = cmdDTO.getStringParam("index"); + Double temperature = cmdDTO.getDoubleParam("temperature"); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(index); + DeviceCommandBundle deviceCommand; + switch (heatModuleId) { + case heat_module_01 -> deviceCommand = DeviceCommandGenerator.heatRod1Open(temperature); + case heat_module_02 -> deviceCommand = DeviceCommandGenerator.heatRod2Open(temperature); + case heat_module_03 -> deviceCommand = DeviceCommandGenerator.heatRod3Open(temperature); + case heat_module_04 -> deviceCommand = DeviceCommandGenerator.heatRod4Open(temperature); + default -> throw new RuntimeException("index 未找到"); + } + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugHeaterStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugHeaterStopCommand.java new file mode 100644 index 0000000..2e7b50d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugHeaterStopCommand.java @@ -0,0 +1,45 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 处理停止加热器指令 todo 小何确定次序 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("heater_stop") +public class DebugHeaterStopCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + String index = cmdDTO.getStringParam("index"); + HeatModuleCode heatModuleId = HeatModuleCode.valueOf(index); + DeviceCommandBundle deviceCommand; + switch (heatModuleId) { + case heat_module_01 -> deviceCommand = DeviceCommandGenerator.heatRod1Close(); + case heat_module_02 -> deviceCommand = DeviceCommandGenerator.heatRod2Close(); + case heat_module_03 -> deviceCommand = DeviceCommandGenerator.heatRod3Close(); + case heat_module_04 -> deviceCommand = DeviceCommandGenerator.heatRod4Close(); + default -> throw new RuntimeException("index 未找到"); + } + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugLiquidPumpStartCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugLiquidPumpStartCommand.java new file mode 100644 index 0000000..7a2668b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugLiquidPumpStartCommand.java @@ -0,0 +1,46 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import com.iflytop.sgs.common.enums.AcidPumpDeviceCode; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.StepMotorCtrlDriver; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 处理加液泵启动指令 todo 需要加正向反向 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("liquid_pump_start") +public class DebugLiquidPumpStartCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + private final StepMotorCtrlDriver stepMotorCtrlDriver; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + Double velocity = cmdDTO.getDoubleParam("velocity"); + String direction=cmdDTO.getStringParam("direction");//front back 正转倒转 + AcidPumpDeviceCode acidPumpDevice = AcidPumpDeviceCode.liquid_pump; + DeviceCommandBundle deviceCommand; + if (velocity != null) { + deviceCommand = DeviceCommandGenerator.acidPump1Set(velocity); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.ACID_PUMP_1_MOTOR_MID, 1); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugLiquidPumpStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugLiquidPumpStopCommand.java new file mode 100644 index 0000000..684e2e4 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugLiquidPumpStopCommand.java @@ -0,0 +1,41 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import com.iflytop.sgs.common.enums.AcidPumpDeviceCode; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.StepMotorCtrlDriver; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 处理加液泵停止指令 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("liquid_pump_stop") +public class DebugLiquidPumpStopCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + private final StepMotorCtrlDriver stepMotorCtrlDriver; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + AcidPumpDeviceCode acidPumpDevice = AcidPumpDeviceCode.liquid_pump; + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.liquidPumpStop(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.ACID_PUMP_1_MOTOR_MID, 0); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidMoveByCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidMoveByCommand.java new file mode 100644 index 0000000..4b3178f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidMoveByCommand.java @@ -0,0 +1,75 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 加液位电机相对移动 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("motor_liquid_move_by") +public class DebugMotorLiquidMoveByCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + private boolean stop = false; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + this.stop = false; + return runAsync(() -> { + Double dimDistance = cmdDTO.getDoubleParam("distance"); + + Double dimVelocity = cmdDTO.getDoubleParam("velocity"); + Integer times = cmdDTO.getIntegerParam("times"); + + + if (dimVelocity != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.motorLiquidSet(dimVelocity); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + if (times != null) { + for (int i = 0; i < times; i++) { + if (stop) { + return; + } + if (dimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.motorLiquidSet(dimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + if (dimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.motorLiquidSet(-dimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + } + } else { + if (dimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.motorLiquidSet(dimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + + } + + + }); + } + + public synchronized void stop() { + this.stop = true; + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidMoveToCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidMoveToCommand.java new file mode 100644 index 0000000..28f2f3e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidMoveToCommand.java @@ -0,0 +1,37 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 加液位电机绝对移动 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("motor_liquid_position") +public class DebugMotorLiquidMoveToCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) throws Exception { + double position = cmdDTO.getDoubleParam("position"); + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.motorLiquidSet(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidResetCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidResetCommand.java new file mode 100644 index 0000000..f36c8a8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidResetCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 加液位电机复位 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("motor_liquid_origin") +public class DebugMotorLiquidResetCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.motorLiquidOrigin(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidStopCommand.java new file mode 100644 index 0000000..c060ec7 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorLiquidStopCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 加液位电机停止 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("motor_liquid_stop") +public class DebugMotorLiquidStopCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.motorLiquidStop(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXMoveByCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXMoveByCommand.java new file mode 100644 index 0000000..a984edd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXMoveByCommand.java @@ -0,0 +1,78 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * x轴电机相对移动 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_x_move_by") +public class DebugMotorXMoveByCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + private boolean stop = false; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + this.stop = false; + return runAsync(() -> { + Double xDimDistance = cmdDTO.getDoubleParam("distance"); + + + Double xDimVelocity = cmdDTO.getDoubleParam("velocity"); + + Integer times = cmdDTO.getIntegerParam("times"); + + if (xDimVelocity != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXSet(xDimVelocity); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + if (times != null) { + for (int i = 0; i < times; i++) { + if (stop) { + return; + } + if (xDimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXMoveBy(xDimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + + if (xDimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXMoveBy(-xDimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + + } + } else { + if (xDimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXMoveBy(xDimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + + } + + + }); + } + + public synchronized void stop() { + this.stop = true; + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXMoveToCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXMoveToCommand.java new file mode 100644 index 0000000..a4bd7be --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXMoveToCommand.java @@ -0,0 +1,41 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * x轴电机绝对移动 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_x_move_to") +public class DebugMotorXMoveToCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + private boolean stop = false; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + double position = cmdDTO.getDoubleParam("position"); + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXMove(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } + + public synchronized void stop() { + this.stop = true; + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXOriginCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXOriginCommand.java new file mode 100644 index 0000000..aa0dc4a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXOriginCommand.java @@ -0,0 +1,39 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * 处理复位x轴电机复位 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_x_origin") +public class DebugMotorXOriginCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + List futuresList = new ArrayList<>(); + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXOrigin(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + futuresList.add(deviceCommandFuture); + commandWait(futuresList.toArray(new CommandFuture[0])); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXStopCommand.java new file mode 100644 index 0000000..25a92b7 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorXStopCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * x轴电机停止 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_x_stop") +public class DebugMotorXStopCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXStop(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZMoveByCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZMoveByCommand.java new file mode 100644 index 0000000..314e4f5 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZMoveByCommand.java @@ -0,0 +1,76 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * z轴电机相对移动 + * + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_z_move_by") +public class DebugMotorZMoveByCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + private boolean stop = false; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + this.stop = false; + return runAsync(() -> { + Double zDimDistance = cmdDTO.getDoubleParam("distance"); + + Double zDimVelocity = cmdDTO.getDoubleParam("velocity"); + Integer times = cmdDTO.getIntegerParam("times"); + + + if (zDimVelocity != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZSet(zDimVelocity); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + if (times != null) { + for (int i = 0; i < times; i++) { + if (stop) { + return; + } + if (zDimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZMoveBy(zDimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + if (zDimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZMoveBy(-zDimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + } + } else { + if (zDimDistance != null) { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZMoveBy(zDimDistance); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + } + + } + + + }); + } + + public synchronized void stop() { + this.stop = true; + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZMoveToCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZMoveToCommand.java new file mode 100644 index 0000000..3a7620d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZMoveToCommand.java @@ -0,0 +1,37 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * z轴电机绝对移动 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_z_position") +public class DebugMotorZMoveToCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) throws Exception { + double position = cmdDTO.getDoubleParam("position"); + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZMove(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZOriginCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZOriginCommand.java new file mode 100644 index 0000000..6f7af97 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZOriginCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * z轴电机复位 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_z_origin") +public class DebugMotorZOriginCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZOrigin(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZStopCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZStopCommand.java new file mode 100644 index 0000000..126a600 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugMotorZStopCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * z轴电机停止 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("gantry_z_stop") +public class DebugMotorZStopCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZStop(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenThickWayCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenThickWayCommand.java new file mode 100644 index 0000000..735c8fd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenThickWayCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 打开冷却风扇 todo 需要与小何确定次序 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("liquid_valve_open_thick_way") +public class DebugValveOpenThickWayCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand=DeviceCommandGenerator.valveTurn(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenThinWayCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenThinWayCommand.java new file mode 100644 index 0000000..cf2a57b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenThinWayCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 打开冷却风扇 todo 需要与小何确定次序 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("liquid_valve_open_thin_way") +public class DebugValveOpenThinWayCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand=DeviceCommandGenerator.valveTurn(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenVacantWayCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenVacantWayCommand.java new file mode 100644 index 0000000..7d1ff4e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenVacantWayCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 打开冷却风扇 todo 需要与小何确定次序 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("liquid_valve_open_vacant_way") +public class DebugValveOpenVacantWayCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand=DeviceCommandGenerator.valveTurn(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenWasteWayCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenWasteWayCommand.java new file mode 100644 index 0000000..c3cf973 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenWasteWayCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 打开冷却风扇 todo 需要与小何确定次序 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("liquid_valve_open_waste_way") +public class DebugValveOpenWasteWayCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand=DeviceCommandGenerator.valveTurn(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenWaterWayCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenWaterWayCommand.java new file mode 100644 index 0000000..acf89b8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/DebugValveOpenWaterWayCommand.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.cmd.debug; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 打开冷却风扇 todo 需要与小何确定次序 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("liquid_valve_open_water_way") +public class DebugValveOpenWaterWayCommand extends BaseCommandHandler { + private final DeviceCommandService deviceCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) { + return runAsync(() -> { + DeviceCommandBundle deviceCommand=DeviceCommandGenerator.valveTurn(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdDTO.getCommandId(), cmdDTO.getCommand(), deviceCommand); + commandWait(deviceCommandFuture); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugDisabledAllMotorCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugDisabledAllMotorCommand.java new file mode 100644 index 0000000..93aef24 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugDisabledAllMotorCommand.java @@ -0,0 +1,31 @@ +package com.iflytop.sgs.app.cmd.debug.step; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.StepCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 失能所有电机 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("debug_disabled_all_motor") +public class DebugDisabledAllMotorCommand extends BaseCommandHandler { + private final StepCommandService stepCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) throws Exception { + + return runAsync(() -> { + stepCommandService.disabilityAll(); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugEnableAllMotorCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugEnableAllMotorCommand.java new file mode 100644 index 0000000..08795e2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugEnableAllMotorCommand.java @@ -0,0 +1,31 @@ +package com.iflytop.sgs.app.cmd.debug.step; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.StepCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 使能所有电机 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("debug_enable_all_motor") +public class DebugEnableAllMotorCommand extends BaseCommandHandler { + private final StepCommandService stepCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) throws Exception { + + return runAsync(() -> { + stepCommandService.enableAll(); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugStopAllMotorCommand.java b/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugStopAllMotorCommand.java new file mode 100644 index 0000000..7a152a9 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/cmd/debug/step/DebugStopAllMotorCommand.java @@ -0,0 +1,30 @@ +package com.iflytop.sgs.app.cmd.debug.step; + +import com.iflytop.sgs.app.core.BaseCommandHandler; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.StepCommandService; +import com.iflytop.sgs.common.annotation.CommandMapping; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +/** + * 停止所有电机 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@CommandMapping("debug_stop_all_motor") +public class DebugStopAllMotorCommand extends BaseCommandHandler { + private final StepCommandService stepCommandService; + + @Override + public CompletableFuture handle(CmdDTO cmdDTO) throws Exception { + return runAsync(() -> { + stepCommandService.stopAll(); + }); + } +} + diff --git a/src/main/java/com/iflytop/sgs/app/config/A8kCanBusConnectionConfig.java b/src/main/java/com/iflytop/sgs/app/config/A8kCanBusConnectionConfig.java new file mode 100644 index 0000000..0fb84af --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/config/A8kCanBusConnectionConfig.java @@ -0,0 +1,32 @@ +package com.iflytop.sgs.app.config; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusConnection; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class A8kCanBusConnectionConfig { + //协议参考 https://iflytop1.feishu.cn/wiki/QFwVwGnI8iYp0fk20W9cnpYAnkg + @Value("${iflytophald.ip}") + String ip; + + @Value("${iflytophald.cmdch.port}") + Integer cmdchPort; + + @Value("${iflytophald.datach.port}") + Integer datachPort; + + public String getDatachUrl(String datachannel) { + return String.format("ws://%s:%d/%s", ip, datachPort, datachannel); + } + + public String getCmdChBaseUrl(String cmdchannel) { + return String.format("http://%s:%d/%s", ip, cmdchPort, cmdchannel); + } + + @Bean + A8kCanBusConnection a8kCanBusBaseService() { + return new A8kCanBusConnection(getCmdChBaseUrl("zexcan"), getDatachUrl("zexcan")); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/config/AsyncConfig.java b/src/main/java/com/iflytop/sgs/app/config/AsyncConfig.java new file mode 100644 index 0000000..4c1ce4c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/config/AsyncConfig.java @@ -0,0 +1,21 @@ +package com.iflytop.sgs.app.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +public class AsyncConfig { + @Bean("commandTaskExecutor") + public ThreadPoolTaskExecutor taskExecutor() { + ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor(); + exec.setCorePoolSize(10); + exec.setMaxPoolSize(20); + exec.setQueueCapacity(50); + exec.setThreadNamePrefix("cmd-exec-"); + exec.setWaitForTasksToCompleteOnShutdown(true); + exec.setAwaitTerminationSeconds(30); + exec.initialize(); + return exec; + } +} diff --git a/src/main/java/com/iflytop/sgs/app/config/CraftsStateMachineConfig.java b/src/main/java/com/iflytop/sgs/app/config/CraftsStateMachineConfig.java new file mode 100644 index 0000000..e3ac425 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/config/CraftsStateMachineConfig.java @@ -0,0 +1,76 @@ +package com.iflytop.sgs.app.config; + +import com.iflytop.sgs.common.enums.automaton.CraftEvents; +import com.iflytop.sgs.common.enums.automaton.CraftStates; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +import java.util.EnumSet; + +@Configuration +@EnableStateMachineFactory +public class CraftsStateMachineConfig + extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(CraftStates.READY) + .states(EnumSet.allOf(CraftStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer trans) throws Exception { + trans + // 启动 + .withExternal() + .source(CraftStates.READY) + .target(CraftStates.RUNNING) + .event(CraftEvents.START) + .and() + // 单步完成(保持 RUNNING) + .withInternal() + .source(CraftStates.RUNNING) + .event(CraftEvents.STEP_COMPLETE) + .and() + // 暂停 + .withExternal() + .source(CraftStates.RUNNING) + .target(CraftStates.PAUSED) + .event(CraftEvents.PAUSE) + .and() + // 恢复 + .withExternal() + .source(CraftStates.PAUSED) + .target(CraftStates.RUNNING) + .event(CraftEvents.RESUME) + .and() + // 用户手动停止:RUNNING → STOPPED + .withExternal() + .source(CraftStates.RUNNING) + .target(CraftStates.STOPPED) + .event(CraftEvents.STOP) + .and() + // 用户手动停止:PAUSED → STOPPED + .withExternal() + .source(CraftStates.PAUSED) + .target(CraftStates.STOPPED) + .event(CraftEvents.STOP) + .and() + // 出错 + .withExternal() + .source(CraftStates.RUNNING) + .target(CraftStates.ERROR) + .event(CraftEvents.ERROR_OCCUR) + .and() + // 正常完成 + .withExternal() + .source(CraftStates.RUNNING) + .target(CraftStates.FINISHED) + .event(CraftEvents.FINISH); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/config/MybatisPlusConfig.java b/src/main/java/com/iflytop/sgs/app/config/MybatisPlusConfig.java new file mode 100644 index 0000000..fd4fbd8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/config/MybatisPlusConfig.java @@ -0,0 +1,61 @@ +package com.iflytop.sgs.app.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.iflytop.sgs.common.handler.MyMetaObjectHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * + */ +@EnableTransactionManagement +@Configuration +public class MybatisPlusConfig { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 乐观锁插件 + interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); + // 分页插件 + interceptor.addInnerInterceptor(paginationInnerInterceptor()); + + return interceptor; + } + + /** + * 分页插件,自动识别数据库类型 + * ... + */ + public PaginationInnerInterceptor paginationInnerInterceptor() { + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + // 设置数据库类型 + paginationInnerInterceptor.setDbType(DbType.SQLITE); + // 设置最大单页限制数量,默认 500 条,-1 不受限制 + paginationInnerInterceptor.setMaxLimit(-1L); + return paginationInnerInterceptor; + } + + /** + * 乐观锁插件 + * ... + */ + public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { + return new OptimisticLockerInnerInterceptor(); + } + + /** + * 自动填充数据库创建人、创建时间、更新人、更新时间 + */ + @Bean + public GlobalConfig globalConfig() { + GlobalConfig globalConfig = new GlobalConfig(); + globalConfig.setMetaObjectHandler(new MyMetaObjectHandler()); + return globalConfig; + } +} diff --git a/src/main/java/com/iflytop/sgs/app/config/SwaggerConfig.java b/src/main/java/com/iflytop/sgs/app/config/SwaggerConfig.java new file mode 100644 index 0000000..ba59d46 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/config/SwaggerConfig.java @@ -0,0 +1,68 @@ +package com.iflytop.sgs.app.config; + +import cn.hutool.core.util.ArrayUtil; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springdoc.core.customizers.GlobalOpenApiCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.util.AntPathMatcher; + +import java.util.stream.Stream; + +/*** + * 创建Swagger配置 + */ +@Configuration +public class SwaggerConfig { + + @Bean + public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() { + return openApi -> { + // 全局添加Authorization + if (openApi.getPaths() != null) { + openApi.getPaths().forEach((path, pathItem) -> { + + // 忽略认证的请求无需携带 Authorization + String[] ignoreUrls = {"/api/auth/login"}; + if (ArrayUtil.isNotEmpty(ignoreUrls)) { + // Ant 匹配忽略的路径,不添加Authorization + AntPathMatcher antPathMatcher = new AntPathMatcher(); + if (Stream.of(ignoreUrls).anyMatch(ignoreUrl -> antPathMatcher.match(ignoreUrl, path))) { + return; + } + } + + // 其他接口统一添加Authorization +// pathItem.readOperations() +// .forEach(operation -> +// operation.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION)) +// ); + }); + } + }; + } + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("系统API") + .version("1.0")) // 配置全局鉴权参数-Authorize + .components(new Components() + .addSecuritySchemes(HttpHeaders.AUTHORIZATION, + new SecurityScheme() + .name(HttpHeaders.AUTHORIZATION) + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .scheme("Bearer") + .bearerFormat("JWT") + ) + ); + } + + +} diff --git a/src/main/java/com/iflytop/sgs/app/config/WebConfig.java b/src/main/java/com/iflytop/sgs/app/config/WebConfig.java new file mode 100644 index 0000000..5e2550c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/config/WebConfig.java @@ -0,0 +1,16 @@ +package com.iflytop.sgs.app.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedHeaders("*"); + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/controller/AuthController.java b/src/main/java/com/iflytop/sgs/app/controller/AuthController.java new file mode 100644 index 0000000..e3d407f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/AuthController.java @@ -0,0 +1,57 @@ +package com.iflytop.sgs.app.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.iflytop.sgs.app.model.dto.LoginDTO; +import com.iflytop.sgs.app.model.entity.User; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.app.service.UserService; +import com.iflytop.sgs.common.enums.data.Deleted; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.common.result.ResultCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.Objects; + +/** + * 认证控制 + */ +@Tag(name = "认证") +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +@Slf4j +public class AuthController { + + private final UserService userService; + private final DeviceStateService deviceStateService; + + @Operation(summary = "账号密码登录") + @PostMapping("/login") + public Result login(@Valid @RequestBody LoginDTO loginDTO) { + User user = userService.getOne(new LambdaQueryWrapper<>(User.class).eq(User::getUsername, loginDTO.getUsername())); + if (user != null && !Objects.equals(user.getDeleted(), Deleted.ENABLE) && user.getPassword().equals(loginDTO.getPassword())) { + deviceStateService.setCurrentUser(user); + user.setPassword(null); + return Result.success(user); + } + return Result.failed(ResultCode.INVALID_CREDENTIALS); + } + + @Operation(summary = "用户登出") + @PostMapping("/logout") + public Result logout() { + deviceStateService.setCurrentUser(null); + return Result.success(); + } + + @Operation(summary = "获取当前登录用户") + @GetMapping("/current") + public Result current() { + return Result.success(deviceStateService.getDeviceState().getCurrentUser()); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/CmdController.java b/src/main/java/com/iflytop/sgs/app/controller/CmdController.java new file mode 100644 index 0000000..4bf4bfd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/CmdController.java @@ -0,0 +1,74 @@ +package com.iflytop.sgs.app.controller; + +import cn.hutool.json.JSONUtil; +import com.iflytop.sgs.app.core.CommandHandlerRegistry; +import com.iflytop.sgs.app.core.DebugGenerator; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.app.service.WebSocketService; +import com.iflytop.sgs.common.cmd.CommandHandler; +import com.iflytop.sgs.common.constant.CommandStatus; +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.common.result.ResultCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.CompletableFuture; + +@Tag(name = "前端业务指令") +@RestController +@RequestMapping("/api/cmd") +@RequiredArgsConstructor +@Slf4j +public class CmdController { + private final CommandHandlerRegistry registry; + private final WebSocketService webSocketService; + private final DeviceStateService deviceStateService; + + @Operation(summary = "前端统一调用一个接口") + @PostMapping + public Result controlMethod(@Valid @RequestBody CmdDTO cmdDTO) throws Exception { + String commandId = cmdDTO.getCommandId(); + String command = cmdDTO.getCommand(); + try { + webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.RECEIVE, "已收到业务指令请求,开始处理")); + if (deviceStateService.getDeviceState().isEmergencyStop()) { + throw new AppException(ResultCode.EMERGENCY_STOP); + } + CommandHandler commandHandler = registry.getCommandHandler(command); + if (commandHandler == null) { + webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.DEVICE_ERROR, "未找到对应的业务指令")); + log.error("未找到对应的业务指令"); + return Result.failed(); + } + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.START, "业务指令开始执行")); + log.info("业务指令开始执行"); + CompletableFuture future = commandHandler.handle(cmdDTO); + future.whenComplete((v, ex) -> { + if (ex != null) { + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FAIL, "执行业务指令发生异常", ex.getMessage())); + log.error("执行业务指令发生异常: {}", JSONUtil.toJsonStr(cmdDTO), ex); + } else { + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.SUCCESS, "业务指令执行成功")); + log.info("业务指令执行成功"); + } + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FINISH, "业务指令执行结束")); + log.info("业务指令执行结束"); + }); + } catch (Exception e) { + log.error("执行业务指令发生异常: {}", JSONUtil.toJsonStr(cmdDTO), e); + return Result.failed(e.getMessage()); + } + return Result.success(); + } + + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/CmdDebugController.java b/src/main/java/com/iflytop/sgs/app/controller/CmdDebugController.java new file mode 100644 index 0000000..9c08574 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/CmdDebugController.java @@ -0,0 +1,67 @@ +package com.iflytop.sgs.app.controller; + +import cn.hutool.json.JSONUtil; +import com.iflytop.sgs.app.core.CommandHandlerRegistry; +import com.iflytop.sgs.app.core.DebugGenerator; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import com.iflytop.sgs.app.service.WebSocketService; +import com.iflytop.sgs.common.cmd.CommandHandler; +import com.iflytop.sgs.common.constant.CommandStatus; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.CompletableFuture; + +@Tag(name = "前端调试指令") +@RestController +@RequestMapping("/api/debug/cmd") +@RequiredArgsConstructor +@Slf4j +public class CmdDebugController { + private final CommandHandlerRegistry registry; + private final WebSocketService webSocketService; + + @Operation(summary = "前端调试指令") + @PostMapping + public Result controlMethod(@Valid @RequestBody CmdDTO cmdDTO) { + String commandId = cmdDTO.getCommandId(); + String command = cmdDTO.getCommand(); + try { + webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.RECEIVE, "已收到调试指令请求,开始处理")); + CommandHandler commandHandler = registry.getCommandHandler(command); + if (commandHandler == null) { + webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.DEVICE_ERROR, "未找到对应的调试指令")); + log.error("未找到对应的调试指令"); + return Result.failed(); + } + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.START, "调试指令开始执行")); + log.info("调试指令开始执行"); + CompletableFuture future = commandHandler.handle(cmdDTO); + future.whenComplete((v, ex) -> { + if (ex != null) { + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FAIL, "执行调试指令发生异常", ex.getMessage())); + log.error("执行调试指令发生异常: {}", JSONUtil.toJsonStr(cmdDTO), ex); + } else { + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.SUCCESS, "调试指令执行成功")); + log.info("调试指令执行成功"); + } + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FINISH, "调试指令执行结束")); + log.info("调试指令执行结束"); + }); + } catch (Exception e) { + log.error("执行调试指令发生异常: {}", JSONUtil.toJsonStr(cmdDTO), e); + return Result.failed(e.getMessage()); + } + return Result.success(); + } + + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/ContainerController.java b/src/main/java/com/iflytop/sgs/app/controller/ContainerController.java new file mode 100644 index 0000000..64c6887 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/ContainerController.java @@ -0,0 +1,34 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.model.entity.Container; +import com.iflytop.sgs.app.service.ContainerService; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "容器") +@RestController +@RequestMapping("/api/container") +@RequiredArgsConstructor +@Slf4j +public class ContainerController { + private final ContainerService containerService; + + @Operation(summary = "容器列表") + @GetMapping("/list") + public Result> getList() { + return Result.success(containerService.getList()); + } + + @Operation(summary = "更新容器配置") + @PutMapping("") + public Result update(@Valid @RequestBody Container container) { + return Result.success(containerService.updateById(container)); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/CraftsController.java b/src/main/java/com/iflytop/sgs/app/controller/CraftsController.java new file mode 100644 index 0000000..ed1bef2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/CraftsController.java @@ -0,0 +1,112 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.model.dto.*; +import com.iflytop.sgs.app.model.entity.Crafts; +import com.iflytop.sgs.app.model.vo.SetCraftsVO; +import com.iflytop.sgs.app.service.CraftsService; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.common.result.ResultCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "工艺管理") +@RestController +@RequestMapping("/api/crafts") +@RequiredArgsConstructor +@Slf4j +@Validated +public class CraftsController { + private final CraftsService craftsService; + + @Operation(summary = "根据矿石id获取工艺列表") + @GetMapping("/list/{oresId}") + public Result> getList( + @NotNull(message = "矿石ID 不能为空") + @Min(value = 1, message = "矿石ID 必须大于等于 1") + @Parameter(description = "矿石ID") @PathVariable Long oresId) { + List craftList = craftsService.selectAllByOresId(oresId); + return Result.success(craftList); + } + + @Operation(summary = "添加新工艺") + @PostMapping("") + public Result add(@Valid @RequestBody Crafts crafts) { + Crafts existingCrafts = craftsService.findByName(crafts.getName()); + if (existingCrafts == null) { + boolean isSuccess = craftsService.save(crafts); + if (isSuccess) { + return Result.success(); + } + } else { + return Result.failed(ResultCode.DATA_ALREADY_EXISTS); + } + return Result.failed(); + } + + @Operation(summary = "更新工艺") + @PutMapping("") + public Result updateCrafts(@Valid @RequestBody Crafts crafts) { + boolean isSuccess = craftsService.updateById(crafts); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } + + @Operation(summary = "删除工艺") + @DeleteMapping("/{ids}") + public Result delete( + @Parameter(description = "工艺ID,多个以英文逗号(,)分割") @PathVariable String ids) { + boolean isSuccess = craftsService.deleteCrafts(ids); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } + + @Operation(summary = "配置加热区工艺") + @PostMapping("/set") + public Result setCrafts(@Valid @RequestBody SetCraftsDTO setCraftsDTO) { + return Result.success(craftsService.setCraft(setCraftsDTO.getCraftId(), setCraftsDTO.getHeatId())); + } + + + @Operation(summary = "开始执行工艺") + @PostMapping("/start") + public Result startCrafts(@Valid @RequestBody StartCraftsDTO startCraftsDTO) { + craftsService.startCrafts(startCraftsDTO.getHeatId()); + return Result.success(); + } + + @Operation(summary = "暂停执行工艺") + @PostMapping("/pause") + public Result pauseCrafts(@Valid @RequestBody PauseCraftsDto pauseCraftsDto) { + craftsService.pauseCrafts(pauseCraftsDto.getHeatId()); + return Result.success(); + } + + @Operation(summary = "恢复执行工艺") + @PostMapping("/resume") + public Result resumeCrafts(@Valid @RequestBody ResumeCraftsDTO resumeCraftsDto) { + craftsService.resumeCrafts(resumeCraftsDto.getHeatId()); + return Result.success(); + } + + @Operation(summary = "停止执行工艺") + @PostMapping("/stop") + public Result stopCrafts(@Valid @RequestBody StopCraftsDTO stopCraftsDto) { + craftsService.stopCrafts(stopCraftsDto.getHeatId()); + return Result.success(); + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/DeviceParamController.java b/src/main/java/com/iflytop/sgs/app/controller/DeviceParamController.java new file mode 100644 index 0000000..a2be298 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/DeviceParamController.java @@ -0,0 +1,121 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.model.entity.DeviceParamConfig; +import com.iflytop.sgs.app.model.vo.DeviceParamGroupVO; +import com.iflytop.sgs.app.model.vo.ModuleIdVO; +import com.iflytop.sgs.app.model.vo.RegIndexVO; +import com.iflytop.sgs.app.service.DeviceParamConfigService; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * 设备参数配置 + */ +@Tag(name = "设备参数配置") +@RestController +@RequestMapping("/api/device-param") +@RequiredArgsConstructor +@Slf4j +public class DeviceParamController { + + private final DeviceParamConfigService deviceParamConfigService; + + @Operation(summary = "获取所有设备配置") + @GetMapping("/list") + public Result> getList() { + List vos = deviceParamConfigService.listGroupedByModule(); + return Result.success(vos); + } + + @Operation(summary = "获取所有设备模块") + @GetMapping("/modules") + public Result> listAllModules() { + List vos = deviceParamConfigService.listAllModuleIds(); + return Result.success(vos); + } + + @Operation(summary = "获取所有设备模块配置项") + @GetMapping("/reg-indices") + public Result> listAllRegIndices() { + List vos = deviceParamConfigService.listAllRegIndices(); + return Result.success(vos); + } + + + @Operation(summary = "添加新配置") + @PostMapping("") + public Result add(@Valid @RequestBody DeviceParamConfig deviceParamConfig) { + deviceParamConfigService.save(deviceParamConfig); + return Result.success(); + } + + @Operation(summary = "修改配置") + @PutMapping("") + public Result update(@Valid @RequestBody DeviceParamConfig deviceParamConfig) { + deviceParamConfigService.updateById(deviceParamConfig); + return Result.success(); + } + + @Operation(summary = "删除配置") + @DeleteMapping("/{ids}") + public Result delete(@Parameter(description = "矿石ID,多个以英文逗号(,)分割") @PathVariable String ids) { + boolean isSuccess = deviceParamConfigService.deleteDeviceParam(ids); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } + + @Operation(summary = "上传csv文件覆盖数据库配置") + @PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Result uploadFile(@RequestPart("file") MultipartFile file) { + //文件后缀检查 + if (!file.getOriginalFilename().endsWith(".csv")) { + return Result.failed(".csv"); + } + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { + String line; + //是否含有表头 + boolean isHeader = true; + List deviceParamConfigList = new ArrayList<>(); + while ((line = reader.readLine()) != null) { + if (isHeader) { + isHeader = false; + continue; // 跳过CSV文件的标题行 + } + String[] data = line.split(","); + if (data.length >= 4) { // 假设CSV文件至少包含4列 + DeviceParamConfig deviceParamConfig = new DeviceParamConfig(); + deviceParamConfig.setId(Long.valueOf(data[0])); + deviceParamConfig.setMid(String.valueOf(data[1])); + deviceParamConfig.setRegIndex(String.valueOf(data[2])); + deviceParamConfig.setRegVal(Integer.valueOf(data[3])); + deviceParamConfigService.save(deviceParamConfig); + deviceParamConfigList.add(deviceParamConfig); + } + } + deviceParamConfigService.saveOrUpdateBatch(deviceParamConfigList); + } catch (IOException e) { + log.error("csv文件导入数据库失败:{}", e.getMessage()); + throw new RuntimeException(e); + } + return Result.success(); + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/DevicePointController.java b/src/main/java/com/iflytop/sgs/app/controller/DevicePointController.java new file mode 100644 index 0000000..34f27b8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/DevicePointController.java @@ -0,0 +1,59 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.model.dto.AddDevicePositionDTO; +import com.iflytop.sgs.app.model.entity.DevicePosition; +import com.iflytop.sgs.app.model.vo.DevicePositionVO; +import com.iflytop.sgs.app.service.DevicePositionService; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 设备点位接口 + */ +@Tag(name = "设备位置配置") +@RestController +@RequestMapping("/api/device-point") +@RequiredArgsConstructor +@Slf4j +public class DevicePointController { + + private final DevicePositionService devicePointService; + + @Operation(summary = "列表") + @GetMapping("/list") + public Result> listGroupedParams() { + List devicePointVOList = devicePointService.getList(); + return Result.success(devicePointVOList); + } + + @Operation(summary = "添加") + @PostMapping("") + public Result add(@Valid @RequestBody DevicePosition devicePoint) { + devicePointService.save(devicePoint); + return Result.success(); + } + + @Operation(summary = "修改") + @PutMapping("") + public Result update(@Valid @RequestBody AddDevicePositionDTO addDevicePositionDTO) { + return Result.success(devicePointService.updatePosition(addDevicePositionDTO)); + } + + @Operation(summary = "删除") + @DeleteMapping("/{ids}") + public Result delete(@Parameter(description = "位置ID,多个以英文逗号(,)分割") @PathVariable String ids) { + boolean isSuccess = devicePointService.deletePositon(ids); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/HeatModuleController.java b/src/main/java/com/iflytop/sgs/app/controller/HeatModuleController.java new file mode 100644 index 0000000..b7560b8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/HeatModuleController.java @@ -0,0 +1,33 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.model.vo.SetTargetTemperatureVO; +import com.iflytop.sgs.app.service.HeatModuleService; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 加热模块控制 todo wmy 需要和前端同步参数 加热 烘干 退火 + */ +@Tag(name = "加热模块控制") +@RestController +@RequestMapping("/api/heat") +@RequiredArgsConstructor +@Slf4j +public class HeatModuleController { + private final HeatModuleService heatModuleService; + + @Operation(summary = "加热模块设定目标温度") + @PostMapping("/target-temperature") + public Result setTargetTemperature(@Valid @RequestBody SetTargetTemperatureVO setTargetTemperatureVO) { + heatModuleService.setTargetTemperature(setTargetTemperatureVO); + return Result.success(); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/OresController.java b/src/main/java/com/iflytop/sgs/app/controller/OresController.java new file mode 100644 index 0000000..17b1081 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/OresController.java @@ -0,0 +1,74 @@ +package com.iflytop.sgs.app.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.iflytop.sgs.app.model.entity.Ores; +import com.iflytop.sgs.app.model.vo.OresCraftsListVO; +import com.iflytop.sgs.app.service.OresService; +import com.iflytop.sgs.common.base.BasePageQuery; +import com.iflytop.sgs.common.result.PageResult; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.common.result.ResultCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "矿石管理") +@RestController +@RequestMapping("/api/ores") +@RequiredArgsConstructor +@Slf4j +public class OresController { + private final OresService oresService; + + @Operation(summary = "矿石工艺列表") + @GetMapping("/list") + public PageResult getList(BasePageQuery pageQuery) { + IPage result = oresService.getPage(pageQuery); + return PageResult.success(result); + } + + @Operation(summary = "添加新矿石") + @PostMapping("") + public Result add(@Valid @RequestBody Ores ores) { + Ores existingOres = oresService.findByName(ores.getName()); + if (existingOres == null) { + boolean isSuccess = oresService.addOres(ores); + if (isSuccess) { + return Result.success(); + } + } else { + return Result.failed(ResultCode.DATA_ALREADY_EXISTS); + } + return Result.failed(); + } + + @Operation(summary = "更新矿石") + @PutMapping("") + public Result update(@Valid @RequestBody Ores ores) { + Ores existingOres = oresService.findByName(ores.getName()); + if (existingOres == null) { + boolean isSuccess = oresService.updateOres(ores); + if (isSuccess) { + return Result.success(); + } + } else { + return Result.failed(ResultCode.DATA_ALREADY_EXISTS); + } + return Result.failed(); + } + + @Operation(summary = "删除矿石") + @DeleteMapping("/{ids}") + public Result delete(@Parameter(description = "矿石ID,多个以英文逗号(,)分割") @PathVariable String ids) { + boolean isSuccess = oresService.deleteOres(ids); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/SelfTestController.java b/src/main/java/com/iflytop/sgs/app/controller/SelfTestController.java new file mode 100644 index 0000000..cf03dca --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/SelfTestController.java @@ -0,0 +1,59 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.core.device.SelfTestState; +import com.iflytop.sgs.app.service.DeviceCommandUtilService; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.app.service.SelfTestService; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "自检") +@RestController +@RequestMapping("/api/self-test") +@RequiredArgsConstructor +@Slf4j +public class SelfTestController { + private final SelfTestService selfTestService; + private final DeviceStateService deviceStateService; + private final DeviceCommandUtilService deviceCommandUtilService; + + @Operation(summary = "获取自检状态") + @GetMapping("/status") + public Result getSelfTestStatus() { + return Result.success(selfTestService.getSelfTestState()); + } + + @Operation(summary = "自检完毕") + @PostMapping("/finish") + public Result selfTestFinish() throws Exception { + deviceStateService.setSelfTest(true); + new Thread(() -> { + try { + //deviceCommandUtilService.capMotorMove(21); + } catch (Exception e) { + log.error("拍子存放区初始化上升失败", e); + } + }).start(); + return Result.success(); + } + @Operation(summary = "改变自检状态") + @PostMapping("/isFinish") + public Result selfTestFinish(boolean isFinish) throws Exception { + deviceStateService.setSelfTest(isFinish); + new Thread(() -> { + try { + //deviceCommandUtilService.capMotorMove(21); + } catch (Exception e) { + log.error("拍子存放区初始化上升失败", e); + } + }).start(); + return Result.success(); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/SolutionsController.java b/src/main/java/com/iflytop/sgs/app/controller/SolutionsController.java new file mode 100644 index 0000000..66f28fc --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/SolutionsController.java @@ -0,0 +1,74 @@ +package com.iflytop.sgs.app.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.iflytop.sgs.app.model.entity.Solutions; +import com.iflytop.sgs.app.service.SolutionsService; +import com.iflytop.sgs.common.base.BasePageQuery; +import com.iflytop.sgs.common.result.PageResult; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.common.result.ResultCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "溶液管理") +@RestController +@RequestMapping("/api/sols") +@RequiredArgsConstructor +@Slf4j +public class SolutionsController { + private final SolutionsService solutionsService; + + @Operation(summary = "溶液列表") + @GetMapping("/list") + public PageResult getAllSols(BasePageQuery pageQuery) { + IPage result = solutionsService.page(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), null); + return PageResult.success(result); + } + + @Operation(summary = "添加新溶液") + @PostMapping("") + public Result addSolutions(@Valid @RequestBody Solutions solutions) { + Solutions existingSolutions = solutionsService.findByName(solutions.getName()); + if (existingSolutions == null) { + boolean isSuccess = solutionsService.addSolutions(solutions); + if (isSuccess) { + return Result.success(); + } + } else { + return Result.failed(ResultCode.DATA_ALREADY_EXISTS); + } + return Result.failed(); + } + + @Operation(summary = "更新溶液") + @PutMapping("") + public Result updateSolutions(@Valid @RequestBody Solutions solutions) { + Solutions existingSolutions = solutionsService.findByName(solutions.getName()); + if (existingSolutions == null) { + boolean isSuccess = solutionsService.updateSolutions(solutions); + if (isSuccess) { + return Result.success(); + } + } else { + return Result.failed(ResultCode.DATA_ALREADY_EXISTS); + } + return Result.failed(); + } + + @Operation(summary = "删除溶液") + @DeleteMapping("/{ids}") + public Result delete(@Parameter(description = "溶液ID,多个以英文逗号(,)分割") @PathVariable String ids) { + boolean isSuccess = solutionsService.deleteSolutions(ids); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/StepCommandController.java b/src/main/java/com/iflytop/sgs/app/controller/StepCommandController.java new file mode 100644 index 0000000..71f5115 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/StepCommandController.java @@ -0,0 +1,72 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.model.bo.Point3D; +import com.iflytop.sgs.app.service.StepCommandService; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "单步调试") +@RestController +@RequestMapping("/api/cmd/step") +@RequiredArgsConstructor +@Slf4j +public class StepCommandController { + private final StepCommandService stepCommandService; + + @Operation(summary = "获取当前龙门架机械臂坐标") + @GetMapping("/gantry-point") + public Result getGantryPoint() throws Exception { + Point3D point3D = stepCommandService.getGantryPoint(); + return Result.success(point3D.getX() + "," + point3D.getY() + "," + point3D.getZ()); + } + + @Operation(summary = "获取指定加热区升降当前位置") + @GetMapping("/heat-lifting-position/{heatId}") + public Result getHeatLiftingPosition(@PathVariable String heatId) throws Exception { + double capPosition = stepCommandService.getHeatMotorPostion(heatId); + return Result.success(String.valueOf(capPosition)); + } + + @Operation(summary = "获取拍子存放区升降当前位置") + @GetMapping("/cap-lifting-position") + public Result getHeatPosition() throws Exception { + double capPosition = stepCommandService.getCapMotorPostion(); + return Result.success(String.valueOf(capPosition)); + } + + @Operation(summary = "使能所有电机") + @PostMapping("/enable-all") + public Result enableAll() throws Exception { + stepCommandService.enableAll(); + return Result.success(); + } + + @Operation(summary = "失能所有电机") + @PostMapping("/disability-all") + public Result disabilityAll() throws Exception { + stepCommandService.disabilityAll(); + return Result.success(); + } + + @Operation(summary = "停止所有电机") + @PostMapping("/stop-all") + public Result stopAll() throws Exception { + stepCommandService.stopAll(); + return Result.success(); + } + + @Operation(summary = "龙门架移至指定坐标") + @PostMapping("/gantry-to-point3d") + public Result gantryToTray(Point3D point3D) throws Exception { + stepCommandService.gantryToPoint3D(point3D); + return Result.success(); + } + + //移动到指定拍子上方(加热模块、拍子存放) + //移动至指定托盘上方(加热模块、加液模块) + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/SystemController.java b/src/main/java/com/iflytop/sgs/app/controller/SystemController.java new file mode 100644 index 0000000..d0c5975 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/SystemController.java @@ -0,0 +1,51 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.core.device.DeviceState; +import com.iflytop.sgs.app.model.dto.SetSystemDatetimeDTO; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.app.service.SystemConfigService; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "系统") +@RestController +@RequestMapping("/api/sys") +@RequiredArgsConstructor +@Slf4j +public class SystemController { + private final SystemConfigService systemConfigService; + private final DeviceStateService deviceStateService; + + @Operation(summary = "是否启动虚拟模式") + @PostMapping("/virtual") + public Result changeVirtualMode(Boolean mode) { + deviceStateService.setVirtual(mode); + return Result.success(); + } + + @Operation(summary = "模拟向硬件写入完毕") + @PostMapping("/initComplete") + public Result setInitComplete(Boolean initComplete) { + deviceStateService.setInitComplete(initComplete); + return Result.success(); + } + + @Operation(summary = "系统状态") + @GetMapping("/device-status") + public Result getDeviceStatus() { + return Result.success(deviceStateService.getDeviceState()); + } + + @Operation(summary = "设置系统时间") + @PostMapping("/datetime") + public Result setDatetime(@Valid @RequestBody SetSystemDatetimeDTO setSystemDatetimeDTO) { + systemConfigService.setDatetime(setSystemDatetimeDTO.getDatetime()); + return Result.success(); + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/SystemLogController.java b/src/main/java/com/iflytop/sgs/app/controller/SystemLogController.java new file mode 100644 index 0000000..f958ed5 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/SystemLogController.java @@ -0,0 +1,48 @@ +package com.iflytop.sgs.app.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.iflytop.sgs.app.model.entity.SystemLog; +import com.iflytop.sgs.app.service.SystemLogService; +import com.iflytop.sgs.common.base.BasePageQuery; +import com.iflytop.sgs.common.result.PageResult; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "系统日志") +@RestController +@RequestMapping("/api/log") +@RequiredArgsConstructor +@Slf4j +public class SystemLogController { + private final SystemLogService systemLogService; + + @Operation(summary = "日志列表") + @GetMapping("/list") + public PageResult list(BasePageQuery pageQuery) { + IPage result = systemLogService.page(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), null); + return PageResult.success(result); + } + + @Operation(summary = "日志详情") + @GetMapping("/{id}") + public Result get(@PathVariable String id) { + SystemLog systemLog = systemLogService.getById(id); + return Result.success(systemLog); + } + + @Operation(summary = "删除日志") + @DeleteMapping("/{ids}") + public Result delete(@Parameter(description = "ID,多个以英文逗号(,)分割") @PathVariable String ids) { + boolean isSuccess = systemLogService.deleteLog(ids); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/TasksController.java b/src/main/java/com/iflytop/sgs/app/controller/TasksController.java new file mode 100644 index 0000000..3c8b385 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/TasksController.java @@ -0,0 +1,83 @@ +package com.iflytop.sgs.app.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.iflytop.sgs.app.model.dto.GetAllTasksDTO; +import com.iflytop.sgs.app.model.dto.TaskDTO; +import com.iflytop.sgs.app.model.entity.Tasks; +import com.iflytop.sgs.app.model.vo.TaskStepsVO; +import com.iflytop.sgs.app.service.TasksService; +import com.iflytop.sgs.common.result.PageResult; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "实验") +@RestController +@RequestMapping("/api/tasks") +@RequiredArgsConstructor +@Slf4j +public class TasksController { + private final TasksService tasksService; + + @Operation(summary = "实验列表") + @GetMapping("/list") + public PageResult list(GetAllTasksDTO getAllTasksDTO) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper + .like(StringUtils.hasText(getAllTasksDTO.getName()), Tasks::getName, getAllTasksDTO.getName()) + .orderByDesc(Tasks::getCreateTime); + + IPage result = tasksService.page( + new Page<>(getAllTasksDTO.getPageNum(), getAllTasksDTO.getPageSize()), + queryWrapper + ); + return PageResult.success(result); + } + + @Operation(summary = "实验详情") + @GetMapping("/{id}") + public Result detail(@PathVariable Long id) { + return Result.success(tasksService.detail(id)); + } + + @Operation(summary = "获取正在进行的实验") + @GetMapping("/current") + public Result getCurrent() { + return Result.success(tasksService.getCurrent()); + } + + @Operation(summary = "开始新实验") + @PostMapping("/") + public Result start(@RequestBody TaskDTO dto) { + if (tasksService.getCurrent() != null) { + return Result.failed("存在正在运行的实验,请先终止"); + } + return Result.success(tasksService.start(dto.getName())); + } + + @Operation(summary = "更新实验") + @PutMapping("/") + public Result updateTask(@RequestBody Tasks task) { + return Result.success(tasksService.updateById(task)); + } + + @Operation(summary = "删除实验") + @DeleteMapping("/{ids}") + public Result deleteTask(@Parameter(description = "ID,多个以英文逗号(,)分割") @PathVariable String ids) { + return Result.success(tasksService.deleteTasks(ids)); + } + + @Operation(summary = "停止当前实验") + @PostMapping("/stop") + public Result stop() { + tasksService.stop(); + return Result.success(); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/TrayController.java b/src/main/java/com/iflytop/sgs/app/controller/TrayController.java new file mode 100644 index 0000000..1e29f15 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/TrayController.java @@ -0,0 +1,47 @@ +package com.iflytop.sgs.app.controller; + +import com.iflytop.sgs.app.core.device.TrayState; +import com.iflytop.sgs.app.model.vo.SetTrayTubeVO; +import com.iflytop.sgs.app.service.TrayService; +import com.iflytop.sgs.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 托盘控制 + */ +@Tag(name = "托盘控制") +@RestController +@RequestMapping("/api/tray") +@RequiredArgsConstructor +@Slf4j +public class TrayController { + private final TrayService trayService; + + @Operation(summary = "放入新托盘") + @PostMapping("/in") + public Result trayIn() { + return Result.success(trayService.trayIn()); + } + + @Operation(summary = "拿走托盘") + @PostMapping("/out") + public Result trayOut() { + trayService.trayOut(); + return Result.success(); + } + + @Operation(summary = "设置托盘试管") + @PostMapping("/tube") + public Result setTrayTube(@RequestBody SetTrayTubeVO setTrayTubeVO) { + trayService.setTrayTube(setTrayTubeVO); + return Result.success(); + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/controller/UserController.java b/src/main/java/com/iflytop/sgs/app/controller/UserController.java new file mode 100644 index 0000000..fecb321 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/controller/UserController.java @@ -0,0 +1,72 @@ +package com.iflytop.sgs.app.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.iflytop.sgs.app.model.entity.User; +import com.iflytop.sgs.app.service.UserService; +import com.iflytop.sgs.common.base.BasePageQuery; +import com.iflytop.sgs.common.enums.data.Deleted; +import com.iflytop.sgs.common.result.PageResult; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.common.result.ResultCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "用户接口") +@RestController +@RequestMapping("/api/user") +@RequiredArgsConstructor +@Slf4j +public class UserController { + private final UserService userService; + + @Operation(summary = "用户列表") + @GetMapping("/list") + public PageResult getList(BasePageQuery pageQuery) { + IPage result = userService.page(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper().eq(User::getDeleted, Deleted.DISABLE)); + result.getRecords().forEach(user -> user.setPassword(null)); + return PageResult.success(result); + } + + @Operation(summary = "添加新用户") + @PostMapping("") + public Result add(@Valid @RequestBody User user) { + User existingUser = userService.getOne(new LambdaQueryWrapper().eq(User::getUsername, user.getUsername())); + if (existingUser == null) { + user.setFixedUser(null); + boolean isSuccess = userService.save(user); + if (isSuccess) { + return Result.success(); + } + } else { + return Result.failed(ResultCode.USER_ALREADY_EXISTS); + } + return Result.failed(); + } + + @Operation(summary = "更新用户信息") + @PutMapping("") + public Result update(@Valid @RequestBody User user) { + boolean isSuccess = userService.updateById(user); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } + + @Operation(summary = "删除用户") + @DeleteMapping("/{ids}") + public Result delete(@Parameter(description = "用户ID,多个以英文逗号(,)分割") @PathVariable String ids) { + boolean isSuccess = userService.deleteUser(ids); + if (isSuccess) { + return Result.success(); + } + return Result.failed(); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/core/BaseCommandHandler.java b/src/main/java/com/iflytop/sgs/app/core/BaseCommandHandler.java new file mode 100644 index 0000000..57693ef --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/BaseCommandHandler.java @@ -0,0 +1,33 @@ +package com.iflytop.sgs.app.core; + + +import com.iflytop.sgs.common.annotation.CheckedRunnable; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.CommandHandler; +import com.iflytop.sgs.common.utils.LambdaUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +public abstract class BaseCommandHandler implements CommandHandler { + + @Autowired + @Qualifier("commandTaskExecutor") + private Executor commandExecutor; + + protected CompletableFuture runAsync(CheckedRunnable task) { + return CompletableFuture.runAsync(LambdaUtil.unchecked(task), commandExecutor); + } + + protected void commandWait(CommandFuture... futures) throws Exception { + CompletableFuture[] responseFutures = Arrays.stream(futures) + .map(CommandFuture::getResponseFuture) + .toArray(CompletableFuture[]::new); + CompletableFuture.allOf(responseFutures) + .get(120, TimeUnit.SECONDS); + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/core/CommandHandlerRegistry.java b/src/main/java/com/iflytop/sgs/app/core/CommandHandlerRegistry.java new file mode 100644 index 0000000..e36935e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/CommandHandlerRegistry.java @@ -0,0 +1,58 @@ +package com.iflytop.sgs.app.core; + +import com.iflytop.sgs.common.annotation.CommandMapping; +import com.iflytop.sgs.common.cmd.CommandHandler; +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.exception.UnSupportCommandException; +import com.iflytop.sgs.common.result.ResultCode; +import io.micrometer.common.lang.NonNull; +import jakarta.annotation.PostConstruct; +import jakarta.validation.constraints.NotNull; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class CommandHandlerRegistry implements ApplicationContextAware { + + private final Map handlerMap = new HashMap<>(); + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @PostConstruct + public void init() { + Map beans = applicationContext.getBeansWithAnnotation(CommandMapping.class); + for (Object bean : beans.values()) { + // 获取实际目标类,而不是代理类 + Class targetClass = AopUtils.getTargetClass(bean); + CommandMapping mapping = targetClass.getAnnotation(CommandMapping.class); + if (mapping != null && bean instanceof CommandHandler) { + String mappingKey = mapping.value(); + handlerMap.put(mappingKey, (CommandHandler) bean); + } + } + } + + /** + * 通过模块名称和命令名称获取命令处理器 + * + * @param commandName 命令名称 + * @return 命令处理器 + * @throws UnSupportCommandException + */ + public CommandHandler getCommandHandler(@NotNull String commandName) throws UnSupportCommandException { + if (!handlerMap.containsKey(commandName)) { + throw new AppException(ResultCode.COMMAND_NOT_FOUND); + } + return handlerMap.get(commandName); + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/core/CommandPoolManager.java b/src/main/java/com/iflytop/sgs/app/core/CommandPoolManager.java new file mode 100644 index 0000000..11ef8e2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/CommandPoolManager.java @@ -0,0 +1,31 @@ +package com.iflytop.sgs.app.core; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; + +import java.util.concurrent.ThreadPoolExecutor; + +@Slf4j +@Component +public class CommandPoolManager { + private final ThreadPoolTaskExecutor executor; + + public CommandPoolManager(@Qualifier("commandTaskExecutor") ThreadPoolTaskExecutor executor) { + this.executor = executor; + } + + /** + * 强制中断所有正在执行的任务 + */ + public void forceShutdownAll() { + try { + ThreadPoolExecutor nativeExecutor = executor.getThreadPoolExecutor(); + nativeExecutor.shutdownNow(); + } catch (Exception e) { + log.error("强制关闭所有cmd线程失败"); + } + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/core/CraftsContext.java b/src/main/java/com/iflytop/sgs/app/core/CraftsContext.java new file mode 100644 index 0000000..1830c4d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/CraftsContext.java @@ -0,0 +1,151 @@ +package com.iflytop.sgs.app.core; + +import cn.hutool.json.JSONUtil; +import com.iflytop.sgs.app.model.bo.CraftsStep; +import com.iflytop.sgs.app.model.entity.Crafts; +import com.iflytop.sgs.app.model.entity.Ores; +import com.iflytop.sgs.app.service.CraftsStepService; +import com.iflytop.sgs.app.service.WebSocketService; +import com.iflytop.sgs.common.constant.WebSocketMessageType; +import com.iflytop.sgs.common.enums.automaton.CraftEvents; +import com.iflytop.sgs.common.enums.automaton.CraftStates; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Getter +public class CraftsContext implements Runnable { + private final String heatId; + private final Ores ores; + private final Crafts craft; + private final List craftsStepList; + private final StateMachine sm; + private final WebSocketService webSocketService; + private final CraftsStepService craftsStepService; + private int currentIndex = 0; + + /** + * 构造方法,初始化上下文并启动状态机至 READY + */ + public CraftsContext(String heatId, Ores ores, + Crafts craft, + StateMachineFactory factory, + WebSocketService webSocketService, + CraftsStepService craftsStepService) { + this.heatId = heatId; + this.ores = ores; + this.craft = craft; + this.craftsStepList = JSONUtil.parseArray(craft.getSteps()).toList(CraftsStep.class); + this.webSocketService = webSocketService; + this.craftsStepService = craftsStepService; + + this.sm = factory.getStateMachine(heatId); + sm.addStateListener(new StateMachineListenerAdapter<>() { + @Override + public void stateEntered(State state) { + Map dataMap = new HashMap<>(); + dataMap.put("heatId", heatId); + dataMap.put("state", state.getId()); + dataMap.put("index", currentIndex); + webSocketService.push(WebSocketMessageType.CRAFTS_STATE, dataMap); + } + }); + Mono.from(sm.startReactively()).block(); + } + + /** + * 执行工艺脚本,遍历所有步骤并根据状态机控制流程 + */ + @Override + public void run() { + try { + Message startMsg = MessageBuilder.withPayload(CraftEvents.START).build(); + Mono.from(sm.sendEvent(Mono.just(startMsg))).block(); + + for (; currentIndex < craftsStepList.size(); currentIndex++) { + if (sm.getState().getId() == CraftStates.STOPPED) break; + CraftsStep step = craftsStepList.get(currentIndex); + boolean ok = executeStep(step); + + if (!ok) { + Message errMsg = MessageBuilder.withPayload(CraftEvents.ERROR_OCCUR).build(); + Mono.from(sm.sendEvent(Mono.just(errMsg))).block(); + break; + } + Message compMsg = MessageBuilder.withPayload(CraftEvents.STEP_COMPLETE).build(); + Mono.from(sm.sendEvent(Mono.just(compMsg))).block(); + + synchronized (this) { + while (sm.getState().getId() == CraftStates.PAUSED) { + this.wait(); + } + } + } + if (sm.getState().getId() == CraftStates.RUNNING) { + Message finishMsg = MessageBuilder.withPayload(CraftEvents.FINISH).build(); + Mono.from(sm.sendEvent(Mono.just(finishMsg))).block(); + } + } catch (Exception e) { + log.error("工艺执行失败", e); + webSocketService.pushCraftsDebug(CraftsDebugGenerator.generateJson(heatId, "工艺执行失败", e.getMessage())); + Message stopMsg = MessageBuilder.withPayload(CraftEvents.ERROR_OCCUR).build(); + Mono.from(sm.sendEvent(Mono.just(stopMsg))).block(); + } + } + + /** + * 执行单个步骤,并推送开始及结果 + * + * @param step 当前工艺步骤 + * @return 是否执行成功 + * @throws InterruptedException 执行中被中断 + */ + private boolean executeStep(CraftsStep step) throws Exception { + Map startData = new HashMap<>(); + startData.put("heatId", heatId); + startData.put("currentStep", step.getMethod()); + webSocketService.push(WebSocketMessageType.CRAFTS_STEP, startData); + return craftsStepService.executeStep(heatId, step); + } + + /** + * 暂停执行,触发 PAUSE 事件 + */ + public void pause() { + Message msg = MessageBuilder.withPayload(CraftEvents.PAUSE).build(); + Mono.from(sm.sendEvent(Mono.just(msg))).block(); + } + + /** + * 恢复执行,触发 RESUME 事件并唤醒线程 + */ + public void resume() { + Message msg = MessageBuilder.withPayload(CraftEvents.RESUME).build(); + Mono.from(sm.sendEvent(Mono.just(msg))).block(); + synchronized (this) { + this.notify(); + } + } + + /** + * 停止执行,触发 STOP 事件并唤醒线程 + */ + public void stop() { + Message msg = MessageBuilder.withPayload(CraftEvents.STOP).build(); + Mono.from(sm.sendEvent(Mono.just(msg))).block(); + synchronized (this) { + this.notify(); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/app/core/CraftsDebugGenerator.java b/src/main/java/com/iflytop/sgs/app/core/CraftsDebugGenerator.java new file mode 100644 index 0000000..985c6bb --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/CraftsDebugGenerator.java @@ -0,0 +1,15 @@ +package com.iflytop.sgs.app.core; + +import cn.hutool.json.JSONObject; + +public class CraftsDebugGenerator { + + public static JSONObject generateJson(String heatId, String title, Object content) { + JSONObject jsonObject = new JSONObject(); + jsonObject.set("heatId", heatId); + jsonObject.set("title", title); + jsonObject.set("content", content); + return jsonObject; + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/core/DebugGenerator.java b/src/main/java/com/iflytop/sgs/app/core/DebugGenerator.java new file mode 100644 index 0000000..9e29266 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/DebugGenerator.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.app.core; + +import cn.hutool.json.JSONObject; + +public class DebugGenerator { + + public static JSONObject generateJson(String commandId, String command, String status, String title, Object content) { + JSONObject jsonObject = new JSONObject(); + jsonObject.set("commandId", commandId); + jsonObject.set("command", command); + jsonObject.set("status", status); + jsonObject.set("title", title); + jsonObject.set("content", content); + return jsonObject; + } + + public static JSONObject generateJson(String commandId, String command, String status, String title) { + return generateJson(commandId, command, status, title, null); + } + + +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/CommandState.java b/src/main/java/com/iflytop/sgs/app/core/device/CommandState.java new file mode 100644 index 0000000..6454561 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/CommandState.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.core.device; + +import lombok.Data; + +/** + * 指令互斥关系状态 + */ +@Data +public class CommandState { + private boolean capFreeCommandExecuting = false; + private boolean capLiftingOriginCommandExecuting = false; + private boolean capUsedCommandExecuting = false; + private boolean doorCloseCommandExecuting = false; + private boolean doorOpenCommandExecuting = false; + private boolean doorOriginCommandExecuting = false; + private boolean dualRobotJointOriginCommandExecuting = false; + private boolean fanStartCommandExecuting = false; + private boolean fanStopCommandExecuting = false; + private boolean filledSolutionStartCommandExecuting = false; + private boolean filledSolutionStopCommandExecuting = false; + private boolean gantryXOriginCommandExecuting = false; + private boolean gantryYOriginCommandExecuting = false; + private boolean gantryZOriginCommandExecuting = false; + private boolean heatStartCommandExecuting = false; + private boolean heatStopCommandExecuting = false; + private boolean moveToHeatAreaCommandExecuting = false; + private boolean moveToSolutionAreaCommandExecuting = false; + private boolean shakeStartCommandExecuting = false; + private boolean shakeStopCommandExecuting = false; + private boolean solutionAddCommandExecuting = false; + private boolean takePhotoCommandExecuting = false; + private boolean trayDownCommandExecuting = false; + private boolean trayLiftingOriginCommandExecuting = false; + private boolean trayUpCommandExecuting = false; +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/CraftsState.java b/src/main/java/com/iflytop/sgs/app/core/device/CraftsState.java new file mode 100644 index 0000000..a007e79 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/CraftsState.java @@ -0,0 +1,23 @@ +package com.iflytop.sgs.app.core.device; + +import com.iflytop.sgs.app.model.entity.Crafts; +import com.iflytop.sgs.app.model.entity.Ores; +import com.iflytop.sgs.common.enums.automaton.CraftStates; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "工艺") +@Data +public class CraftsState { + @Schema(description = "工艺状态") + private CraftStates state; + + @Schema(description = "工艺所属矿石") + private Ores ores; + + @Schema(description = "工艺") + private Crafts craft; + + @Schema(description = "工艺当前步骤下标") + private int currentIndex = 0; +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/DeviceState.java b/src/main/java/com/iflytop/sgs/app/core/device/DeviceState.java new file mode 100644 index 0000000..58eb13d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/DeviceState.java @@ -0,0 +1,36 @@ +package com.iflytop.sgs.app.core.device; + +import com.iflytop.sgs.app.model.entity.Tasks; +import com.iflytop.sgs.app.model.entity.User; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Schema(description = "当前设备") +@Data +public class DeviceState { + @Schema(description = "门状态") + private final DoorState door = new DoorState(); + @Schema(description = "龙门架机械臂状态") + private final GantryArmState gantryArm = new GantryArmState(); + @Schema(description = "加液模块属性") + private final SolutionModuleState solutionModule = new SolutionModuleState(); + @Schema(description = "加热模块属性") + private final List heatModule = new ArrayList<>(); + @Schema(description = "托盘") + private final List tray = new ArrayList<>(); + @Schema(description = "虚拟模式,true为虚拟") + private boolean virtual = false; + @Schema(description = "初始化状态,true初始化完毕") + private boolean initComplete = false; + @Schema(description = "自检状态,true自检完毕") + private boolean selfTest = false; + @Schema(description = "是否是急停状态,true为急停") + private boolean emergencyStop = false; + @Schema(description = "当前登录用户") + private User currentUser; + @Schema(description = "当前实验") + private Tasks currentTasks; +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/DoorState.java b/src/main/java/com/iflytop/sgs/app/core/device/DoorState.java new file mode 100644 index 0000000..3299f73 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/DoorState.java @@ -0,0 +1,11 @@ +package com.iflytop.sgs.app.core.device; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "门") +@Data +public class DoorState { + @Schema(description = "是否开门,true为开启状态,false为关闭状态") + private boolean open = false; +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/GantryArmState.java b/src/main/java/com/iflytop/sgs/app/core/device/GantryArmState.java new file mode 100644 index 0000000..a26450e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/GantryArmState.java @@ -0,0 +1,11 @@ +package com.iflytop.sgs.app.core.device; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "龙门架机械臂") +@Data +public class GantryArmState { + @Schema(description = "是否空闲,true为空闲,false为占用") + private boolean idle = true; +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/HeatModuleState.java b/src/main/java/com/iflytop/sgs/app/core/device/HeatModuleState.java new file mode 100644 index 0000000..8056e12 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/HeatModuleState.java @@ -0,0 +1,47 @@ +package com.iflytop.sgs.app.core.device; + +import com.iflytop.sgs.common.enums.HeatModuleCode; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "加热模块") +@Data +public class HeatModuleState { + @Schema(description = "加热模块code") + private HeatModuleCode moduleCode; + + @Schema(description = "托盘升降状态,0为降下,1为抬起") + private int trayUp = 0; + + @Schema(description = "托盘状态,0为无托盘,1为有托盘") + private int trayStatus = 0; + + @Schema(description = "是否正在加热,true为正在加热,false为未加热") + private boolean heating = false; + + @Schema(description = "是否正在烘干,true为正在烘干,false为未烘干") + private boolean drying = false; + + @Schema(description = "是否正在退火,true为正在退火,false为未退火") + private boolean annealing = false; + + @Schema(description = "是否启动散热,true为正在散热,false为未在散热") + private boolean fanOpen = false; + + @Schema(description = "加热器目标温度") + private Double targetTemperature = null; + + @Schema(description = "加热器烘干温度") + private Double dryTemperature; + @Schema(description = "加热器退火温度") + private Double annealTemperature; + @Schema(description = "加热器加热温度") + private Double heatTemperature; + + @Schema(description = "加热器当前温度") + private Double temperature = null; + + public HeatModuleState(HeatModuleCode moduleCode) { + this.moduleCode = moduleCode; + } +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/SelfTestState.java b/src/main/java/com/iflytop/sgs/app/core/device/SelfTestState.java new file mode 100644 index 0000000..bba2021 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/SelfTestState.java @@ -0,0 +1,49 @@ +package com.iflytop.sgs.app.core.device; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "自检状态") +@Data +public class SelfTestState { + @Schema(description = "门是否在原点") + private boolean doorOrigin = false; + + @Schema(description = "摇匀是否在原点") + private boolean shakeOrigin = false; + + @Schema(description = "龙门架机械臂x轴是否在原点") + private boolean gantryXOrigin = false; + + @Schema(description = "龙门架机械臂y轴是否在原点") + private boolean gantryYOrigin = false; + + @Schema(description = "龙门架机械臂z轴是否在原点") + private boolean gantryZOrigin = false; + + @Schema(description = "加液机械臂是否在原点") + private boolean dualRobotOrigin = false; + + @Schema(description = "拍子升降是否在原点") + private boolean capLiftingOrigin = false; + + @Schema(description = "加热模块01托盘升降是否在原点") + private boolean trayLifting01Origin = false; + + @Schema(description = "加热模块02托盘升降是否在原点") + private boolean trayLifting02Origin = false; + + @Schema(description = "加热模块03托盘升降是否在原点") + private boolean trayLifting03Origin = false; + + @Schema(description = "加热模块04托盘升降是否在原点") + private boolean trayLifting04Origin = false; + + @Schema(description = "加热模块05托盘升降是否在原点") + private boolean trayLifting05Origin = false; + + @Schema(description = "加热模块06托盘升降是否在原点") + private boolean trayLifting06Origin = false; + + +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/SolutionContainerState.java b/src/main/java/com/iflytop/sgs/app/core/device/SolutionContainerState.java new file mode 100644 index 0000000..77c1eac --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/SolutionContainerState.java @@ -0,0 +1,34 @@ +package com.iflytop.sgs.app.core.device; + +import com.iflytop.sgs.common.enums.ContainerCode; +import com.iflytop.sgs.common.enums.ContainerType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "溶液容器") +@Data +public class SolutionContainerState { + @Schema(description = "容器数据id") + private Long id; + + @Schema(description = "容器code") + private ContainerCode containerCode; + + @Schema(description = "容器类型 solution溶液容器 neutralization中和容器") + private ContainerType type; + + @Schema(description = "容器是否为空,true为空,false不为空") + private boolean empty = false; + + @Schema(description = "容器是否为满,true为满,false不满") + private boolean full = false; + + @Schema(description = "是否完成预充,true为预充,false为排空") + private boolean filledSolution = false; + + public SolutionContainerState(Long id, ContainerCode containerCode, ContainerType type) { + this.id = id; + this.containerCode = containerCode; + this.type = type; + } +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/SolutionModuleState.java b/src/main/java/com/iflytop/sgs/app/core/device/SolutionModuleState.java new file mode 100644 index 0000000..9938bf3 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/SolutionModuleState.java @@ -0,0 +1,23 @@ +package com.iflytop.sgs.app.core.device; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Schema(description = "加液模块") +@Data +public class SolutionModuleState { + @Schema(description = "是否空闲,true为空闲,false为占用") + private boolean idle = true; + + @Schema(description = "托盘状态,0为无托盘,1为有托盘") + private int trayStatus = 0; + + @Schema(description = "溶液容器状态") + private List solutionContainer = new ArrayList<>(); + + @Schema(description = "是否正在加液,true正在加液,false未运行") + private boolean pumping = false; +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/TrayState.java b/src/main/java/com/iflytop/sgs/app/core/device/TrayState.java new file mode 100644 index 0000000..b879a8f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/TrayState.java @@ -0,0 +1,37 @@ +package com.iflytop.sgs.app.core.device; + +import com.iflytop.sgs.common.enums.HeatModuleCode; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.UUID; + +@Schema(description = "托盘") +@Data +public class TrayState { + @Schema(description = "托盘唯一id") + private String uuid = UUID.randomUUID().toString(); + + @Schema(description = "所属加热模块id") + private HeatModuleCode heatModuleId; + + @Schema(description = "是否在加液模块中") + private boolean inSolutionModule = false; + + @Schema(description = "是否在加热模块中") + private boolean inHeatModule = false; + + @Schema(description = "试管") + private TubeState[] tubes = new TubeState[40]; + + @Schema(description = "当前托盘的工艺") + private CraftsState crafts = null; + + public TrayState() { + for (int i = 0; i < tubes.length; i++) { + TubeState tubeState = new TubeState(); + tubeState.setTubeNum(i + 1); + tubes[i] = tubeState; + } + } +} diff --git a/src/main/java/com/iflytop/sgs/app/core/device/TubeState.java b/src/main/java/com/iflytop/sgs/app/core/device/TubeState.java new file mode 100644 index 0000000..d540570 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/device/TubeState.java @@ -0,0 +1,17 @@ +package com.iflytop.sgs.app.core.device; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "试管位") +@Data +public class TubeState { + @Schema(description = "试管编号") + private int tubeNum; + + @Schema(description = "是否添加过溶液 true添加过") + private boolean addSolution = false; + + @Schema(description = "是否存在试管 true存在") + private boolean exists = true; +} diff --git a/src/main/java/com/iflytop/sgs/app/core/event/CommandFeedbackEvent.java b/src/main/java/com/iflytop/sgs/app/core/event/CommandFeedbackEvent.java new file mode 100644 index 0000000..09f74cd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/event/CommandFeedbackEvent.java @@ -0,0 +1,16 @@ +package com.iflytop.sgs.app.core.event; + +import cn.hutool.json.JSONObject; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class CommandFeedbackEvent extends ApplicationEvent { + private final JSONObject jsonResponse; + + public CommandFeedbackEvent(Object source, JSONObject jsonResponse) { + super(source); + this.jsonResponse = jsonResponse; + } + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/core/listener/CommandFeedbackListener.java b/src/main/java/com/iflytop/sgs/app/core/listener/CommandFeedbackListener.java new file mode 100644 index 0000000..fde664c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/listener/CommandFeedbackListener.java @@ -0,0 +1,18 @@ +package com.iflytop.sgs.app.core.listener; + +import com.iflytop.sgs.app.core.event.CommandFeedbackEvent; +import com.iflytop.sgs.app.service.DeviceCommandService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CommandFeedbackListener { + private final DeviceCommandService deviceCommandService; + + @EventListener + public void handleCommandFeedbackEvent(CommandFeedbackEvent event) { + deviceCommandService.completeCommandResponse(event.getJsonResponse()); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/core/listener/DeviceStateListener.java b/src/main/java/com/iflytop/sgs/app/core/listener/DeviceStateListener.java new file mode 100644 index 0000000..fd5f22a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/core/listener/DeviceStateListener.java @@ -0,0 +1,33 @@ +package com.iflytop.sgs.app.core.listener; + +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.app.service.WebSocketService; +import com.iflytop.sgs.common.constant.WebSocketMessageType; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DeviceStateListener implements PropertyChangeListener { + private final WebSocketService webSocketService; + private final DeviceStateService deviceStateService; + + @PostConstruct + private void init() { + deviceStateService.addListener(this); + } + + // 在此处理DeviceState的变化事件 + @Override + public void propertyChange(PropertyChangeEvent event) { + log.info("设备状态发生改变,类型{} ,from {} to {}", event.getPropertyName(), event.getOldValue(), event.getNewValue()); + webSocketService.push(WebSocketMessageType.STATUS, deviceStateService.getDeviceState()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/mapper/ContainerMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/ContainerMapper.java new file mode 100644 index 0000000..76262ac --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/ContainerMapper.java @@ -0,0 +1,15 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.Container; +import org.apache.ibatis.annotations.Mapper; + + +/** + * 容器持久层接口 + */ +@Mapper +public interface ContainerMapper extends BaseMapper { + +} + diff --git a/src/main/java/com/iflytop/sgs/app/mapper/CraftsMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/CraftsMapper.java new file mode 100644 index 0000000..894a9d1 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/CraftsMapper.java @@ -0,0 +1,19 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.Crafts; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 工艺持久层接口 + */ +@Mapper +public interface CraftsMapper extends BaseMapper { + + @Select("SELECT * FROM crafts WHERE ores_id = #{oresId}") + List selectAllByOresId(Long oresId); + +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/DeviceParamConfigMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/DeviceParamConfigMapper.java new file mode 100644 index 0000000..0e58ff7 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/DeviceParamConfigMapper.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.DeviceParamConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + * 设备参数配置持久层接口 + */ +@Mapper +public interface DeviceParamConfigMapper extends BaseMapper { +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/DevicePositionMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/DevicePositionMapper.java new file mode 100644 index 0000000..416a265 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/DevicePositionMapper.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.DevicePosition; +import org.apache.ibatis.annotations.Mapper; + +/** + * 设备位置持久层接口 + */ +@Mapper +public interface DevicePositionMapper extends BaseMapper { +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/OresMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/OresMapper.java new file mode 100644 index 0000000..c538853 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/OresMapper.java @@ -0,0 +1,15 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.Ores; +import org.apache.ibatis.annotations.Mapper; + +/** + * 矿石持久层接口 + */ +@Mapper +public interface OresMapper extends BaseMapper { + + Ores findByName(String name); + +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/SolutionsMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/SolutionsMapper.java new file mode 100644 index 0000000..d16b3a3 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/SolutionsMapper.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.Solutions; +import org.apache.ibatis.annotations.Mapper; + +/** + * 溶液持久层接口 + */ +@Mapper +public interface SolutionsMapper extends BaseMapper { + +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/SystemConfigMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/SystemConfigMapper.java new file mode 100644 index 0000000..a890e7c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/SystemConfigMapper.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.SystemConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + * 系统配置持久层接口 + */ +@Mapper +public interface SystemConfigMapper extends BaseMapper { + +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/SystemLogMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/SystemLogMapper.java new file mode 100644 index 0000000..d15c8c5 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/SystemLogMapper.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.SystemLog; +import org.apache.ibatis.annotations.Mapper; + +/** + * 系统日志持久层接口 + */ +@Mapper +public interface SystemLogMapper extends BaseMapper { + +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/TaskStepsMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/TaskStepsMapper.java new file mode 100644 index 0000000..15d5ab2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/TaskStepsMapper.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.TaskSteps; +import org.apache.ibatis.annotations.Mapper; + +/** + * 实验持久层接口 + */ +@Mapper +public interface TaskStepsMapper extends BaseMapper { +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/TasksMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/TasksMapper.java new file mode 100644 index 0000000..c24d2b2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/TasksMapper.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.Tasks; +import org.apache.ibatis.annotations.Mapper; + +/** + * 实验持久层接口 + */ +@Mapper +public interface TasksMapper extends BaseMapper { + +} diff --git a/src/main/java/com/iflytop/sgs/app/mapper/UserMapper.java b/src/main/java/com/iflytop/sgs/app/mapper/UserMapper.java new file mode 100644 index 0000000..9b5c944 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/mapper/UserMapper.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.sgs.app.model.entity.User; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户持久层接口 + */ +@Mapper +public interface UserMapper extends BaseMapper { + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/bo/CraftsStep.java b/src/main/java/com/iflytop/sgs/app/model/bo/CraftsStep.java new file mode 100644 index 0000000..d34d5d3 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/bo/CraftsStep.java @@ -0,0 +1,10 @@ +package com.iflytop.sgs.app.model.bo; + +import cn.hutool.json.JSONObject; +import lombok.Data; + +@Data +public class CraftsStep { + private String method; + private JSONObject params; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/bo/DeviceInitializationData.java b/src/main/java/com/iflytop/sgs/app/model/bo/DeviceInitializationData.java new file mode 100644 index 0000000..4162ec4 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/bo/DeviceInitializationData.java @@ -0,0 +1,11 @@ +package com.iflytop.sgs.app.model.bo; + +import lombok.Data; + +@Data +public class DeviceInitializationData { + private int id; + private String mid; + private String regIndex; + private int regInitVal; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/bo/Notification.java b/src/main/java/com/iflytop/sgs/app/model/bo/Notification.java new file mode 100644 index 0000000..5f3f897 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/bo/Notification.java @@ -0,0 +1,102 @@ +package com.iflytop.sgs.app.model.bo; + +import cn.hutool.core.date.DateTime; +import com.iflytop.sgs.app.model.dto.CmdDTO; +import lombok.Getter; + + +/** + * 向前台推送的消息类 + */ + +@Getter +public class Notification { + private final String commandId; + private final String command; + private final String level; + private final String title; + private final String dateTime = DateTime.now().toString("yyyy/MM/dd HH:mm:ss"); + private String content = null; + + private Notification(String commandId, String command, String level, String title, String content) { + this.commandId = commandId; + this.level = level; + this.title = title; + this.content = content; + this.command = command; + } + + private Notification(String commandId, String command, String level, String title) { + this.commandId = commandId; + this.level = level; + this.title = title; + this.command = command; + } + + /** + * 创建Info级别通知 + * + * @param content 通知内容 + * @return 通知实例 + */ + public static Notification infoNotification(String commandId, String command, String title, String content) { + return new Notification(commandId, command, "info", title, content); + } + + public static Notification infoNotification(String commandId, String command, String title) { + return new Notification(commandId, command, "info", title, ""); + } + + public static Notification infoNotification(CmdDTO cmdDTO, String title) { + return new Notification(cmdDTO.getCommandId(), cmdDTO.getCommand(), "info", title, ""); + } + + public static Notification infoHandleStartNotification(CmdDTO cmdDTO) { + String title = String.format("开始执行{}指令", cmdDTO.getCommand()); + return new Notification(cmdDTO.getCommandId(), cmdDTO.getCommand(), "info", title, ""); + } + + public static Notification infoHandleEndNotification(CmdDTO cmdDTO) { + String title = String.format("{}指令执行完成", cmdDTO.getCommand()); + return new Notification(cmdDTO.getCommandId(), cmdDTO.getCommand(), "info", title, ""); + } + + + /** + * 创建Warn级别通知 + * + * @param content 通知内容 + * @return 通知实例 + */ + public static Notification warnNotification(String commandId, String command, String title, String content) { + return new Notification(commandId, command, "warn", title, content); + } + + /** + * 创建Error级别通知 + * + * @param content 通知内容 + * @return 通知实例 + */ + public static Notification errorNotification(String commandId, String command, String title, String content) { + return new Notification(commandId, command, "error", title, content); + } + + public static Notification errorNotification(CmdDTO cmdDTO, Exception e) { + String title = String.format("执行{}出错", cmdDTO.getCommand()); + return new Notification(cmdDTO.getCommandId(), cmdDTO.getCommand(), "error", title, e.getMessage()); + } + + + /** + * 创建Fatal级别通知 + * + * @param content 通知内容 + * @return 通知实例 + */ + public static Notification fatalNotification(String commandId, String command, String title, String content) { + return new Notification(commandId, command, "fatal", title, content); + } + + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/bo/Point2D.java b/src/main/java/com/iflytop/sgs/app/model/bo/Point2D.java new file mode 100644 index 0000000..9e66c2d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/bo/Point2D.java @@ -0,0 +1,20 @@ +package com.iflytop.sgs.app.model.bo; + + +import lombok.Data; + +@Data +public class Point2D { + private Double x; + private Double y; + + public Point2D() { + } + + public Point2D(Double x, Double y) { + this.x = x; + this.y = y; + } + + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/bo/Point3D.java b/src/main/java/com/iflytop/sgs/app/model/bo/Point3D.java new file mode 100644 index 0000000..b808316 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/bo/Point3D.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.app.model.bo; + + +import lombok.Data; + +@Data +public class Point3D { + private Double x; + private Double y; + private Double z; + + public Point3D() { + } + + public Point3D(Double x, Double y, Double z) { + this.x = x; + this.y = y; + this.z = z; + } + + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/bo/TubeSol.java b/src/main/java/com/iflytop/sgs/app/model/bo/TubeSol.java new file mode 100644 index 0000000..db5f314 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/bo/TubeSol.java @@ -0,0 +1,23 @@ +package com.iflytop.sgs.app.model.bo; + +import lombok.Data; + +/** + * 试管添加溶液 + */ +@Data +public class TubeSol { + /** + * 需要添加溶液的试管编号 + */ + private Integer tubeNum; + /** + * 溶液id + */ + private Long solId; + /** + * 加液量 + */ + private Integer volume; + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/AddDevicePositionDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/AddDevicePositionDTO.java new file mode 100644 index 0000000..40523f3 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/AddDevicePositionDTO.java @@ -0,0 +1,20 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + + +@Data +public class AddDevicePositionDTO { + + private Long id; + + @NotNull + @NotBlank + @Schema(description = "位置数据") + private String position; + + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/CmdDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/CmdDTO.java new file mode 100644 index 0000000..fde932d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/CmdDTO.java @@ -0,0 +1,67 @@ +package com.iflytop.sgs.app.model.dto; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Map; + +@Schema(description = "指令") +@Data +public class CmdDTO { + + @NotNull + @Schema(description = "指令id,前端生成唯一ID") + private String commandId; + + @NotNull + @Schema(description = "命令名称") + private String command; + + @Schema(description = "参数") + private Map params; + + // 获取 Double 类型的参数,null 或空字符串时返回 null + public Double getDoubleParam(String key) { + String value = getStringParam(key); + return (value != null && !value.isEmpty()) ? Double.parseDouble(value) : null; + } + + // 获取 String 类型的参数,null 或空字符串时返回 null + public String getStringParam(String key) { + Object value = params.get(key); + // 确保值不是 null 或空字符串,转换成 String 后返回 + return (value != null && !value.toString().isEmpty()) ? value.toString() : null; + } + + // 获取 Integer 类型的参数,null 或空字符串时返回 null + public Integer getIntegerParam(String key) { + String value = getStringParam(key); + return (value != null && !value.isEmpty()) ? Integer.parseInt(value) : null; + } + + // 获取 Boolean 类型的参数,null 或空字符串时返回 null + public Boolean getBooleanParam(String key) { + String value = getStringParam(key); + return (value != null && !value.isEmpty()) ? Boolean.parseBoolean(value) : null; + } + + public JSONObject getJsonObjectParam(String key) { + Object value = params.get(key); + return new JSONObject(value); + + } + + public JSONArray getJSONArrayParam(String key) { + Object value = params.get(key); + return new JSONArray(value); + } + + @Override + public String toString() { + return JSONUtil.toJsonStr(this); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/GetAllTasksDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/GetAllTasksDTO.java new file mode 100644 index 0000000..d98b410 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/GetAllTasksDTO.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.model.dto; + +import com.iflytop.sgs.common.base.BasePageQuery; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class GetAllTasksDTO extends BasePageQuery { + @Schema(description = "实验名称") + private String name; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/LoginDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/LoginDTO.java new file mode 100644 index 0000000..6855f7a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/LoginDTO.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 用户登录表单 + */ +@Schema(description = "用户登录") +@Data +public class LoginDTO { + + @NotNull + @Schema(description = "用户名", example = "admin") + private String username; + + @NotNull + @Schema(description = "用户密码", example = "12345") + private String password; + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/PauseCraftsDto.java b/src/main/java/com/iflytop/sgs/app/model/dto/PauseCraftsDto.java new file mode 100644 index 0000000..ccd0d37 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/PauseCraftsDto.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "暂停执行工艺") +@Data +public class PauseCraftsDto { + @NotNull + @Schema(description = "加热区id") + private String heatId; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/ResumeCraftsDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/ResumeCraftsDTO.java new file mode 100644 index 0000000..b8c57f3 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/ResumeCraftsDTO.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "恢复执行工艺") +@Data +public class ResumeCraftsDTO { + @NotNull + @Schema(description = "加热区id") + private String heatId; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/SetCraftsDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/SetCraftsDTO.java new file mode 100644 index 0000000..0fc5f5d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/SetCraftsDTO.java @@ -0,0 +1,19 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +@Schema(description = "配置工艺") +@Data +public class SetCraftsDTO { + @Positive(message = "工艺ID 必须是正数") + @NotNull + @Schema(description = "工艺id") + private Long craftId; + + @NotNull + @Schema(description = "加热区id") + private String heatId; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/SetSystemDatetimeDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/SetSystemDatetimeDTO.java new file mode 100644 index 0000000..cdf3024 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/SetSystemDatetimeDTO.java @@ -0,0 +1,15 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "设置系统时间") +@Data +public class SetSystemDatetimeDTO { + @NotNull + @NotBlank + @Schema(description = "系统时间") + private String datetime; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/StartCraftsDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/StartCraftsDTO.java new file mode 100644 index 0000000..e400677 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/StartCraftsDTO.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "开始工艺") +@Data +public class StartCraftsDTO { + @NotNull + @Schema(description = "加热区id") + private String heatId; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/StopCraftsDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/StopCraftsDTO.java new file mode 100644 index 0000000..46de21c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/StopCraftsDTO.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "恢复执行工艺") +@Data +public class StopCraftsDTO { + @NotNull + @Schema(description = "加热区id") + private String heatId; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/StopTaskDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/StopTaskDTO.java new file mode 100644 index 0000000..24be7c7 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/StopTaskDTO.java @@ -0,0 +1,10 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class StopTaskDTO { + @Schema(description = "实验id") + private Long taskId; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/TaskDTO.java b/src/main/java/com/iflytop/sgs/app/model/dto/TaskDTO.java new file mode 100644 index 0000000..d611e87 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/TaskDTO.java @@ -0,0 +1,10 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class TaskDTO { + @Schema(description = "实验名称") + private String name; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/dto/WebsocketResult.java b/src/main/java/com/iflytop/sgs/app/model/dto/WebsocketResult.java new file mode 100644 index 0000000..a8f61dd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/dto/WebsocketResult.java @@ -0,0 +1,18 @@ +package com.iflytop.sgs.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class WebsocketResult { + /** + * 推送类型(指令 cmd,报警 warn ,状态 status) + */ + @Schema(description = "推送类型(指令 cmd,报警 warn ,状态 status)") + private String type; + /** + * 执行结果 + */ + @Schema(description = "推送数据") + private Object data; +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/Container.java b/src/main/java/com/iflytop/sgs/app/model/entity/Container.java new file mode 100644 index 0000000..a5c84e6 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/Container.java @@ -0,0 +1,47 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = false) +@Schema(description = "容器表") +@TableName("container") +@Data +public class Container extends BaseEntity { + + @Min(value = 0, message = "类型只能为 0 或 1") + @Max(value = 1, message = "类型只能为 0 或 1") + @NotNull + @Schema(description = "类型 0 溶液 1 废液") + private Long type; + + @Schema(description = "容器代码") + private String code; + + @Positive(message = "溶液id 必须是正数") + @NotNull + @Schema(description = "溶液id") + private Long solutionId; + + @NotNull + @Schema(description = "泵id") + private String pumpId; + + @Positive(message = "总容量 必须是正数") + @NotNull + @Schema(description = "总容量") + private Integer capacityTotal; + + @Positive(message = "当前容量 必须是正数") + @NotNull + @Schema(description = "当前容量") + private Integer capacityUsed; + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/Crafts.java b/src/main/java/com/iflytop/sgs/app/model/entity/Crafts.java new file mode 100644 index 0000000..5ad04c2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/Crafts.java @@ -0,0 +1,32 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "工艺") +@TableName("crafts") +@Data +public class Crafts extends BaseEntity { + + @NotNull + @NotBlank + @Schema(description = "工艺名称") + private String name; + + @NotNull + @NotBlank + @Schema(description = "工艺步骤") + private String steps; + + @Positive(message = "矿石ID 必须是正数") + @NotNull + @Schema(description = "矿石ID") + private Long oresId; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/DeviceParamConfig.java b/src/main/java/com/iflytop/sgs/app/model/entity/DeviceParamConfig.java new file mode 100644 index 0000000..1b5ccca --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/DeviceParamConfig.java @@ -0,0 +1,30 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "设备参数配置") +@TableName("device_param_config") +@Data +public class DeviceParamConfig extends BaseEntity { + + @NotNull + @NotBlank + @Schema(description = "模块标识") + private String mid; + + @NotNull + @NotBlank + @Schema(description = "寄存器索引") + private String regIndex; + + @Schema(description = "寄存器值") + private Integer regVal; + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/DevicePosition.java b/src/main/java/com/iflytop/sgs/app/model/entity/DevicePosition.java new file mode 100644 index 0000000..2efcd4b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/DevicePosition.java @@ -0,0 +1,57 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.app.model.bo.Point2D; +import com.iflytop.sgs.app.model.bo.Point3D; +import com.iflytop.sgs.common.base.BaseEntity; +import com.iflytop.sgs.common.enums.data.DevicePositionCode; +import com.iflytop.sgs.common.enums.data.DevicePositionType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Arrays; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "设备位置记录") +@TableName("device_position") +@Data +public class DevicePosition extends BaseEntity { + + @Schema(description = "位置名称") + private String name; + + @Schema(description = "位置代码") + private DevicePositionCode code; + + @Schema(description = "位置类型") + private DevicePositionType type; + + @NotNull + @NotBlank + @Schema(description = "位置数据") + private String position; + + public Point3D getPoint3D() { + String[] positions = position.split(","); + Double[] doublePositions = Arrays.stream(positions) + .map(Double::parseDouble) + .toArray(Double[]::new); + return new Point3D(doublePositions[0], doublePositions[1], doublePositions[2]); + } + + public Point2D getPoint2D() { + String[] positions = position.split(","); + Double[] doublePositions = Arrays.stream(positions) + .map(Double::parseDouble) + .toArray(Double[]::new); + return new Point2D(doublePositions[0], doublePositions[1]); + } + + public Double getDistance() { + return Double.valueOf(position); + } + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/Ores.java b/src/main/java/com/iflytop/sgs/app/model/entity/Ores.java new file mode 100644 index 0000000..306d03a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/Ores.java @@ -0,0 +1,20 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "矿石") +@TableName("ores") +@Data +public class Ores extends BaseEntity { + + @NotNull + @Schema(description = "矿石名称") + private String name; + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/Solutions.java b/src/main/java/com/iflytop/sgs/app/model/entity/Solutions.java new file mode 100644 index 0000000..4f85106 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/Solutions.java @@ -0,0 +1,20 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "溶液") +@TableName("solutions") +@Data +public class Solutions extends BaseEntity { + + @NotNull + @Schema(description = "溶液名称") + private String name; + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/SystemConfig.java b/src/main/java/com/iflytop/sgs/app/model/entity/SystemConfig.java new file mode 100644 index 0000000..7a6ec0c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/SystemConfig.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "系统配置") +@TableName("system_config") +@Data +public class SystemConfig extends BaseEntity { + + @NotBlank + @Schema(description = "配置键") + private String key; + + @Schema(description = "配置值") + private String value; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/SystemLog.java b/src/main/java/com/iflytop/sgs/app/model/entity/SystemLog.java new file mode 100644 index 0000000..56cda96 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/SystemLog.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "系统日志") +@TableName("system_log") +@Data +public class SystemLog extends BaseEntity { + + @NotBlank + @Schema(description = "日志标题") + private String title; + + @Schema(description = "日志内容") + private String content; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/TaskSteps.java b/src/main/java/com/iflytop/sgs/app/model/entity/TaskSteps.java new file mode 100644 index 0000000..b385bd9 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/TaskSteps.java @@ -0,0 +1,30 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = false) +@Schema(description = "实验步骤") +@TableName("task_steps") +@Data +public class TaskSteps { + + @NotBlank + @Schema(description = "id") + private Long id; + + @NotBlank + @Schema(description = "实验id") + private Long taskId; + + @NotBlank + @Schema(description = "步骤描述") + private String stepDescription; + + @NotBlank + @Schema(description = "创建时间") + private String createTime; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/Tasks.java b/src/main/java/com/iflytop/sgs/app/model/entity/Tasks.java new file mode 100644 index 0000000..3b01532 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/Tasks.java @@ -0,0 +1,40 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.iflytop.sgs.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "实验") +@TableName("tasks") +@Data +public class Tasks extends BaseEntity { + + @NotBlank + @Schema(description = "实验名称") + private String name; + + @NotBlank + @Schema(description = "开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "结束时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "创建人") + private Long createUser; + + @Schema(description = "状态 1 执行中 2 执行完毕") + private Integer status; + + @Schema(description = "是否删除 0 未删除 1 已删除") + private Integer isDeleted; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/entity/User.java b/src/main/java/com/iflytop/sgs/app/model/entity/User.java new file mode 100644 index 0000000..919ef86 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/entity/User.java @@ -0,0 +1,45 @@ +package com.iflytop.sgs.app.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.sgs.common.base.BaseEntity; +import com.iflytop.sgs.common.enums.data.Deleted; +import com.iflytop.sgs.common.enums.data.FixedUser; +import com.iflytop.sgs.common.enums.data.UsrRole; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户实体 + */ +@EqualsAndHashCode(callSuper = true) +@Schema(description = "用户") +@TableName("user") +@Data +public class User extends BaseEntity { + + @NotNull + @Schema(description = "用户名") + private String username; + + @NotNull + @Schema(description = "昵称") + private String nickname; + + @NotNull + @Schema(description = "密码") + private String password; + + @NotNull + @Schema(description = "人员角色") + private UsrRole role; + + @NotNull + @Schema(description = "是否删除") + private Deleted deleted; + + @Schema(description = "是否是系统固定用户") + private FixedUser fixedUser; + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/ContainerVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/ContainerVO.java new file mode 100644 index 0000000..4b258d6 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/ContainerVO.java @@ -0,0 +1,41 @@ +package com.iflytop.sgs.app.model.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.iflytop.sgs.app.model.entity.Solutions; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "容器") +@Data +public class ContainerVO { + private Long id; + + @Schema(description = "类型 0 溶液 1 废液") + private Long type; + + @Schema(description = "容器代码") + private String code; + + @Schema(description = "当前配置的溶液") + private Solutions solutions; + + @Schema(description = "泵id") + private String pumpId; + + @Schema(description = "总容量") + private Integer capacityTotal; + + @Schema(description = "当前容量") + private Integer capacityUsed; + + @Schema(description = "预充量") + private Double filled; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/CraftStatusVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/CraftStatusVO.java new file mode 100644 index 0000000..3fd5c5a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/CraftStatusVO.java @@ -0,0 +1,39 @@ +package com.iflytop.sgs.app.model.vo; + +import com.iflytop.sgs.app.model.bo.CraftsStep; +import com.iflytop.sgs.common.enums.automaton.CraftStates; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 工艺执行状态视图对象 + */ +@Data +public class CraftStatusVO { + + @Schema(description = "加热区 ID") + private String heatId; + + @Schema(description = "矿石 ID") + private Long oresId; + + @Schema(description = "矿石名称") + private String oresName; + + @Schema(description = "工艺 ID") + private Long craftsId; + + @Schema(description = "工艺名称") + private String craftsName; + + @Schema(description = "当前状态") + private CraftStates state; + + @Schema(description = "当前执行到的步骤索引") + private int currentIndex; + + @Schema(description = "工艺所有步骤") + private List steps; +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/DeviceParamGroupVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/DeviceParamGroupVO.java new file mode 100644 index 0000000..589e1b2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/DeviceParamGroupVO.java @@ -0,0 +1,36 @@ +package com.iflytop.sgs.app.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +/** + * 按模块分组的设备参数视图 + */ +@Data +@Schema(description = "按模块分组的设备参数") +public class DeviceParamGroupVO { + + @Schema(description = "模块标识(ModuleId.name),如 DoorM") + private String mid; + + @Schema(description = "该模块下的所有配置项") + private List reg; + + public DeviceParamGroupVO(String mid, List reg) { + this.mid = mid; + this.reg = reg; + } + + @AllArgsConstructor + @Data + @Schema(description = "配置项") + public static class ParamItem { + @Schema(description = "配置项名称,如 kreg_step_motor_pos") + private String name; + @Schema(description = "值") + private Integer value; + } +} diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/DevicePositionVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/DevicePositionVO.java new file mode 100644 index 0000000..017230f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/DevicePositionVO.java @@ -0,0 +1,39 @@ +package com.iflytop.sgs.app.model.vo; + +import com.iflytop.sgs.app.model.entity.DevicePosition; +import com.iflytop.sgs.common.enums.data.DevicePositionCode; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 设备位置记录 的展示视图 + */ +@Data +@AllArgsConstructor +@Schema(description = "设备位置记录") +public class DevicePositionVO { + + private Long id; + + @Schema(description = "位置名称") + private String name; + + @Schema(description = "位置代码") + private DevicePositionCode code; + + @Schema(description = "位置类型") + private String type; + + @Schema(description = "位置数据") + private String position; + + public DevicePositionVO(DevicePosition devicePosition) { + this.id = devicePosition.getId(); + this.name = devicePosition.getName(); + this.code = devicePosition.getCode(); + this.type = devicePosition.getType().getName(); + this.position = devicePosition.getPosition(); + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/ModuleIdVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/ModuleIdVO.java new file mode 100644 index 0000000..5879729 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/ModuleIdVO.java @@ -0,0 +1,19 @@ +package com.iflytop.sgs.app.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * ModuleId 的展示视图 + */ +@Data +@AllArgsConstructor +@Schema(description = "ModuleId 的展示视图") +public class ModuleIdVO { + @Schema(description = "模块名称,如 DoorM") + private String name; + + @Schema(description = "模块描述") + private String description; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/OresCraftsListVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/OresCraftsListVO.java new file mode 100644 index 0000000..12cfabc --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/OresCraftsListVO.java @@ -0,0 +1,30 @@ +package com.iflytop.sgs.app.model.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.iflytop.sgs.app.model.entity.Crafts; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "矿石工艺视图") +@Data +public class OresCraftsListVO { + + @Schema(description = "矿石id") + private Long id; + + @Schema(description = "矿石名称") + private String oresName; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "该矿石下工艺列表") + private List craftsList; + +} diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/RegIndexVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/RegIndexVO.java new file mode 100644 index 0000000..d0a7c70 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/RegIndexVO.java @@ -0,0 +1,16 @@ +package com.iflytop.sgs.app.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * RegIndex 的展示视图 + */ +@Data +@AllArgsConstructor +@Schema(description = "RegIndex 的展示视图") +public class RegIndexVO { + @Schema(description = "配置项名称,如 kreg_step_motor_pos") + private String name; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/SetCraftsVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/SetCraftsVO.java new file mode 100644 index 0000000..f3578c0 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/SetCraftsVO.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.app.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class SetCraftsVO { + @Schema(description = "加热区id") + private String heatId; + + @Schema(description = "工艺名称") + private String craftsName; + + @Schema(description = "工艺名称") + private Long craftsId; + + @Schema(description = "矿石名称") + private String oresName; + + @Schema(description = "矿石ID") + private Long oresId; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/SetTargetTemperatureVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/SetTargetTemperatureVO.java new file mode 100644 index 0000000..ea57e5b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/SetTargetTemperatureVO.java @@ -0,0 +1,32 @@ +package com.iflytop.sgs.app.model.vo; + +import com.iflytop.sgs.common.enums.HeatModuleCode; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = false) +@Data +public class SetTargetTemperatureVO { + @Schema(description = "加热模块code") + private HeatModuleCode moduleCode; + + @Max(value = 150, message = "加热温度不能超过150度") + @Min(value = 0, message = "加热温度不能低于0度") + @Schema(description = "加热温度") + private Double heatTemperature; + + + @Max(value = 200, message = "烘干温度不能超过200度") + @Min(value = 100, message = "烘干温度不能低于100度") + @Schema(description = "烘干温度") + private Double dryTemperature; + + + @Max(value = 400, message = "退火温度不能超过400度") + @Min(value = 200, message = "退火温度不能低于200度") + @Schema(description = "退火温度") + private Double annealTemperature; +} diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/SetTrayTubeVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/SetTrayTubeVO.java new file mode 100644 index 0000000..eb4b144 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/SetTrayTubeVO.java @@ -0,0 +1,18 @@ +package com.iflytop.sgs.app.model.vo; + +import com.iflytop.sgs.app.core.device.TubeState; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 设置托盘试管 + */ +@Data +public class SetTrayTubeVO { + @Schema(description = "托盘UUID") + private String trayUuid; + + @Schema(description = "矿石 ID") + private TubeState[] tubes; + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/model/vo/TaskStepsVO.java b/src/main/java/com/iflytop/sgs/app/model/vo/TaskStepsVO.java new file mode 100644 index 0000000..78bfc5c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/model/vo/TaskStepsVO.java @@ -0,0 +1,16 @@ +package com.iflytop.sgs.app.model.vo; + +import com.iflytop.sgs.app.model.entity.TaskSteps; +import com.iflytop.sgs.app.model.entity.Tasks; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@EqualsAndHashCode(callSuper = false) +@Data +public class TaskStepsVO extends Tasks { + @Schema(description = "实验步骤") + private List steps; +} diff --git a/src/main/java/com/iflytop/sgs/app/service/ContainerService.java b/src/main/java/com/iflytop/sgs/app/service/ContainerService.java new file mode 100644 index 0000000..1378bcd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/ContainerService.java @@ -0,0 +1,49 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.ContainerMapper; +import com.iflytop.sgs.app.model.entity.Container; +import com.iflytop.sgs.common.enums.AcidPumpDeviceCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 容器实现类 + */ +@Service +@RequiredArgsConstructor +public class ContainerService extends ServiceImpl { + private final ContainerMapper containerMapper; + + public List getList() { + return this.list(); + } + + /** + * 根据溶液id获取泵id + */ + public AcidPumpDeviceCode getPumpBySolutionId(Long solutionId) { + Container container = this.getOne(new LambdaQueryWrapper().eq(Container::getSolutionId, solutionId)); + if (container != null) { + String pumpId = container.getPumpId(); + return AcidPumpDeviceCode.valueOf(pumpId); + } + return null; + } + + /** + * 根据容器id获取泵id + */ + public AcidPumpDeviceCode getPumpByContainerId(Long containerId) { + Container container = this.getById(containerId); + if (container != null) { + String pumpId = container.getPumpId(); + return AcidPumpDeviceCode.valueOf(pumpId); + } + return null; + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/CraftsService.java b/src/main/java/com/iflytop/sgs/app/service/CraftsService.java new file mode 100644 index 0000000..8c2e194 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/CraftsService.java @@ -0,0 +1,191 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.core.CraftsContext; +import com.iflytop.sgs.app.mapper.CraftsMapper; +import com.iflytop.sgs.app.model.entity.Crafts; +import com.iflytop.sgs.app.model.entity.Ores; +import com.iflytop.sgs.app.model.vo.CraftStatusVO; +import com.iflytop.sgs.app.model.vo.SetCraftsVO; +import com.iflytop.sgs.common.enums.automaton.CraftEvents; +import com.iflytop.sgs.common.enums.automaton.CraftStates; +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.result.ResultCode; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +/** + * 工艺执行管理服务 + */ +@Service +@RequiredArgsConstructor +public class CraftsService extends ServiceImpl { + private final StateMachineFactory stateMachineFactory; + private final WebSocketService webSocketService; + private final CraftsStepService craftsStepService; + private final OresService oresService; + private final ConcurrentHashMap contextMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> futureMap = new ConcurrentHashMap<>(); + private ExecutorService executor; + + @PostConstruct + public void init() { + this.executor = Executors.newCachedThreadPool(); + } + + /** + * 配置工艺 + */ + public synchronized SetCraftsVO setCraft(Long craftId, String heatId) { + // 校验已有上下文状态,仅允许在 READY、STOPPED 或 FINISHED 状态下重置 + CraftsContext existing = contextMap.get(heatId); + if (existing != null) { + CraftStates state = existing.getSm().getState().getId(); + if (state == CraftStates.RUNNING || state == CraftStates.PAUSED) { + throw new AppException(ResultCode.CRAFT_RUNNING); + } + clearCraftContext(heatId); + } + Crafts craft = this.getById(craftId); + Ores ores = oresService.getById(craft.getOresId()); + CraftsContext ctx = new CraftsContext( + heatId, + ores, + craft, + stateMachineFactory, + webSocketService, + craftsStepService + ); + contextMap.put(heatId, ctx); + SetCraftsVO setCraftsVO = new SetCraftsVO(); + setCraftsVO.setHeatId(heatId); + setCraftsVO.setCraftsName(craft.getName()); + setCraftsVO.setCraftsId(craft.getId()); + setCraftsVO.setOresName(ores.getName()); + setCraftsVO.setOresId(ores.getId()); + return setCraftsVO; + } + + /** + * 启动执行工艺(需先调用 setCraft) + */ + public synchronized void startCrafts(String heatId) { + CraftsContext ctx = contextMap.get(heatId); + if (ctx == null) { + throw new AppException(ResultCode.CRAFT_CONTEXT_NULL); + } + if (futureMap.containsKey(heatId)) { + throw new AppException(ResultCode.CRAFT_RUNNING); + } + Future future = executor.submit(ctx); + futureMap.put(heatId, future); + } + + /** + * 暂停执行工艺 + */ + public synchronized void pauseCrafts(String heatId) { + CraftsContext ctx = contextMap.get(heatId); + if (ctx == null) { + throw new AppException(ResultCode.CRAFT_CONTEXT_NULL); + } + ctx.pause(); + } + + /** + * 恢复执行工艺 + */ + public synchronized void resumeCrafts(String heatId) { + CraftsContext ctx = contextMap.get(heatId); + if (ctx == null) { + throw new AppException(ResultCode.CRAFT_CONTEXT_NULL); + } + ctx.resume(); + } + + /** + * 停止执行工艺,不清除上下文 + */ + public synchronized void stopCrafts(String heatId) { + CraftsContext ctx = contextMap.get(heatId); + Future future = futureMap.remove(heatId); + if (ctx == null || future == null) { + throw new AppException(ResultCode.CRAFT_CONTEXT_NULL); + } + ctx.stop(); + future.cancel(true); + } + + /** + * 清理指定 heatId 的执行上下文和 Future + */ + public synchronized void clearCraftContext(String heatId) { + contextMap.remove(heatId); + Future future = futureMap.remove(heatId); + if (future != null) future.cancel(true); + } + + /** + * 查询指定 heatId 的执行状态 + */ + public CraftStatusVO getStatus(String heatId) { + CraftsContext ctx = contextMap.get(heatId); + if (ctx == null) { + return null; + } + CraftStatusVO vo = new CraftStatusVO(); + vo.setHeatId(heatId); + vo.setOresId(ctx.getOres().getId()); + vo.setOresName(ctx.getOres().getName()); + vo.setCraftsId(ctx.getCraft().getId()); + vo.setCraftsName(ctx.getCraft().getName()); + vo.setState(ctx.getSm().getState().getId()); + vo.setCurrentIndex(ctx.getCurrentIndex()); + vo.setSteps(ctx.getCraftsStepList()); + return vo; + } + + /** + * 查询所有正在执行的工艺状态 + */ + public List getAllStatuses() { + return contextMap.entrySet().stream().map(entry -> { + String heatIdKey = entry.getKey(); + CraftsContext ctx = entry.getValue(); + CraftStatusVO vo = new CraftStatusVO(); + vo.setHeatId(heatIdKey); + vo.setOresId(ctx.getOres().getId()); + vo.setOresName(ctx.getOres().getName()); + vo.setCraftsId(ctx.getCraft().getId()); + vo.setCraftsName(ctx.getCraft().getName()); + vo.setState(ctx.getSm().getState().getId()); + vo.setCurrentIndex(ctx.getCurrentIndex()); + vo.setSteps(ctx.getCraftsStepList()); + return vo; + }).collect(Collectors.toList()); + } + + public List selectAllByOresId(Long oresId) { + return this.baseMapper.selectAllByOresId(oresId); + } + + public Crafts findByName(String name) { + return this.getOne(new LambdaQueryWrapper<>(new Crafts()).eq(Crafts::getName, name)); + } + + public boolean deleteCrafts(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")).map(Long::parseLong).collect(Collectors.toList()); + return this.removeByIds(ids); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/CraftsStepService.java b/src/main/java/com/iflytop/sgs/app/service/CraftsStepService.java new file mode 100644 index 0000000..0e1fe3b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/CraftsStepService.java @@ -0,0 +1,86 @@ +package com.iflytop.sgs.app.service; + +import cn.hutool.json.JSONObject; +import com.iflytop.sgs.app.core.CraftsDebugGenerator; +import com.iflytop.sgs.app.model.bo.CraftsStep; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 工艺步骤执行服务 + */ +@Service +@RequiredArgsConstructor +public class CraftsStepService { + private final DeviceCommandUtilService deviceCommandUtilService; + private final DevicePositionService devicePositionService; + private final ContainerService containerService; + private final WebSocketService webSocketService; + private final GantryArmService gantryArmService; + private final DeviceStateService deviceStateService; + + + /** + * 执行单个工艺步骤 + * + * @param heatId 加热区 ID + * @param step 工艺步骤,包括 method 和 params + * @return true 表示执行成功,false 表示失败 + */ + public boolean executeStep(String heatId, CraftsStep step) throws Exception { + String method = step.getMethod(); + JSONObject params = step.getParams(); + return switch (method) { + case "addLiquid" -> addLiquid(heatId, params); + case "shaking" -> shaking(heatId, params); + case "startHeating" -> heating(heatId, params); + case "takePhoto" -> takePhoto(heatId); + default -> true; + }; + } + + /** + * 将托盘从加热区移动至加液区并且添加溶液 + */ + private boolean addLiquid(String heatId, JSONObject params) throws Exception { + return true; + } + + /** + * 摇匀操作 + */ + private boolean shaking(String heatId, JSONObject params) throws Exception { + Integer second = params.getInt("second"); + webSocketService.pushCraftsDebug(CraftsDebugGenerator.generateJson(heatId, "开始摇匀", params)); + delay(second); + webSocketService.pushCraftsDebug(CraftsDebugGenerator.generateJson(heatId, "停止摇匀", null)); + return true; + } + + /** + * 将托盘从加液区移动至加热区并开启加热 + */ + private boolean heating(String heatId, JSONObject params) throws Exception { + return true; + } + + /** + * 拍照操作· + */ + private boolean takePhoto(String heatId) throws Exception { + deviceCommandUtilService.fillLightClose();//关闭补光灯 + return true; + } + + /** + * 延时等待 + */ + private boolean delay(int seconds) { + try { + Thread.sleep(seconds * 1000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return true; + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/DeviceCommandService.java b/src/main/java/com/iflytop/sgs/app/service/DeviceCommandService.java new file mode 100644 index 0000000..b67cb0e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/DeviceCommandService.java @@ -0,0 +1,180 @@ +package com.iflytop.sgs.app.service; + + +import cn.hutool.json.JSONObject; +import com.iflytop.sgs.app.core.DebugGenerator; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.CyclicNumberGenerator; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.constant.CommandStatus; +import com.iflytop.sgs.hardware.HardwareService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedBlockingQueue; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DeviceCommandService { + private final HardwareService hardwareService; + private final WebSocketService webSocketService; + private final DeviceStateService deviceStateService; + /** + * 需要等待加液区空闲的龙门架机械臂指令 + */ + private final ConcurrentMap sendCommandFutureMap = new ConcurrentHashMap<>(); + private final BlockingQueue gantryCommandQueue = new LinkedBlockingQueue<>(); + + @PostConstruct + private void initExecutorThread() { + new Thread(this::executeCommands).start(); + } + + private void executeCommands() { + while (true) { + try { + deviceStateService.setGantryArmStateIdle(true); + CommandFuture[] commandFutureArray = gantryCommandQueue.take(); + for (CommandFuture commandFuture : commandFutureArray) { + executeCommand(commandFuture); + } + } catch (Exception e) { + Thread.currentThread().interrupt(); + } finally { + deviceStateService.setGantryArmStateIdle(false); + } + } + } + + public synchronized CommandFuture[] sendCommandGantryQueue(DeviceCommandBundle... deviceCommandBundles) { + return sendCommandGantryQueue(null, null, deviceCommandBundles); + } + + public synchronized CommandFuture[] sendCommandGantryQueue(String cmdId, String cmdCode, DeviceCommandBundle... deviceCommandBundles) { + List commandFutureList = new ArrayList<>(); + for (DeviceCommandBundle deviceCommandBundle : deviceCommandBundles) { + commandFutureList.add(createDeviceCommandFuture(cmdId, cmdCode, deviceCommandBundle)); + } + CommandFuture[] commandFutureArray = commandFutureList.toArray(new CommandFuture[0]); + try { + gantryCommandQueue.put(commandFutureArray); + } catch (Exception e) { + log.error("设备指令入队列失败", e); + throw new RuntimeException(e); + } + return commandFutureArray; + } + + /** + * 根据 DeviceCommand 创建 CommandFuture + */ + private CommandFuture createDeviceCommandFuture(String cmdId, String cmdCode, DeviceCommandBundle deviceCommandBundle) { + CommandFuture commandFuture = createDeviceCommandFuture(deviceCommandBundle); + commandFuture.setCmdId(cmdId); + commandFuture.setCmdCode(cmdCode); + return commandFuture; + } + + /** + * 根据 DeviceCommand 创建 CommandFuture + */ + private CommandFuture createDeviceCommandFuture(DeviceCommandBundle deviceCommandBundle) { + CommandFuture commandFuture = new CommandFuture(); + commandFuture.setDeviceCommandBundle(deviceCommandBundle); + commandFuture.getResponseFuture().whenComplete((result, ex) -> { + sendCommandFutureMap.remove(deviceCommandBundle.getDeviceCommand().getCmdId()); + }); + return commandFuture; + } + + public void executeCommand(CommandFuture commandFuture) { + int cmdId = CyclicNumberGenerator.getInstance().generateNumber(); + commandFuture.getDeviceCommandBundle().getDeviceCommand().setCmdId(cmdId); + sendCommandFutureMap.put(cmdId, commandFuture); + commandFuture.setStartSendTime(System.currentTimeMillis()); + if (!deviceStateService.getDeviceState().isVirtual()) { + if (!hardwareService.sendCommand(commandFuture.getDeviceCommandBundle().getDeviceCommand())) { + sendCommandFutureMap.remove(commandFuture.getDeviceCommandBundle().getDeviceCommand().getCmdId()); + throw new RuntimeException("向设备发送指令失败"); + } + } else { + //虚拟模式 + new Thread(() -> { + try { + String actionName = commandFuture.getDeviceCommandBundle().getDeviceCommand().getAction().name(); + if (actionName.contains("move") || actionName.contains("origin")) { + Thread.sleep(300); + } + JSONObject jsonObject = new JSONObject(); + jsonObject.putOnce("cmdId", cmdId); + jsonObject.putOnce("success", true); + completeCommandResponse(jsonObject); + } catch (InterruptedException e) { + // 处理中断异常 + Thread.currentThread().interrupt(); + } + }).start(); + } + if (commandFuture.getCmdId() != null) { + webSocketService.pushDebug(DebugGenerator.generateJson(commandFuture.getCmdId(), commandFuture.getCmdCode(), CommandStatus.DEVICE_SEND, commandFuture.getDeviceCommandBundle().getCmdName() + "指令,已发给设备", commandFuture.getDeviceCommandBundle())); + } + + } + + public CommandFuture sendCommand(DeviceCommandBundle deviceCommand) { + CommandFuture commandFuture = createDeviceCommandFuture(deviceCommand); + executeCommand(commandFuture); + return commandFuture; + } + + public CommandFuture sendCommand(String cmdId, String cmdCode, DeviceCommandBundle deviceCommandBundle) { + CommandFuture commandFuture = createDeviceCommandFuture(cmdId, cmdCode, deviceCommandBundle); + executeCommand(commandFuture); + return commandFuture; + } + + public void completeCommandResponse(JSONObject deviceResult) { + Integer cmdId = deviceResult.getInt("cmdId"); + if (cmdId != null) { + CommandFuture future = sendCommandFutureMap.get(cmdId); + if (future != null) { + future.setEndSendTime(System.currentTimeMillis()); + Boolean success = deviceResult.getBool("success"); //数据验证 + if (success == null || !success) { //response失败 + if (future.getCmdId() != null) { + webSocketService.pushDebug(DebugGenerator.generateJson(future.getCmdId(), future.getCmdCode(), CommandStatus.DEVICE_ERROR, + future.getDeviceCommandBundle().getCmdName() + "指令,设备response错误,耗时:" + (future.getEndSendTime() - future.getStartSendTime()), deviceResult)); + } + future.completeResponseExceptionally(new RuntimeException("response失败:" + deviceResult)); + } else { + if (future.getCmdId() != null) { + webSocketService.pushDebug(DebugGenerator.generateJson(future.getCmdId(), future.getCmdCode(), CommandStatus.DEVICE_RESULT, + future.getDeviceCommandBundle().getCmdName() + "指令,设备response正常,耗时:" + (future.getEndSendTime() - future.getStartSendTime()), deviceResult)); + } + future.completeResponse(deviceResult); + } + } + } + } + + /** + * 取消等待中的future并从map中移除 + */ + public synchronized void releaseAllCommandFutures() { + for (Integer key : sendCommandFutureMap.keySet()) { + CommandFuture future = sendCommandFutureMap.remove(key); + if (future != null) { + future.getResponseFuture().cancel(true); + } + } + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/DeviceCommandUtilService.java b/src/main/java/com/iflytop/sgs/app/service/DeviceCommandUtilService.java new file mode 100644 index 0000000..d7f42eb --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/DeviceCommandUtilService.java @@ -0,0 +1,567 @@ +package com.iflytop.sgs.app.service; + +import com.iflytop.sgs.app.model.bo.Point2D; +import com.iflytop.sgs.app.model.bo.Point3D; +import com.iflytop.sgs.app.model.entity.DevicePosition; +import com.iflytop.sgs.common.cmd.CommandFuture; +import com.iflytop.sgs.common.cmd.DeviceCommandBundle; +import com.iflytop.sgs.common.cmd.DeviceCommandGenerator; +import com.iflytop.sgs.common.enums.AcidPumpDeviceCode; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import com.iflytop.sgs.common.enums.data.DevicePositionCode; +import com.iflytop.sgs.hardware.drivers.LiquidDistributionArmDriver; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.StepMotorCtrlDriver; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.service.GDDeviceStatusService; +import com.iflytop.sgs.hardware.type.IO.InputIOMId; +import com.iflytop.sgs.hardware.type.Servo.LiquidArmMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DeviceCommandUtilService { + private final DeviceCommandService deviceCommandService; + private final DevicePositionService devicePositionService; + private final LiquidDistributionArmDriver liquidDistributionArmDriver; + private final GDDeviceStatusService gdDeviceStatusService; + private final StepMotorCtrlDriver stepMotorCtrlDriver; + + /** + * 门电机回原点 + */ + public void doorOrigin() throws Exception { + doorOrigin(null, null); + } + + /** + * 门电机回原点 + */ + public void doorOrigin(String cmdId, String cmdCode) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.doorOrigin(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 门电机移动 + */ + public void doorMove(double position) throws Exception { + doorMove(null, null, position); + } + + /** + * 门电机移动 + */ + public void doorMove(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.doorMove(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 龙门架机械臂移动到0点 + */ + public void gantryMoveZero() throws Exception { + gantryMove(null, null, new Point3D(0.0, 0.0, 0.0)); + } + + /** + * 龙门架机械臂移动到指定点 + */ + public void gantryMove(Point3D point) throws Exception { + gantryMove(null, null, point); + } + + /** + * 龙门架机械臂移动到指定点 + */ + public void gantryMove(String cmdId, String cmdCode, Point3D point) throws Exception { + DeviceCommandBundle gantryXMoveDeviceCommand = DeviceCommandGenerator.gantryXMove(point.getX()); + CommandFuture gantryXMoveDeviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, gantryXMoveDeviceCommand); + DeviceCommandBundle gantryZMoveDeviceCommand = DeviceCommandGenerator.gantryZMove(point.getZ()); + CommandFuture gantryZMoveDeviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, gantryZMoveDeviceCommand); + commandWait(gantryXMoveDeviceCommandFuture,gantryZMoveDeviceCommandFuture); + } + + /** + * 龙门架机械臂移动到指定点 + */ + public void gantryMoveQueue(Point3D point) throws Exception { + gantryMoveQueue(null, null, point); + } + + /** + * 龙门架机械臂移动到指定点 + */ + public void gantryMoveQueue(String cmdId, String cmdCode, Point3D point) throws Exception { + DeviceCommandBundle gantryXMoveDeviceCommand = DeviceCommandGenerator.gantryXMove(point.getX()); + DeviceCommandBundle gantryZMoveDeviceCommand = DeviceCommandGenerator.gantryZMove(point.getZ()); + CommandFuture[] deviceCommandFutureArr = deviceCommandService.sendCommandGantryQueue(cmdId, cmdCode, gantryXMoveDeviceCommand, gantryZMoveDeviceCommand); + commandWait(deviceCommandFutureArr); + } + + + /** + * 龙门架机械臂X轴回原点 + */ + public void gantryXMoveOrigin() throws Exception { + gantryXMoveOrigin(null, null); + } + + /** + * 龙门架机械臂X轴回原点 + */ + public void gantryXMoveOrigin(String cmdId, String cmdCode) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXOrigin(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 龙门架机械臂X轴回原点 + */ + public void gantryXMoveOriginQueue() throws Exception { + gantryXMoveOriginQueue(null, null); + } + + /** + * 龙门架机械臂X轴回原点 + */ + public void gantryXMoveOriginQueue(String cmdId, String cmdCode) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXOrigin(); + CommandFuture[] deviceCommandFutureArr = deviceCommandService.sendCommandGantryQueue(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFutureArr); + } + + + /** + * 龙门架机械臂Z轴回原点 + */ + public void gantryZMoveOrigin() throws Exception { + gantryZMoveOrigin(null, null); + } + + /** + * 龙门架机械臂Z轴回原点 + */ + public void gantryZMoveOrigin(String cmdId, String cmdCode) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZOrigin(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + + /** + * 龙门架机械臂Z轴回原点 + */ + public void gantryZMoveOriginQueue() throws Exception { + gantryZMoveOriginQueue(null, null); + } + + /** + * 龙门架机械臂Z轴回原点 + */ + public void gantryZMoveOriginQueue(String cmdId, String cmdCode) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZOrigin(); + CommandFuture[] deviceCommandFutureArr = deviceCommandService.sendCommandGantryQueue(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFutureArr); + } + + /** + * 龙门架机械臂X轴移动到指定点 + */ + public void gantryXMove(double position) throws Exception { + gantryXMove(null, null, position); + } + + /** + * 龙门架机械臂X轴移动到指定点 + */ + public void gantryXMove(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXMove(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 龙门架机械臂X轴移动到指定点 + */ + public void gantryXMoveQueue(double position) throws Exception { + gantryXMoveQueue(null, null, position); + } + + /** + * 龙门架机械臂X轴移动到指定点 + */ + public void gantryXMoveQueue(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXMove(position); + CommandFuture[] deviceCommandFutureArr = deviceCommandService.sendCommandGantryQueue(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFutureArr); + } + + /** + * 龙门架机械臂Z轴移动到指定点 + */ + public void gantryZMove(double position) throws Exception { + gantryZMove(null, null, position); + } + + /** + * 加液机械臂移动到指定点 + */ + public void motorLiquidMove(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.motorLiquidMove(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + /** + * 加液机械臂移动到指定点 + */ + public void motorLiquidMove(double position) throws Exception { + motorLiquidMove(null, null, position); + } + + /** + * 龙门架机械臂Z轴移动到指定点 + */ + public void gantryZMove(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZMove(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 龙门架机械臂Z轴移动到指定点 + */ + public void gantryZMoveQueue(double position) throws Exception { + gantryZMoveQueue(null, null, position); + } + + /** + * 龙门架机械臂Z轴移动到指定点 + */ + public void gantryZMoveQueue(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZMove(position); + CommandFuture[] deviceCommandFutureArr = deviceCommandService.sendCommandGantryQueue(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFutureArr); + } + + /** + * 龙门架机械臂X轴相对移动到指定点 + */ + public void gantryXMoveBy(double position) throws Exception { + gantryXMoveBy(null, null, position); + } + + /** + * 龙门架机械臂X轴相对移动到指定点 + */ + public void gantryXMoveBy(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXMoveBy(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 龙门架机械臂X轴相对移动到指定点 + */ + public void gantryXMoveByQueue(double position) throws Exception { + gantryXMoveByQueue(null, null, position); + } + + /** + * 龙门架机械臂X轴相对移动到指定点 + */ + public void gantryXMoveByQueue(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryXMoveBy(position); + CommandFuture[] deviceCommandFutureArr = deviceCommandService.sendCommandGantryQueue(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFutureArr); + } + + /** + * 龙门架机械臂Y轴相对移动到指定点 + */ + public void gantryYMoveBy(double position) throws Exception { + gantryYMoveBy(null, null, position); + } + + /** + * 龙门架机械臂Y轴相对移动到指定点 + */ + public void gantryYMoveBy(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryYMoveBy(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 龙门架机械臂Y轴相对移动到指定点 + */ + public void gantryYMoveByQueue(double position) throws Exception { + gantryYMoveByQueue(null, null, position); + } + + /** + * 龙门架机械臂Y轴相对移动到指定点 + */ + public void gantryYMoveByQueue(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryYMoveBy(position); + CommandFuture[] deviceCommandFutureArr = deviceCommandService.sendCommandGantryQueue(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFutureArr); + } + + /** + * 龙门架机械臂Z轴相对移动到指定点 + */ + public void gantryZMoveBy(double position) throws Exception { + gantryZMoveBy(null, null, position); + } + + /** + * 龙门架机械臂Z轴相对移动到指定点 + */ + public void gantryZMoveBy(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZMoveBy(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 龙门架机械臂Z轴相对移动到指定点 + */ + public void gantryZMoveByQueue(double position) throws Exception { + gantryZMoveByQueue(null, null, position); + } + + /** + * 龙门架机械臂Z轴相对移动到指定点 + */ + public void gantryZMoveByQueue(String cmdId, String cmdCode, double position) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.gantryZMoveBy(position); + CommandFuture[] deviceCommandFutureArr = deviceCommandService.sendCommandGantryQueue(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFutureArr); + } + + + /** + * 双轴械臂 移动至指定试管 + */ + public void dualRobotMovePoint(int index) throws Exception { + liquidDistributionArmDriver.liquidDistributionArmMoveTo(LiquidArmMId.LiquidDistributionArm, index); + } + + /** + * 加液机械臂回原点 + */ + public void dualRobotOrigin() throws Exception { + liquidDistributionArmDriver.liquidDistributionArmMoveTo(LiquidArmMId.LiquidDistributionArm, 0); + } + + /** + * 停止泵旋转 + */ + public void acidPumpStop(String cmdId, String cmdCode, AcidPumpDeviceCode acidPumpDevice) throws Exception { + DeviceCommandBundle deviceCommand=DeviceCommandGenerator.liquidPumpStop(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 添加溶液 + */ + public void acidPumpMoveBy(AcidPumpDeviceCode acidPumpDevice, double position) throws Exception { + acidPumpMoveBy(null, null, acidPumpDevice, position); + } + + /** + * 添加溶液 + */ + public void acidPumpMoveBy(String cmdId, String cmdCode, AcidPumpDeviceCode acidPumpDevice, double position) throws Exception { + DeviceCommandBundle deviceCommand=DeviceCommandGenerator.liquidPumpMoveBy(position); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 指定加热模块开始加热 + */ + public void heatRodOpen(HeatModuleCode heatModuleId, double temperature) throws Exception { + heatRodOpen(null, null, heatModuleId, temperature); + } + + /** + * 指定加热模块开始加热 + */ + public void heatRodOpen(String cmdId, String cmdCode, HeatModuleCode heatModuleId, double temperature) throws Exception { + DeviceCommandBundle deviceCommand = switch (heatModuleId) { + case heat_module_01 -> DeviceCommandGenerator.heatRod1Open(temperature); + case heat_module_02 -> DeviceCommandGenerator.heatRod2Open(temperature); + case heat_module_03 -> DeviceCommandGenerator.heatRod3Open(temperature); + case heat_module_04 -> DeviceCommandGenerator.heatRod4Open(temperature); + }; + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 指定加热模块停止加热 + */ + public void heatRodClose(HeatModuleCode heatModuleId) throws Exception { + heatRodClose(null, null, heatModuleId); + } + + /** + * 指定加热模块停止加热 + */ + public void heatRodClose(String cmdId, String cmdCode, HeatModuleCode heatModuleId) throws Exception { + DeviceCommandBundle deviceCommand = switch (heatModuleId) { + case heat_module_01 -> DeviceCommandGenerator.heatRod1Close(); + case heat_module_02 -> DeviceCommandGenerator.heatRod2Close(); + case heat_module_03 -> DeviceCommandGenerator.heatRod3Close(); + case heat_module_04 -> DeviceCommandGenerator.heatRod4Close(); + }; + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 开启补光灯 + */ + public void fillLightOpen(Double brightness) throws Exception { + fillLightOpen(null, null, brightness); + } + + /** + * 开启补光灯 + */ + public void fillLightOpen(String cmdId, String cmdCode, Double brightness) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.fillLightOpen(brightness); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 关闭补光灯 + */ + public void fillLightClose() throws Exception { + fillLightClose(null, null); + } + + /** + * 关闭补光灯 + */ + public void fillLightClose(String cmdId, String cmdCode) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.fillLightClose(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 拍照 + */ + public void takePhoto() throws Exception { + takePhoto(null, null); + } + + /** + * 拍照 + */ + public void takePhoto(String cmdId, String cmdCode) throws Exception { + DeviceCommandBundle deviceCommand = DeviceCommandGenerator.takePhoto(); + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } + + /** + * 获取指定加热模块托盘检测传感器数值 + */ + public Boolean heatModuleSensorState(HeatModuleCode heatModuleCode) throws HardwareException { + return switch (heatModuleCode) { + case heat_module_01 -> gdDeviceStatusService.getInputState(InputIOMId.HEAT_MODULE01_EXIST); + case heat_module_02 -> gdDeviceStatusService.getInputState(InputIOMId.HEAT_MODULE02_EXIST); + case heat_module_03 -> gdDeviceStatusService.getInputState(InputIOMId.HEAT_MODULE03_EXIST); + case heat_module_04 -> gdDeviceStatusService.getInputState(InputIOMId.HEAT_MODULE04_EXIST); + }; + + } + + + public void commandWait(CommandFuture... futures) throws Exception { + CompletableFuture[] responseFutures = Arrays.stream(futures) + .map(CommandFuture::getResponseFuture) + .toArray(CompletableFuture[]::new); + CompletableFuture.allOf(responseFutures) + .get(120, TimeUnit.SECONDS); + } + + /////////////////////////////////////////////////获取位置 + + /** + * 获取指定加热区托盘夹爪点位 + */ + public Point3D getHeatAreaTrayClawPoint3D(HeatModuleCode heatModuleId) { + DevicePosition devicePosition = switch (heatModuleId) { + case heat_module_01 -> devicePositionService.getPosition(DevicePositionCode.heatArea1TrayClawPoint); + case heat_module_02 -> devicePositionService.getPosition(DevicePositionCode.heatArea2TrayClawPoint); + case heat_module_03 -> devicePositionService.getPosition(DevicePositionCode.heatArea3TrayClawPoint); + case heat_module_04 -> devicePositionService.getPosition(DevicePositionCode.heatArea4TrayClawPoint); + }; + return devicePosition.getPoint3D(); + } + + + /** + * 获取指定加热区拍子夹爪点位 + */ + public Point3D getHeatAreaCapClawPointPoint3D(HeatModuleCode heatModuleId) { + DevicePosition devicePosition = switch (heatModuleId) { + case heat_module_01 -> devicePositionService.getPosition(DevicePositionCode.heatArea1CapClawPoint); + case heat_module_02 -> devicePositionService.getPosition(DevicePositionCode.heatArea2CapClawPoint); + case heat_module_03 -> devicePositionService.getPosition(DevicePositionCode.heatArea3CapClawPoint); + case heat_module_04 -> devicePositionService.getPosition(DevicePositionCode.heatArea4CapClawPoint); + }; + return devicePosition.getPoint3D(); + } + + /** + * 根据试管需要获取点位 + */ + public Point2D getPointByTubeNum(int tubeNum) { + return new Point2D(tubeNum * 60.0, tubeNum * 60.0);// + } + + + public void fanStart(String cmdId, String cmdCode, HeatModuleCode heatModuleId) throws Exception { + DeviceCommandBundle deviceCommand; + switch (heatModuleId) { + case heat_module_01 -> deviceCommand = DeviceCommandGenerator.fan1Open(); + case heat_module_02 -> deviceCommand = DeviceCommandGenerator.fan2Open(); + case heat_module_03 -> deviceCommand = DeviceCommandGenerator.fan3Open(); + case heat_module_04 -> deviceCommand = DeviceCommandGenerator.fan4Open(); + default -> throw new RuntimeException("heatId 未找到"); + } + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + + } + + public void fanClose(String cmdId, String cmdCode, HeatModuleCode heatModuleId) throws Exception { + DeviceCommandBundle deviceCommand; + switch (heatModuleId) { + case heat_module_01 -> deviceCommand = DeviceCommandGenerator.fan1Close(); + case heat_module_02 -> deviceCommand = DeviceCommandGenerator.fan2Close(); + case heat_module_03 -> deviceCommand = DeviceCommandGenerator.fan3Close(); + case heat_module_04 -> deviceCommand = DeviceCommandGenerator.fan4Close(); + default -> throw new RuntimeException("heatId 未找到"); + } + CommandFuture deviceCommandFuture = deviceCommandService.sendCommand(cmdId, cmdCode, deviceCommand); + commandWait(deviceCommandFuture); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/DeviceInitService.java b/src/main/java/com/iflytop/sgs/app/service/DeviceInitService.java new file mode 100644 index 0000000..8715943 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/DeviceInitService.java @@ -0,0 +1,171 @@ +package com.iflytop.sgs.app.service; + +import com.iflytop.sgs.app.core.device.DeviceState; +import com.iflytop.sgs.app.core.device.HeatModuleState; +import com.iflytop.sgs.app.core.device.SolutionContainerState; +import com.iflytop.sgs.app.model.bo.DeviceInitializationData; +import com.iflytop.sgs.app.model.entity.Container; +import com.iflytop.sgs.app.model.entity.DeviceParamConfig; +import com.iflytop.sgs.common.enums.ContainerCode; +import com.iflytop.sgs.common.enums.ContainerType; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.constants.ActionOvertimeConstant; +import com.iflytop.sgs.hardware.drivers.DODriver.OutputIOCtrlDriver; +import com.iflytop.sgs.hardware.drivers.LeisaiServoDriver; +import com.iflytop.sgs.hardware.drivers.LiquidDistributionArmDriver; +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.MiniServoDriver; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.StepMotorCtrlDriver; +import com.iflytop.sgs.hardware.drivers.TricolorLightDriver; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.RegIndex; +import com.iflytop.sgs.hardware.type.Servo.LeisaiServoMId; +import com.iflytop.sgs.hardware.type.Servo.LiquidArmMId; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DeviceInitService { + private final ContainerService containerService; + private final DeviceStateService deviceStateService; + private final A8kCanBusService canBusService; + private final StepMotorCtrlDriver stepMotorCtrlDriver; + private final MiniServoDriver miniServoDriver; + private final LiquidDistributionArmDriver liquidDistributionArmDriver; + private final TricolorLightDriver tricolorLightDriver; + private final LeisaiServoDriver leisaiServoDriver; + private final ActionOvertimeConstant actionOvertimeConstant; + private final OutputIOCtrlDriver outputIOCtrlDriver; + private final DeviceParamConfigService deviceParamConfigService; + + @PostConstruct + public void init() { + new Thread(() -> { + try { + Thread.sleep(2000); + CompletableFuture.runAsync(() -> { + try { + tricolorLightDriver.open(MId.TriColorLight, TricolorLightDriver.Color.BLUE); + } catch (HardwareException e) { + log.error("设备初始化灯光失败,Color.BLUE"); + } + }); + initDeviceState(); + initDeviceSetData(); + initOvertime(); + initEnable(); + try { + tricolorLightDriver.open(MId.TriColorLight, TricolorLightDriver.Color.GREEN); + } catch (HardwareException e) { + log.error("设备初始化灯光失败,Color.GREEN"); + } + deviceStateService.setInitComplete(true); + } catch (Exception e) { + try { + tricolorLightDriver.open(MId.TriColorLight, TricolorLightDriver.Color.RED); + } catch (Exception ignored) { + } + log.error("设备初始化失败", e); + } + }).start(); + } + + /** + * 设置各动作超时时间 + */ + public void initOvertime() { + actionOvertimeConstant.pushNewConfig(StepMotorMId.DOOR_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 90 * 1000); + actionOvertimeConstant.pushNewConfig(LeisaiServoMId.MainXSV, CmdId.step_motor_easy_move_to_zero, 300 * 1000); + actionOvertimeConstant.pushNewConfig(LeisaiServoMId.MainYSV, CmdId.step_motor_easy_move_to_zero, 120 * 1000); + actionOvertimeConstant.pushNewConfig(StepMotorMId.HBOT_Z_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 30 * 1000); + actionOvertimeConstant.pushNewConfig(StepMotorMId.HEATER_1_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 60 * 1000); + actionOvertimeConstant.pushNewConfig(StepMotorMId.HEATER_2_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 60 * 1000); + actionOvertimeConstant.pushNewConfig(StepMotorMId.HEATER_3_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 60 * 1000); + actionOvertimeConstant.pushNewConfig(StepMotorMId.HEATER_4_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 60 * 1000); + actionOvertimeConstant.pushNewConfig(StepMotorMId.HEATER_5_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 60 * 1000); + actionOvertimeConstant.pushNewConfig(StepMotorMId.HEATER_6_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 60 * 1000); + actionOvertimeConstant.pushNewConfig(StepMotorMId.TRAY_MOTOR_MID, CmdId.step_motor_easy_move_to_zero, 300 * 1000); + actionOvertimeConstant.pushNewConfig(LiquidArmMId.LiquidDistributionArm, CmdId.liquid_distribution_arm_move_to, 6 * 1000); + } + + /** + * 初始化所有设备使能 + */ + public void initEnable() throws Exception { + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.DOOR_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.SHAKE_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.TRAY_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HBOT_Z_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HEATER_1_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HEATER_2_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HEATER_3_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HEATER_4_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HEATER_5_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HEATER_6_MOTOR_MID, 1); + + liquidDistributionArmDriver.liquidDistributionArmEnable(LiquidArmMId.LiquidDistributionArm, 1); + miniServoDriver.miniServoForceEnable(MiniServoMId.CLAW_MID, 1); + leisaiServoDriver.enable(LeisaiServoMId.MainXSV, 1); + leisaiServoDriver.enable(LeisaiServoMId.MainYSV, 1); + + outputIOCtrlDriver.open(OutputIOMId.DO_HBOTZ_MOTOR_CLAMP); + outputIOCtrlDriver.open(OutputIOMId.DO_TRAY_MOTOR_CLAMP); + } + + public void initDeviceSetData() throws Exception { + if (deviceStateService.getDeviceState().isVirtual() || deviceStateService.getDeviceState().isInitComplete()) { + return; + } + //从数据库中读取数据通过串口进行数据初始化 + List deviceParamConfigs = deviceParamConfigService.list(); + for (DeviceParamConfig deviceParamConfig : deviceParamConfigs) { + DeviceInitializationData data = new DeviceInitializationData(); + data.setId(Math.toIntExact(deviceParamConfig.getId())); + data.setMid(deviceParamConfig.getMid()); + data.setRegIndex(deviceParamConfig.getRegIndex()); + data.setRegInitVal(deviceParamConfig.getRegVal()); + try { + sendToDevice(data); + } catch (Exception e) { + log.error("设备初始化写入参数失败,2秒后重试", e); + Thread.sleep(2000); + initDeviceSetData(); + } + } + + } + + public void sendToDevice(DeviceInitializationData data) throws Exception { + canBusService.moduleSetReg(MId.valueOf(data.getMid()), RegIndex.valueOf(data.getRegIndex()), data.getRegInitVal()); + } + + public void initDeviceState() { + DeviceState deviceState = deviceStateService.getDeviceState(); + List heatArea = deviceState.getHeatModule(); + for (HeatModuleCode code : HeatModuleCode.values()) { + heatArea.add(new HeatModuleState(code)); + } + + List containerList = containerService.getList(); + List solutionBucket = deviceState.getSolutionModule().getSolutionContainer(); + for (Container container : containerList) { + if (container.getType() == 0) { + solutionBucket.add(new SolutionContainerState(container.getId(), ContainerCode.valueOf(container.getCode()), ContainerType.solution)); + } else { + solutionBucket.add(new SolutionContainerState(container.getId(), ContainerCode.valueOf(container.getCode()), ContainerType.neutralization)); + } + } + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/DeviceParamConfigService.java b/src/main/java/com/iflytop/sgs/app/service/DeviceParamConfigService.java new file mode 100644 index 0000000..4a337bd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/DeviceParamConfigService.java @@ -0,0 +1,119 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.DeviceParamConfigMapper; +import com.iflytop.sgs.app.model.entity.DeviceParamConfig; +import com.iflytop.sgs.app.model.vo.DeviceParamGroupVO; +import com.iflytop.sgs.app.model.vo.ModuleIdVO; +import com.iflytop.sgs.app.model.vo.RegIndexVO; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.RegIndex; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 设备参数配置服务 + */ +@Service +@RequiredArgsConstructor +public class DeviceParamConfigService extends ServiceImpl { + private final DeviceParamConfigMapper deviceParamConfigMapper; + + /** + * 按 ModuleId 分组,列出每个模块下的 regIndex + regVal + */ + public List listGroupedByModule() { + // 拉取所有配置记录 + List all = this.list(); + + // 按 mid 分组 + Map> grouped = all.stream() + .collect(Collectors.groupingBy(DeviceParamConfig::getMid)); + + // 构造 VO 列表 + return grouped.entrySet().stream() + .map(entry -> { + String moduleId = entry.getKey(); + List items = entry.getValue().stream() + .map(cfg -> new DeviceParamGroupVO.ParamItem( + cfg.getRegIndex(), + cfg.getRegVal() + )) + .collect(Collectors.toList()); + return new DeviceParamGroupVO(moduleId, items); + }) + .collect(Collectors.toList()); + } + + /** + * 根据模块标识和寄存器索引查询设备参数配置 + * + * @param moduleId 模块枚举 + * @param regIndex 寄存器索引 + * @return 唯一匹配的 DeviceParamConfig,找不到返回 null + */ + public DeviceParamConfig getByModuleAndReg(MId moduleId, RegIndex regIndex) { + return deviceParamConfigMapper.selectOne( + new LambdaQueryWrapper() + .eq(DeviceParamConfig::getMid, moduleId.name()) + .eq(DeviceParamConfig::getRegIndex, regIndex.name()) + ); + } + + /** + * 根据模块标识和寄存器索引设置设备参数配置 + * + * @param moduleId 模块枚举 + * @param regIndex 寄存器索引 + * @param regVal 寄存器值 + * @return 唯一匹配的 DeviceParamConfig,找不到返回 null + */ + public int setModuleAndReg(String moduleId, String regIndex, Integer regVal) { + return deviceParamConfigMapper.update(new LambdaUpdateWrapper() + .eq(DeviceParamConfig::getMid, moduleId) + .eq(DeviceParamConfig::getRegIndex, regIndex) + .set(DeviceParamConfig::getRegVal, regVal)); + + } + + /** + * 列出所有 ModuleId,返回 VO 列表 + * + * @return List + */ + public List listAllModuleIds() { + return Arrays.stream(MId.values()) + .map(e -> new ModuleIdVO( + e.name(), // 枚举常量名 + e.description // 描述字段 + )) + .collect(Collectors.toList()); + } + + /** + * 列出所有寄存器索引枚举,返回 VO 列表 + * + * @return List + */ + public List listAllRegIndices() { + return Arrays.stream(RegIndex.values()) + .map(e -> new RegIndexVO( + e.name() // 枚举常量名 + )) + .collect(Collectors.toList()); + } + + public boolean deleteDeviceParam(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .collect(Collectors.toList()); + return this.removeByIds(ids); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/DevicePositionService.java b/src/main/java/com/iflytop/sgs/app/service/DevicePositionService.java new file mode 100644 index 0000000..908e9fc --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/DevicePositionService.java @@ -0,0 +1,69 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.DevicePositionMapper; +import com.iflytop.sgs.app.model.dto.AddDevicePositionDTO; +import com.iflytop.sgs.app.model.entity.DevicePosition; +import com.iflytop.sgs.app.model.vo.DevicePositionVO; +import com.iflytop.sgs.common.enums.data.DevicePositionCode; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 设备位置服务 + */ +@Service +@RequiredArgsConstructor +public class DevicePositionService extends ServiceImpl { + + @PostConstruct + private void init() { + for (DevicePositionCode positionCode : DevicePositionCode.values()) { + System.out.println(positionCode.getName()); // 打印每个枚举常量的name属性 + DevicePosition devicePosition = this.getOne(new LambdaQueryWrapper().eq(DevicePosition::getCode, positionCode)); + if (devicePosition == null) { + devicePosition = new DevicePosition(); + devicePosition.setCode(positionCode); + devicePosition.setName(positionCode.getName()); + devicePosition.setType(positionCode.getType()); + this.save(devicePosition); + } + } + } + + public boolean updatePosition(AddDevicePositionDTO addDevicePositionDTO){ + DevicePosition devicePosition = this.getById(addDevicePositionDTO.getId()); + if(devicePosition != null){ + devicePosition.setPosition(addDevicePositionDTO.getPosition()); + return this.updateById(devicePosition); + } + return false; + } + + public List getList() { + List devicePositions = super.list(); + return devicePositions.stream() + .map(DevicePositionVO::new) + .collect(Collectors.toList()); + } + + /** + * 根据位置类型获取位置 + */ + public DevicePosition getPosition(DevicePositionCode code) { + return this.getOne(new LambdaQueryWrapper<>(DevicePosition.class).eq(DevicePosition::getCode, code)); + } + + public boolean deletePositon(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .collect(Collectors.toList()); + return this.removeByIds(ids); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/DeviceStateService.java b/src/main/java/com/iflytop/sgs/app/service/DeviceStateService.java new file mode 100644 index 0000000..6f25587 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/DeviceStateService.java @@ -0,0 +1,381 @@ +package com.iflytop.sgs.app.service; + +import com.iflytop.sgs.app.core.device.*; +import com.iflytop.sgs.app.core.listener.DeviceStateListener; +import com.iflytop.sgs.app.model.entity.Tasks; +import com.iflytop.sgs.app.model.entity.User; +import com.iflytop.sgs.app.model.vo.SetTargetTemperatureVO; +import com.iflytop.sgs.common.enums.ContainerCode; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.beans.PropertyChangeSupport; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * 设备状态服务 + */ +@Service +@RequiredArgsConstructor +public class DeviceStateService { + private final transient PropertyChangeSupport support = new PropertyChangeSupport(this); + + private final DeviceState deviceState = new DeviceState(); + + @Getter + private final AtomicReference commandState = new AtomicReference<>(new CommandState()); + + + + public synchronized DeviceState getDeviceState() { + return deviceState; + } + + public void addListener(DeviceStateListener deviceStateListener) { + support.addPropertyChangeListener(deviceStateListener); + } + + + public synchronized HeatModuleState getHeatModuleState(HeatModuleCode moduleCode) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (moduleCode.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + return heatModuleState; + } + + public synchronized TrayState getTrayState(String trayUUID) { + TrayState foundTrayState = null; + for (TrayState t : deviceState.getTray()) { + if (trayUUID.equals(t.getUuid())) { + foundTrayState = t; + break; + } + } + return foundTrayState; + } + + public synchronized TrayState getTrayInSolutionModule() { + List trayList = deviceState.getTray(); + return trayList.stream() + .filter(TrayState::isInSolutionModule) + .findFirst() + .orElse(null); + } + + public synchronized void setVirtual(boolean virtual) { + boolean oldValue = deviceState.isVirtual(); + deviceState.setVirtual(virtual); + support.firePropertyChange("setVirtual", oldValue, virtual); + } + + public synchronized void setInitComplete(boolean initComplete) { + boolean oldValue = deviceState.isInitComplete(); + deviceState.setInitComplete(initComplete); + support.firePropertyChange("setInitComplete", oldValue, initComplete); + } + + public synchronized void setEmergencyStop(boolean emergencyStop) { + boolean oldValue = deviceState.isEmergencyStop(); + deviceState.setEmergencyStop(emergencyStop); + support.firePropertyChange("setEmergencyStop", oldValue, emergencyStop); + } + + public synchronized void setSelfTest(boolean selfTest) { + boolean oldValue = deviceState.isSelfTest(); + deviceState.setSelfTest(selfTest); + support.firePropertyChange("setSelfTest", oldValue, selfTest); + } + + public synchronized void setDoorStatus(boolean status) { + boolean oldValue = deviceState.getDoor().isOpen(); + deviceState.getDoor().setOpen(status); + support.firePropertyChange("setDoorStatus", oldValue, status); + } + + public synchronized void setGantryArmStateIdle(boolean idle) { + boolean oldValue = deviceState.getGantryArm().isIdle(); + deviceState.getGantryArm().setIdle(idle); + support.firePropertyChange("setGantryArmStateIdle", oldValue, idle); + } + + public synchronized void setHeatModuleStateTrayUp(HeatModuleCode heatModuleId, int trayStatusUp) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + int oldValue = heatModuleState.getTrayUp(); + heatModuleState.setTrayUp(trayStatusUp); + support.firePropertyChange("setHeatModuleStateTrayUp", oldValue, trayStatusUp); + } + + public synchronized void setHeatModuleStateTrayStatus(HeatModuleCode heatModuleId, int trayStatus) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + int oldValue = heatModuleState.getTrayStatus(); + heatModuleState.setTrayStatus(trayStatus); + support.firePropertyChange("setHeatModuleStateTrayStatus", oldValue, trayStatus); + } + + public synchronized void setHeatModuleStateHeating(HeatModuleCode heatModuleId, boolean heating) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + boolean oldValue = heatModuleState.isHeating(); + heatModuleState.setHeating(heating); + support.firePropertyChange("setHeatModuleStateHeating", oldValue, heating); + } + public synchronized void setHeatModuleStateDrying(HeatModuleCode heatModuleId, boolean drying) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + boolean oldValue = heatModuleState.isDrying(); + heatModuleState.setDrying(drying); + support.firePropertyChange("setHeatModuleStateDrying", oldValue, drying); + } + public synchronized void setHeatModuleStateAnnealing(HeatModuleCode heatModuleId, boolean annealing) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + boolean oldValue = heatModuleState.isAnnealing(); + heatModuleState.setAnnealing(annealing); + support.firePropertyChange("setHeatModuleStateHeating", oldValue, annealing); + } + + public synchronized void setHeatModuleStateFanOpen(HeatModuleCode heatModuleId, boolean fanOpen) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + boolean oldValue = heatModuleState.isFanOpen(); + heatModuleState.setFanOpen(fanOpen); + support.firePropertyChange("setHeatModuleStateFanOpen", oldValue, fanOpen); + } + + + public synchronized void setHeatModuleStatePreSetTemperature(HeatModuleCode heatModuleId, SetTargetTemperatureVO setTargetTemperatureVO) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + heatModuleState.setHeatTemperature(setTargetTemperatureVO.getHeatTemperature()); + heatModuleState.setAnnealTemperature(setTargetTemperatureVO.getAnnealTemperature()); + heatModuleState.setDryTemperature(setTargetTemperatureVO.getDryTemperature()); + //support.firePropertyChange("setHeatModuleStateTargetTemperature", oldValue, targetTemperature); + } + public synchronized void setHeatModuleStateTargetTemperature(HeatModuleCode heatModuleId, double targetTemperature) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + double oldValue = heatModuleState.getTargetTemperature(); + heatModuleState.setTargetTemperature(targetTemperature); + support.firePropertyChange("setHeatModuleStateTargetTemperature", oldValue, targetTemperature); + } + + public synchronized void setHeatModuleStateTemperature(HeatModuleCode heatModuleId, double temperature) { + HeatModuleState heatModuleState = null; + for (HeatModuleState t : deviceState.getHeatModule()) { + if (heatModuleId.equals(t.getModuleCode())) { + heatModuleState = t; + break; + } + } + assert heatModuleState != null; + Double oldValue = heatModuleState.getTemperature(); + heatModuleState.setTemperature(temperature); + support.firePropertyChange("setHeatModuleStateTemperature", oldValue, temperature); + } + + public synchronized void setSolutionModuleStateIdle(boolean idle) { + boolean oldValue = deviceState.getSolutionModule().isIdle(); + deviceState.getSolutionModule().setIdle(idle); + support.firePropertyChange("setSolutionModuleStateIdle", oldValue, idle); + } + + + public synchronized void setSolutionModuleStateTrayStatus(int trayStatus) { + int oldValue = deviceState.getSolutionModule().getTrayStatus(); + deviceState.getSolutionModule().setTrayStatus(trayStatus); + support.firePropertyChange("setSolutionModuleStateTrayStatus", oldValue, trayStatus); + } + + public synchronized void setSolutionModuleStatePumping(boolean pumping) { + boolean oldValue = deviceState.getSolutionModule().isPumping(); + deviceState.getSolutionModule().setPumping(pumping); + support.firePropertyChange("setSolutionModuleStatePumping", oldValue, pumping); + } + + public synchronized void setSolutionContainerStateEmpty(ContainerCode containerCode, boolean empty) { + SolutionContainerState solutionContainerState = null; + for (SolutionContainerState t : deviceState.getSolutionModule().getSolutionContainer()) { + if (containerCode.equals(t.getContainerCode())) { + solutionContainerState = t; + break; + } + } + assert solutionContainerState != null; + boolean oldValue = solutionContainerState.isEmpty(); + solutionContainerState.setEmpty(empty); + support.firePropertyChange("setSolutionContainerStateEmpty", oldValue, empty); + } + + public synchronized void setSolutionContainerStateFull(ContainerCode containerCode, boolean full) { + SolutionContainerState solutionContainerState = null; + for (SolutionContainerState t : deviceState.getSolutionModule().getSolutionContainer()) { + if (containerCode.equals(t.getContainerCode())) { + solutionContainerState = t; + break; + } + } + assert solutionContainerState != null; + boolean oldValue = solutionContainerState.isFull(); + solutionContainerState.setFull(full); + support.firePropertyChange("setSolutionContainerStateFull", oldValue, full); + } + + public synchronized void setTrayStateHeatModuleId(String trayUUID, HeatModuleCode heatModuleId) { + TrayState foundTrayState = null; + for (TrayState t : deviceState.getTray()) { + if (trayUUID.equals(t.getUuid())) { + foundTrayState = t; + break; + } + } + assert foundTrayState != null; + HeatModuleCode oldValue = foundTrayState.getHeatModuleId(); + foundTrayState.setHeatModuleId(heatModuleId); + support.firePropertyChange("setTrayStateHeatModuleId", oldValue, heatModuleId); + } + + public synchronized void setTrayStateInSolutionModule(String trayUUID, boolean inSolutionModule) { + TrayState foundTrayState = null; + for (TrayState t : deviceState.getTray()) { + if (trayUUID.equals(t.getUuid())) { + foundTrayState = t; + break; + } + } + assert foundTrayState != null; + boolean oldValue = foundTrayState.isInSolutionModule(); + foundTrayState.setInSolutionModule(inSolutionModule); + support.firePropertyChange("traySetInSolutionModule", oldValue, inSolutionModule); + } + + public synchronized void setTrayStateInHeatModule(String trayUUID, boolean inHeatModule) { + TrayState foundTrayState = null; + for (TrayState t : deviceState.getTray()) { + if (trayUUID.equals(t.getUuid())) { + foundTrayState = t; + break; + } + } + assert foundTrayState != null; + boolean oldValue = foundTrayState.isInHeatModule(); + foundTrayState.setInHeatModule(inHeatModule); + support.firePropertyChange("setTrayStateInHeatModule", oldValue, inHeatModule); + } + + + public synchronized void setTrayStateTubes(String trayUUID, TubeState[] tubes) { + TrayState foundTrayState = null; + for (TrayState t : deviceState.getTray()) { + if (trayUUID.equals(t.getUuid())) { + foundTrayState = t; + break; + } + } + assert foundTrayState != null; + TubeState[] oldValue = foundTrayState.getTubes(); + foundTrayState.setTubes(tubes); + support.firePropertyChange("setTrayStateTubes", oldValue, tubes); + } + + public synchronized void setTubeStateExists(String trayUUID, int tubeNum, boolean exists) { + TrayState foundTrayState = null; + for (TrayState t : deviceState.getTray()) { + if (trayUUID.equals(t.getUuid())) { + foundTrayState = t; + break; + } + } + assert foundTrayState != null; + TubeState tubeState = foundTrayState.getTubes()[tubeNum]; + boolean oldValue = tubeState.isExists(); + tubeState.setExists(exists); + support.firePropertyChange("setTubeStateExists", oldValue, exists); + } + + public synchronized void setTubeStateAddSolution(String trayUUID, int tubeNum, boolean addSolution) { + TrayState foundTrayState = null; + for (TrayState t : deviceState.getTray()) { + if (trayUUID.equals(t.getUuid())) { + foundTrayState = t; + break; + } + } + assert foundTrayState != null; + TubeState tubeState = foundTrayState.getTubes()[tubeNum]; + boolean oldValue = tubeState.isAddSolution(); + tubeState.setAddSolution(addSolution); + support.firePropertyChange("setTubeStateAddSolution", oldValue, addSolution); + } + + public synchronized void setCurrentUser(User currentUser) { + User oldValue = deviceState.getCurrentUser(); + deviceState.setCurrentUser(currentUser); + support.firePropertyChange("setCurrentUser", oldValue, currentUser); + } + + public synchronized void setCurrentTasks(Tasks currentTasks) { + User oldValue = deviceState.getCurrentUser(); + deviceState.setCurrentTasks(currentTasks); + support.firePropertyChange("setCurrentTasks", oldValue, currentTasks); + } + + + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/GantryArmService.java b/src/main/java/com/iflytop/sgs/app/service/GantryArmService.java new file mode 100644 index 0000000..d15dcc7 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/GantryArmService.java @@ -0,0 +1,62 @@ +package com.iflytop.sgs.app.service; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +@Service +@RequiredArgsConstructor +public class GantryArmService { + private final Lock liquidLock = new ReentrantLock(); + private final Condition liquidIdleCondition = liquidLock.newCondition(); + private final DeviceStateService deviceStateService; + + /** + * 等待加液区空闲 + */ + public void waitLiquidIdle() { + liquidLock.lock(); + try { + while (!deviceStateService.getDeviceState().getSolutionModule().isIdle()) { + liquidIdleCondition.await(); + } + } catch (InterruptedException e) { + throw new RuntimeException("等待加液区空闲错误", e); + } finally { + liquidLock.unlock(); + } + } + + /** + * 锁定加液区 + */ + public void setLiquidIdleFalse() { + liquidLock.lock(); + try { + deviceStateService.setSolutionModuleStateIdle(false); + liquidIdleCondition.signalAll(); // 唤醒所有等待的线程 + } finally { + liquidLock.unlock(); + } + } + + /** + * 释放加液区 + */ + public void setLiquidIdleTrue() { + liquidLock.lock(); + try { + deviceStateService.setSolutionModuleStateIdle(true); + liquidIdleCondition.signalAll(); // 唤醒所有等待的线程 + } finally { + liquidLock.unlock(); + } + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/HeatModuleService.java b/src/main/java/com/iflytop/sgs/app/service/HeatModuleService.java new file mode 100644 index 0000000..a82c481 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/HeatModuleService.java @@ -0,0 +1,18 @@ +package com.iflytop.sgs.app.service; + +import com.iflytop.sgs.app.model.vo.SetTargetTemperatureVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 加热模块服务 + */ +@Service +@RequiredArgsConstructor +public class HeatModuleService { + private final DeviceStateService deviceStateService; + + public void setTargetTemperature(SetTargetTemperatureVO setTargetTemperatureVO) { + deviceStateService.setHeatModuleStatePreSetTemperature(setTargetTemperatureVO.getModuleCode(), setTargetTemperatureVO); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/OresService.java b/src/main/java/com/iflytop/sgs/app/service/OresService.java new file mode 100644 index 0000000..60b8009 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/OresService.java @@ -0,0 +1,102 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.CraftsMapper; +import com.iflytop.sgs.app.mapper.OresMapper; +import com.iflytop.sgs.app.model.entity.Crafts; +import com.iflytop.sgs.app.model.entity.Ores; +import com.iflytop.sgs.app.model.vo.OresCraftsListVO; +import com.iflytop.sgs.common.base.BasePageQuery; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 矿石业务实现类 + */ +@Service +@RequiredArgsConstructor +public class OresService extends ServiceImpl { + + private final CraftsMapper craftsMapper; + + + public IPage getPage(BasePageQuery pageQuery) { + // 构建分页对象 + Page oresPage = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + + // 分页查询矿石数据 + IPage oresIPage = this.baseMapper.selectPage(oresPage, new QueryWrapper<>()); + + // 获取矿石ID列表 + List oresIds = oresIPage.getRecords().stream() + .map(Ores::getId) + .collect(Collectors.toList()); + + // 查询对应的工艺数据 + QueryWrapper craftsQueryWrapper = new QueryWrapper<>(); + craftsQueryWrapper.in("ores_id", oresIds); + List craftsList = craftsMapper.selectList(craftsQueryWrapper); + + // 将工艺数据按矿石ID分组 + Map> craftsMap = craftsList.stream() + .collect(Collectors.groupingBy(Crafts::getOresId)); + + // 转换成 OresCraftsListVO + List oresCraftsList = oresIPage.getRecords().stream().map(ores -> { + OresCraftsListVO oresCraftsListVO = new OresCraftsListVO(); + oresCraftsListVO.setId(ores.getId()); + oresCraftsListVO.setOresName(ores.getName()); + oresCraftsListVO.setCreateTime(ores.getCreateTime()); + oresCraftsListVO.setUpdateTime(ores.getUpdateTime()); + + // 设置该矿石的工艺列表 + List crafts = craftsMap.get(ores.getId()); + if (crafts != null) { + crafts.forEach(c -> c.setSteps(null)); + } + oresCraftsListVO.setCraftsList(crafts); + + return oresCraftsListVO; + }).collect(Collectors.toList()); + + // 将转换后的分页数据放入新的分页对象 + Page resultPage = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + resultPage.setTotal(oresIPage.getTotal()); + resultPage.setRecords(oresCraftsList); + + return resultPage; + + } + + + public Ores findByName(String name) { + return this.getOne(new LambdaQueryWrapper().eq(Ores::getName, name)); + } + + + public boolean addOres(Ores ores) { + return this.save(ores); + } + + + public boolean updateOres(Ores ores) { + return this.updateById(ores); + } + + + public boolean deleteOres(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .collect(Collectors.toList()); + return this.removeByIds(ids); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/SelfTestService.java b/src/main/java/com/iflytop/sgs/app/service/SelfTestService.java new file mode 100644 index 0000000..697dd1a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/SelfTestService.java @@ -0,0 +1,16 @@ +package com.iflytop.sgs.app.service; + +import com.iflytop.sgs.app.core.device.SelfTestState; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SelfTestService { + @Getter + private final SelfTestState selfTestState = new SelfTestState(); + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/SolutionsService.java b/src/main/java/com/iflytop/sgs/app/service/SolutionsService.java new file mode 100644 index 0000000..f4596f0 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/SolutionsService.java @@ -0,0 +1,39 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.SolutionsMapper; +import com.iflytop.sgs.app.model.entity.Solutions; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 溶液业务实现类 + */ +@Service +@RequiredArgsConstructor +public class SolutionsService extends ServiceImpl { + + public Solutions findByName(String name) { + return this.getOne(new LambdaQueryWrapper().eq(Solutions::getName, name)); + } + + public boolean addSolutions(Solutions solutions) { + return this.save(solutions); + } + + public boolean updateSolutions(Solutions solutions) { + return this.updateById(solutions); + } + + public boolean deleteSolutions(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .collect(Collectors.toList()); + return this.removeByIds(ids); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/StepCommandService.java b/src/main/java/com/iflytop/sgs/app/service/StepCommandService.java new file mode 100644 index 0000000..52d7803 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/StepCommandService.java @@ -0,0 +1,84 @@ +package com.iflytop.sgs.app.service; + +import com.iflytop.sgs.app.model.bo.Point3D; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import com.iflytop.sgs.hardware.drivers.LeisaiServoDriver; +import com.iflytop.sgs.hardware.drivers.LiquidDistributionArmDriver; +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.MiniServoDriver; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.StepMotorCtrlDriver; +import com.iflytop.sgs.hardware.service.GDDeviceStatusService; +import com.iflytop.sgs.hardware.type.Servo.LeisaiServoMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 单步运动调试服务 + */ +@Service +@RequiredArgsConstructor +public class StepCommandService { + private final DeviceCommandUtilService deviceCommandUtilService; + private final StepMotorCtrlDriver stepMotorCtrlDriver; + private final MiniServoDriver miniServoDriver; + private final LiquidDistributionArmDriver liquidDistributionArmDriver; + private final LeisaiServoDriver leisaiServoDriver; + private final GDDeviceStatusService gdDeviceStatusService; + + public void gantryToPoint3D(Point3D point3D) throws Exception { + deviceCommandUtilService.gantryMove(point3D); + } + + /** + * 获取当前龙门架机械臂所在点位 + */ + public Point3D getGantryPoint() throws Exception { + double x = gdDeviceStatusService.getXYServoPosition(LeisaiServoMId.MainXSV); + double y = gdDeviceStatusService.getXYServoPosition(LeisaiServoMId.MainYSV); + double z = gdDeviceStatusService.getStepMotorPostion(StepMotorMId.HBOT_Z_MOTOR_MID); + return new Point3D(x, y, z); + } + + public void enableAll() throws Exception { + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.DOOR_MOTOR_MID, 1); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HBOT_Z_MOTOR_MID, 1); + + leisaiServoDriver.enable(LeisaiServoMId.MainXSV, 1); + //todo 增加吸液区电机的失能 + + } + + + public void disabilityAll() throws Exception { + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.DOOR_MOTOR_MID, 0); + stepMotorCtrlDriver.stepMotorEnable(StepMotorMId.HBOT_Z_MOTOR_MID, 0); + leisaiServoDriver.enable(LeisaiServoMId.MainXSV, 0); + //todo 增加吸液区电机的失能 + } + + public void stopAll() throws Exception { + leisaiServoDriver.moduleStop(LeisaiServoMId.MainXSV); + stepMotorCtrlDriver.stepMotorStop(StepMotorMId.HBOT_Z_MOTOR_MID); + + stepMotorCtrlDriver.stepMotorStop(StepMotorMId.DOOR_MOTOR_MID); + //todo 增加吸液区电机的失能 + + } + + public double getCapMotorPostion() throws Exception { + return gdDeviceStatusService.getStepMotorPostion(StepMotorMId.TRAY_MOTOR_MID); + } + + public double getHeatMotorPostion(String heatId) throws Exception { + HeatModuleCode heatModuleCode = HeatModuleCode.valueOf(heatId); + StepMotorMId heatMotorId; + switch (heatModuleCode) { + case HeatModuleCode.heat_module_01 -> heatMotorId = StepMotorMId.HEATER_1_MOTOR_MID; + case HeatModuleCode.heat_module_02 -> heatMotorId = StepMotorMId.HEATER_2_MOTOR_MID; + case HeatModuleCode.heat_module_03 -> heatMotorId = StepMotorMId.HEATER_3_MOTOR_MID; + case HeatModuleCode.heat_module_04 -> heatMotorId = StepMotorMId.HEATER_4_MOTOR_MID; + default -> throw new IllegalStateException("Unexpected value: " + heatModuleCode); + } + return gdDeviceStatusService.getStepMotorPostion(heatMotorId); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/SystemConfigService.java b/src/main/java/com/iflytop/sgs/app/service/SystemConfigService.java new file mode 100644 index 0000000..7296807 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/SystemConfigService.java @@ -0,0 +1,83 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.SystemConfigMapper; +import com.iflytop.sgs.app.model.entity.SystemConfig; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +/** + * 系统配置业务实现类 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SystemConfigService extends ServiceImpl { + + private final SystemConfigMapper systemConfigMapper; + private final DeviceStateService deviceStateService; + + + public void setInitComplete(boolean initComplete) { + deviceStateService.setInitComplete(initComplete); + } + + /** + * 设置系统日期和时间。 + * + * @param datetime 日期时间字符串,格式为 "yyyy-MM-dd HH:mm:ss" + */ + public void setDatetime(String datetime) { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("windows")) { + log.info("检测到 Windows 操作系统,跳过系统时间更新。接收到的日期时间:{}", datetime); + return; + } + try { + // 禁用自动时间同步 + ProcessBuilder pbNtp = new ProcessBuilder("timedatectl", "set-ntp", "false").redirectErrorStream(true); + Process procNtp = pbNtp.start(); + StringBuilder ntpOut = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(procNtp.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + ntpOut.append(line).append("\n"); + } + } + int ntpExit = procNtp.waitFor(); + if (ntpExit != 0) { + log.error("禁用 NTP 失败(退出代码={}):\n{}", ntpExit, ntpOut.toString().trim()); + } else { + log.info("自动时间同步已禁用"); + } + } catch (Exception e) { + log.error("禁用 NTP 时发生错误", e); + } + + // 设置系统时间 + ProcessBuilder pbTime = new ProcessBuilder("timedatectl", "set-time", datetime).redirectErrorStream(true); + try { + Process procTime = pbTime.start(); + StringBuilder timeOut = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(procTime.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + timeOut.append(line).append("\n"); + } + } + int timeExit = procTime.waitFor(); + if (timeExit != 0) { + log.error("更新系统时间失败(退出代码={}):\n{}", timeExit, timeOut.toString().trim()); + return; + } + log.info("系统时间已成功更新为 {}", datetime); + } catch (Exception e) { + log.error("执行系统时间更新命令时发生错误", e); + } + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/SystemLogService.java b/src/main/java/com/iflytop/sgs/app/service/SystemLogService.java new file mode 100644 index 0000000..a5d6d9e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/SystemLogService.java @@ -0,0 +1,28 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.SystemLogMapper; +import com.iflytop.sgs.app.model.entity.SystemLog; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 系统日志业务实现类 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SystemLogService extends ServiceImpl { + + public boolean deleteLog(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .collect(Collectors.toList()); + return this.removeByIds(ids); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/service/TaskStepsService.java b/src/main/java/com/iflytop/sgs/app/service/TaskStepsService.java new file mode 100644 index 0000000..3803582 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/TaskStepsService.java @@ -0,0 +1,16 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.TaskStepsMapper; +import com.iflytop.sgs.app.model.entity.TaskSteps; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 实验业务实现类 + */ +@Service +@RequiredArgsConstructor +public class TaskStepsService extends ServiceImpl { + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/TasksService.java b/src/main/java/com/iflytop/sgs/app/service/TasksService.java new file mode 100644 index 0000000..cb789db --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/TasksService.java @@ -0,0 +1,98 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.TasksMapper; +import com.iflytop.sgs.app.model.entity.TaskSteps; +import com.iflytop.sgs.app.model.entity.Tasks; +import com.iflytop.sgs.app.model.vo.TaskStepsVO; +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.result.ResultCode; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 实验业务实现类 + */ +@Service +@RequiredArgsConstructor +public class TasksService extends ServiceImpl { + private final TaskStepsService taskStepsService; + private final DeviceStateService deviceStateService; + + @PostConstruct + public void init() { + List tasksList = this.list(new LambdaQueryWrapper().eq(Tasks::getStatus, 1)); + if(!tasksList.isEmpty()){ + deviceStateService.getDeviceState().setCurrentTasks(tasksList.getFirst()); + } + } + + public TaskStepsVO detail(Long id) { + Tasks tasks = this.getById(id); + List taskSteps = taskStepsService.list(new LambdaQueryWrapper<>(TaskSteps.class).eq(TaskSteps::getTaskId, id)); + TaskStepsVO taskStepsVO = new TaskStepsVO(); + taskStepsVO.setId(tasks.getId()); + taskStepsVO.setName(tasks.getName()); + taskStepsVO.setStartTime(tasks.getStartTime()); + taskStepsVO.setEndTime(tasks.getEndTime()); + taskStepsVO.setCreateUser(tasks.getCreateUser()); + taskStepsVO.setStatus(tasks.getStatus()); + taskStepsVO.setIsDeleted(tasks.getIsDeleted()); + taskStepsVO.setSteps(taskSteps); + return taskStepsVO; + } + + public Tasks start(String taskName) { + Tasks tasks = this.getOne(new LambdaQueryWrapper().eq(Tasks::getName, taskName)); + if (tasks != null) { + throw new AppException(ResultCode.DATA_ALREADY_EXISTS); + } + tasks = new Tasks(); + tasks.setName(taskName); + tasks.setCreateUser(deviceStateService.getDeviceState().getCurrentUser().getId()); + tasks.setStatus(1); + tasks.setIsDeleted(0); + tasks.setStartTime(LocalDateTime.now()); + this.save(tasks); + TaskSteps taskSteps = new TaskSteps(); + taskSteps.setTaskId(tasks.getId()); + taskSteps.setStepDescription("开始实验"); + taskStepsService.save(taskSteps); + deviceStateService.setCurrentTasks(tasks); + return tasks; + } + + public Tasks getCurrent() { + return deviceStateService.getDeviceState().getCurrentTasks(); + } + + public boolean deleteTasks(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .toList(); + for (Long id : ids) { + List taskSteps = taskStepsService.list(new LambdaQueryWrapper<>(TaskSteps.class).eq(TaskSteps::getTaskId, id)); + taskStepsService.removeByIds(taskSteps.stream().map(TaskSteps::getId).collect(Collectors.toList())); + this.removeById(id); + } + return true; + } + + public void stop() { + Tasks tasks = deviceStateService.getDeviceState().getCurrentTasks(); + if (tasks != null) { + tasks.setStatus(2); + tasks.setEndTime(LocalDateTime.now()); + this.updateById(tasks); + deviceStateService.setCurrentTasks(null); + } + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/TrayService.java b/src/main/java/com/iflytop/sgs/app/service/TrayService.java new file mode 100644 index 0000000..573104b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/TrayService.java @@ -0,0 +1,47 @@ +package com.iflytop.sgs.app.service; + +import com.iflytop.sgs.app.core.device.TrayState; +import com.iflytop.sgs.app.model.vo.SetTrayTubeVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 托盘控制实现类 + */ +@Service +@RequiredArgsConstructor +public class TrayService { + private final DeviceStateService deviceStateService; + + /** + * 放入了新托盘 + */ + public synchronized TrayState trayIn() { + TrayState trayState = new TrayState(); + trayState.setInSolutionModule(true); + deviceStateService.getDeviceState().getTray().add(trayState); + deviceStateService.setSolutionModuleStateTrayStatus(1); + return trayState; + } + + /** + * 拿走了托盘 + */ + public synchronized void trayOut() { + List trayList = deviceStateService.getDeviceState().getTray(); + trayList.removeIf(TrayState::isInSolutionModule); + deviceStateService.setSolutionModuleStateTrayStatus(0); + } + + /** + * 设置托盘试管 + */ + public synchronized void setTrayTube(SetTrayTubeVO setTrayTubeVO) { + TrayState trayState = deviceStateService.getTrayState(setTrayTubeVO.getTrayUuid()); + trayState.setTubes(setTrayTubeVO.getTubes()); + } + + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/UserService.java b/src/main/java/com/iflytop/sgs/app/service/UserService.java new file mode 100644 index 0000000..e61e95e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/UserService.java @@ -0,0 +1,35 @@ +package com.iflytop.sgs.app.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.sgs.app.mapper.UserMapper; +import com.iflytop.sgs.app.model.entity.User; +import com.iflytop.sgs.common.enums.data.Deleted; +import com.iflytop.sgs.common.enums.data.FixedUser; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; + +/** + * 用户业务实现类 + */ +@Service +@RequiredArgsConstructor +public class UserService extends ServiceImpl { + + public boolean deleteUser(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .toList(); + for (Long id : ids) { + User user = this.getById(id); + if (user != null && user.getFixedUser() != FixedUser.ENABLE) { + user.setDeleted(Deleted.ENABLE); + return this.save(user); + } + } + return true; + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/WebSocketService.java b/src/main/java/com/iflytop/sgs/app/service/WebSocketService.java new file mode 100644 index 0000000..1907bf0 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/WebSocketService.java @@ -0,0 +1,37 @@ +package com.iflytop.sgs.app.service; + +import cn.hutool.json.JSONUtil; +import com.iflytop.sgs.app.model.bo.Notification; +import com.iflytop.sgs.app.model.dto.WebsocketResult; +import com.iflytop.sgs.app.ws.server.WebSocketServer; +import com.iflytop.sgs.common.constant.WebSocketMessageType; +import org.springframework.stereotype.Service; + +@Service +public class WebSocketService { + + public void push(String type, Object data) { + WebsocketResult websocketResult = new WebsocketResult(); + websocketResult.setType(type); + websocketResult.setData(data); + WebSocketServer.sendMessageToClients(JSONUtil.toJsonStr(websocketResult)); + } + + public void pushCraftsDebug(Object data) { + push(WebSocketMessageType.CRAFTS_DEBUG, data); + } + + public void pushDebug(Object data) { + push(WebSocketMessageType.CMD_DEBUG, data); + } + + public void pushCMDResponse(Object data) { + push(WebSocketMessageType.CMD_RESPONSE, data); + } + + + public void pushNotification(Notification notification) { + push("notification", notification); + } + +} diff --git a/src/main/java/com/iflytop/sgs/app/service/scheduled/FetchTemperatureScheduledTask.java b/src/main/java/com/iflytop/sgs/app/service/scheduled/FetchTemperatureScheduledTask.java new file mode 100644 index 0000000..46a19dd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/service/scheduled/FetchTemperatureScheduledTask.java @@ -0,0 +1,50 @@ +package com.iflytop.sgs.app.service.scheduled; + +import com.iflytop.sgs.app.core.device.HeatModuleState; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.common.enums.HeatModuleCode; +import com.iflytop.sgs.hardware.service.GDDeviceStatusService; +import com.iflytop.sgs.hardware.type.driver.HeaterRodSlavedId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FetchTemperatureScheduledTask { + private final GDDeviceStatusService gdDeviceStatusService; + private final DeviceStateService deviceStateService; + + @Scheduled(fixedRate = 30000) + public void fetchTemperature() { + try { + if (!deviceStateService.getDeviceState().isVirtual()) { + Double heatModule01Temperature = gdDeviceStatusService.getHeaterRodTemperature(HeaterRodSlavedId.HEATER_ROD1_ID); + deviceStateService.setHeatModuleStateTemperature(HeatModuleCode.heat_module_01, heatModule01Temperature); + + Double heatModule02Temperature = gdDeviceStatusService.getHeaterRodTemperature(HeaterRodSlavedId.HEATER_ROD2_ID); + deviceStateService.setHeatModuleStateTemperature(HeatModuleCode.heat_module_02, heatModule02Temperature); + + Double heatModule03Temperature = gdDeviceStatusService.getHeaterRodTemperature(HeaterRodSlavedId.HEATER_ROD3_ID); + deviceStateService.setHeatModuleStateTemperature(HeatModuleCode.heat_module_03, heatModule03Temperature); + + Double heatModule04Temperature = gdDeviceStatusService.getHeaterRodTemperature(HeaterRodSlavedId.HEATER_ROD4_ID); + deviceStateService.setHeatModuleStateTemperature(HeatModuleCode.heat_module_04, heatModule04Temperature); + } else { + List heatModuleStateList = deviceStateService.getDeviceState().getHeatModule(); + for (HeatModuleState heatModuleState : heatModuleStateList) { + if (heatModuleState.isHeating()) { + deviceStateService.setHeatModuleStateTemperature(heatModuleState.getModuleCode(), 999); + } + } + } + } catch (Exception e) { + log.error("定时采集加热棒温度错误", e); + } + + } +} diff --git a/src/main/java/com/iflytop/sgs/app/ws/client/DeviceEmergencyStopClient.java b/src/main/java/com/iflytop/sgs/app/ws/client/DeviceEmergencyStopClient.java new file mode 100644 index 0000000..7c9dd6b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/ws/client/DeviceEmergencyStopClient.java @@ -0,0 +1,61 @@ +package com.iflytop.sgs.app.ws.client; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.enums.ReadyState; +import org.java_websocket.handshake.ServerHandshake; +import org.springframework.scheduling.annotation.Scheduled; + +import java.net.URI; +import java.util.function.Consumer; + +/** + * WebsocketCliet监听设备发送的急停消息,实现设备的急停 + */ +@Slf4j +public class DeviceEmergencyStopClient extends WebSocketClient { + private Consumer consumer; + + public DeviceEmergencyStopClient(URI serverUri, Consumer consumer) { + super(serverUri); + this.consumer = consumer; + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + log.info("device_status_listener_websocket connect success"); + } + + @Override + public void onMessage(String message) { + JSONObject jsonObject = JSONUtil.parseObj(message); + consumer.accept(jsonObject); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + log.info("device_status_listener_websocket lost connection..."); + } + + @Override + public void onError(Exception ex) { + log.info("device_status_listener_websocket on error"); + } + + //失去连接后重新连接 10s轮询 + @Scheduled(fixedRate = 10000) + private void autoConnect() { + if (!isOpen()) { + if (getReadyState().equals(ReadyState.NOT_YET_CONNECTED)) { + try { + connect(); + } catch (IllegalStateException ignored) { + } + } else if (getReadyState().equals(ReadyState.CLOSED)) { + reconnect(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/app/ws/client/DeviceEmergencyStopConfig.java b/src/main/java/com/iflytop/sgs/app/ws/client/DeviceEmergencyStopConfig.java new file mode 100644 index 0000000..025a42d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/ws/client/DeviceEmergencyStopConfig.java @@ -0,0 +1,49 @@ +package com.iflytop.sgs.app.ws.client; + +import com.iflytop.sgs.app.core.CommandPoolManager; +import com.iflytop.sgs.app.service.DeviceStateService; +import com.iflytop.sgs.app.service.StepCommandService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.net.URI; + +@Slf4j +@Configuration +@RequiredArgsConstructor +public class DeviceEmergencyStopConfig { + private final StepCommandService stepCommandService; + private final DeviceStateService deviceStateService; + private final CommandPoolManager commandPoolManager; + + @Value("${iflytophald.ip}") + String ip; + @Value("${iflytophald.datach.port}") + Integer port; + + + @Bean + DeviceEmergencyStopClient stopAllClientService() { + URI uri = URI.create(String.format("ws://%s:%d/%s", ip, port, "key")); + return new DeviceEmergencyStopClient(uri, s -> { + if (s.get("event").equals("press")) { + log.info("触发急停{}", s); + try { + commandPoolManager.forceShutdownAll();//强制终止现在运行的所有指令 + stepCommandService.stopAll(); + deviceStateService.setEmergencyStop(true); + } catch (Exception e) { + log.error("设备急停失败:{}", e.getMessage()); + throw new RuntimeException(e); + } + } else if (s.get("event").equals("release")) { + deviceStateService.setEmergencyStop(false); + log.info("解除急停{}", s); + } + }); + + } +} diff --git a/src/main/java/com/iflytop/sgs/app/ws/server/WebSocketServer.java b/src/main/java/com/iflytop/sgs/app/ws/server/WebSocketServer.java new file mode 100644 index 0000000..acaf1c5 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/ws/server/WebSocketServer.java @@ -0,0 +1,52 @@ +package com.iflytop.sgs.app.ws.server; + +import jakarta.websocket.*; +import jakarta.websocket.server.ServerEndpoint; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@Slf4j +@ServerEndpoint("/ws") +@Component +public class WebSocketServer { + + private static final Set sessions = Collections.synchronizedSet(new HashSet<>()); + + public static void sendMessageToClients(String message) { + synchronized (sessions) { + for (Session session : sessions) { + try { + session.getBasicRemote().sendText(message); + } catch (Exception e) { + log.error("发送给客户端失败 sessionId={}", session.getId(), e); + } + } + } + } + + @OnOpen + public void onOpen(Session session) { + sessions.add(session); + log.info("新连接加入,sessionId={}", session.getId()); + } + + @OnMessage + public void onMessage(String message, Session session) { +// log.info("收到消息 sessionId={},内容:{}", session.getId(), message); + } + + @OnClose + public void onClose(Session session) { + sessions.remove(session); + log.info("连接已关闭,sessionId={}", session.getId()); + } + + @OnError + public void onError(Session session, Throwable error) { + log.error("发生错误,sessionId={}", session.getId(), error); + } +} diff --git a/src/main/java/com/iflytop/sgs/app/ws/server/WebSocketServerConfig.java b/src/main/java/com/iflytop/sgs/app/ws/server/WebSocketServerConfig.java new file mode 100644 index 0000000..5e38559 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/app/ws/server/WebSocketServerConfig.java @@ -0,0 +1,14 @@ +package com.iflytop.sgs.app.ws.server; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WebSocketServerConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/src/main/java/com/iflytop/sgs/common/annotation/CheckedRunnable.java b/src/main/java/com/iflytop/sgs/common/annotation/CheckedRunnable.java new file mode 100644 index 0000000..d5fbf34 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/annotation/CheckedRunnable.java @@ -0,0 +1,6 @@ +package com.iflytop.sgs.common.annotation; + +@FunctionalInterface +public interface CheckedRunnable { + void run() throws Exception; +} diff --git a/src/main/java/com/iflytop/sgs/common/annotation/CommandMapping.java b/src/main/java/com/iflytop/sgs/common/annotation/CommandMapping.java new file mode 100644 index 0000000..86c6d0a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/annotation/CommandMapping.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface CommandMapping { + String value(); +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/common/base/BaseEntity.java b/src/main/java/com/iflytop/sgs/common/base/BaseEntity.java new file mode 100644 index 0000000..6257148 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/base/BaseEntity.java @@ -0,0 +1,44 @@ +package com.iflytop.sgs.common.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 基础实体类 + * 实体类的基类,包含了实体类的公共属性,如创建时间、更新时间、逻辑删除标识等

+ */ +@Data +public class BaseEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/iflytop/sgs/common/base/BasePageQuery.java b/src/main/java/com/iflytop/sgs/common/base/BasePageQuery.java new file mode 100644 index 0000000..30b5c95 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/base/BasePageQuery.java @@ -0,0 +1,26 @@ +package com.iflytop.sgs.common.base; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 基础分页请求对象 + */ +@Data +@Schema +public class BasePageQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "页码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private int pageNum = 1; + + @Schema(description = "每页记录数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private int pageSize = 10; + + +} diff --git a/src/main/java/com/iflytop/sgs/common/base/IBaseEnum.java b/src/main/java/com/iflytop/sgs/common/base/IBaseEnum.java new file mode 100644 index 0000000..a81756e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/base/IBaseEnum.java @@ -0,0 +1,84 @@ +package com.iflytop.sgs.common.base; + + +import cn.hutool.core.util.ObjectUtil; + +import java.util.EnumSet; +import java.util.Objects; + +/** + * 枚举通用接口 + */ +public interface IBaseEnum { + + /** + * 根据值获取枚举 + * + * @param value + * @param clazz + * @param 枚举 + * @return + */ + static & IBaseEnum> E getEnumByValue(Object value, Class clazz) { + Objects.requireNonNull(value); + EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举 + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getValue(), value)) + .findFirst() + .orElse(null); + return matchEnum; + } + + /** + * 根据文本标签获取值 + * + * @param value + * @param clazz + * @param + * @return + */ + static & IBaseEnum> String getLabelByValue(Object value, Class clazz) { + Objects.requireNonNull(value); + EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举 + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getValue(), value)) + .findFirst() + .orElse(null); + + String label = null; + if (matchEnum != null) { + label = matchEnum.getLabel(); + } + return label; + } + + /** + * 根据文本标签获取值 + * + * @param label + * @param clazz + * @param + * @return + */ + static & IBaseEnum> Object getValueByLabel(String label, Class clazz) { + Objects.requireNonNull(label); + EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举 + String finalLabel = label; + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getLabel(), finalLabel)) + .findFirst() + .orElse(null); + + Object value = null; + if (matchEnum != null) { + value = matchEnum.getValue(); + } + return value; + } + + T getValue(); + + String getLabel(); + + +} diff --git a/src/main/java/com/iflytop/sgs/common/cmd/CommandFuture.java b/src/main/java/com/iflytop/sgs/common/cmd/CommandFuture.java new file mode 100644 index 0000000..0fda389 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/cmd/CommandFuture.java @@ -0,0 +1,55 @@ +package com.iflytop.sgs.common.cmd; + +import cn.hutool.json.JSONObject; +import lombok.Getter; +import lombok.Setter; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +@Setter +@Getter +public class CommandFuture { + /** + * 用于保存response反馈 + */ + private CompletableFuture responseFuture = new CompletableFuture<>(); + + private long startSendTime; + private long endSendTime; + + /** + * 业务指令id + */ + private String cmdId; + /** + * 业务指令code + */ + private String cmdCode; + /** + * 设备指令 + */ + private DeviceCommandBundle deviceCommandBundle; + + /** + * 完成response + */ + public void completeResponse(JSONObject result) { + responseFuture.complete(result); + } + + /** + * 异常完成response + */ + public void completeResponseExceptionally(Throwable ex) { + responseFuture.completeExceptionally(ex); + } + + /** + * 获取response的json + */ + public JSONObject getResponseResult() throws ExecutionException, InterruptedException { + return responseFuture.get(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/common/cmd/CommandHandler.java b/src/main/java/com/iflytop/sgs/common/cmd/CommandHandler.java new file mode 100644 index 0000000..15a4c4c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/cmd/CommandHandler.java @@ -0,0 +1,9 @@ +package com.iflytop.sgs.common.cmd; + +import com.iflytop.sgs.app.model.dto.CmdDTO; + +import java.util.concurrent.CompletableFuture; + +public interface CommandHandler { + CompletableFuture handle(CmdDTO cmdDTO) throws Exception; +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/common/cmd/CyclicNumberGenerator.java b/src/main/java/com/iflytop/sgs/common/cmd/CyclicNumberGenerator.java new file mode 100644 index 0000000..6941ca9 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/cmd/CyclicNumberGenerator.java @@ -0,0 +1,39 @@ +package com.iflytop.sgs.common.cmd; + +public class CyclicNumberGenerator { + // 饿汉式单例,在类加载时就创建实例 + private static final CyclicNumberGenerator INSTANCE = new CyclicNumberGenerator(); + // 当前生成的数字,初始值为 1 + private int currentNumber = 1; + + // 私有构造函数,防止外部实例化 + private CyclicNumberGenerator() { + } + + // 提供全局访问点获取单例实例 + public static CyclicNumberGenerator getInstance() { + return INSTANCE; + } + + public static void main(String[] args) { + CyclicNumberGenerator generator = CyclicNumberGenerator.getInstance(); + for (int i = 0; i < 4096; i++) { + System.out.println(generator.generateNumber()); + } + } + + /** + * 生成 1 到 255 之间的循环整数 + * + * @return 生成的整数 + */ + public synchronized int generateNumber() { + int result = currentNumber; + // 每次生成后将当前数字加 1 + currentNumber++; + if (currentNumber > 4096) { + currentNumber = 1; + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommand.java b/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommand.java new file mode 100644 index 0000000..0e78a14 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommand.java @@ -0,0 +1,34 @@ +package com.iflytop.sgs.common.cmd; + +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import lombok.Data; + +@Data +public class DeviceCommand { + /** + * 指令ID + */ + private Integer cmdId; + + /** + * 指令代码 例如 "device_status_get" + */ + private String cmdCode; + + /** + * 目标设备 + */ + private CmdDevice device; + + /** + * 执行动作 + */ + private CmdAction action; + + /** + * 指令参数 + */ + private DeviceCommandParams param; +} + diff --git a/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandBundle.java b/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandBundle.java new file mode 100644 index 0000000..2fab003 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandBundle.java @@ -0,0 +1,19 @@ +package com.iflytop.sgs.common.cmd; + +import lombok.Data; + +@Data +public class DeviceCommandBundle { + + /** + * 指令名称 + */ + private String cmdName; + + /** + * 指令 + */ + private DeviceCommand deviceCommand; + +} + diff --git a/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandGenerator.java b/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandGenerator.java new file mode 100644 index 0000000..ac636f1 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandGenerator.java @@ -0,0 +1,532 @@ +package com.iflytop.sgs.common.cmd; + + +import com.iflytop.sgs.common.enums.cmd.*; + +/** + * 生成给设备发送的指令 + */ +public class DeviceCommandGenerator { + + /** + * 门 移动 + * + * @param position 位置 单位mm + */ + public static DeviceCommandBundle doorMove(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return controlMotorCmd(CmdDevice.door_motor, CmdAction.move, params, "门 移动"); + } + + /** + * 门 相对移动 + * + * @param position 位置 单位mm + */ + public static DeviceCommandBundle doorMoveBy(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return controlMotorCmd(CmdDevice.door_motor, CmdAction.move_by, params, "门 相对移动"); + } + + /** + * 门 停止动作 + */ + public static DeviceCommandBundle doorStop() { + return controlMotorCmd(CmdDevice.door_motor, CmdAction.stop, null, "门 停止"); + } + + /** + * 门 回原点 + */ + public static DeviceCommandBundle doorOrigin() { + return controlMotorCmd(CmdDevice.door_motor, CmdAction.origin, null, "门 回原点"); + } + + /** + * 门 设置参数 + * + * @param speed 速度 单位 mm/s + */ + public static DeviceCommandBundle doorSet(Double speed) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setSpeed(speed); + return setInfoCmd(CmdDevice.door_motor, CmdAction.set, null, "门 设置参数"); + } + + /** + * 龙门架 x轴设置参数 + * + * @param speed 速度 单位 mm/s + */ + public static DeviceCommandBundle gantryXSet(Double speed) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setSpeed(speed); + return setInfoCmd(CmdDevice.gantry_x, CmdAction.set, params, "龙门架 x轴设置参数"); + } + + + /** + * 龙门架 z轴设置参数 + * + * @param speed 速度 单位 mm/s + */ + public static DeviceCommandBundle gantryZSet(Double speed) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setSpeed(speed); + return setInfoCmd(CmdDevice.gantry_z, CmdAction.set, params, "龙门架 z轴设置参数"); + } + + /** + * 加液位升降电机参数设置 + * + * @param speed 速度 单位 mm/s + */ + public static DeviceCommandBundle motorLiquidSet(Double speed) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setSpeed(speed); + return setInfoCmd(CmdDevice.motor_liquid, CmdAction.set, params, "加液体位电机 设置参数"); + } + + + /** + * 龙门架 x轴回原点 + */ + public static DeviceCommandBundle gantryXOrigin() { + return controlMotorCmd(CmdDevice.gantry_x, CmdAction.origin, null, "龙门架 x轴回原点"); + } + + + /** + * 龙门架 z轴回原点 + */ + public static DeviceCommandBundle gantryZOrigin() { + return controlMotorCmd(CmdDevice.gantry_z, CmdAction.origin, null, "龙门架 z轴回原点"); + } + /** + * 加液位升降电机回原点 + */ + public static DeviceCommandBundle motorLiquidOrigin() { + return controlMotorCmd(CmdDevice.motor_liquid, CmdAction.origin, null, "加液位电机 回原点"); + } + + /** + * 龙门架 x轴移动 + * + * @param position 位置 单位 mm + */ + public static DeviceCommandBundle gantryXMove(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return controlMotorCmd(CmdDevice.gantry_x, CmdAction.move, params, "龙门架 x轴移动"); + } + + + /** + * 龙门架 z轴移动 + * + * @param position 位置 单位 mm + */ + public static DeviceCommandBundle gantryZMove(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return controlMotorCmd(CmdDevice.gantry_z, CmdAction.move, params, "龙门架 z轴移动"); + } + + /** + * 龙门架 x轴相对移动 + * + * @param position 位置 单位 mm + */ + public static DeviceCommandBundle gantryXMoveBy(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return controlMotorCmd(CmdDevice.gantry_x, CmdAction.move_by, params, "龙门架 x轴相对移动"); + } + + /** + * 龙门架 y轴相对移动 + * + * @param position 位置 单位 mm + */ + public static DeviceCommandBundle gantryYMoveBy(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return controlMotorCmd(CmdDevice.gantry_y, CmdAction.move_by, params, "龙门架 y轴相对移动"); + } + + /** + * 龙门架 z轴相对移动 + * + * @param position 位置 单位 mm + */ + public static DeviceCommandBundle gantryZMoveBy(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return controlMotorCmd(CmdDevice.gantry_z, CmdAction.move_by, params, "龙门架 z轴相对移动"); + } + + /** + * 龙门架 x轴停止 + */ + public static DeviceCommandBundle gantryXStop() { + return controlMotorCmd(CmdDevice.gantry_x, CmdAction.stop, null, "龙门架 x轴停止"); + } + + /** + * 龙门架 y轴停止 + */ + public static DeviceCommandBundle gantryYStop() { + return controlMotorCmd(CmdDevice.gantry_y, CmdAction.stop, null, "龙门架 y轴停止"); + } + + /** + * 龙门架 z轴停止 + */ + public static DeviceCommandBundle gantryZStop() { + return controlMotorCmd(CmdDevice.gantry_z, CmdAction.stop, null, "龙门架 z轴停止"); + } + + /** + * 加液位电机停止 + */ + public static DeviceCommandBundle motorLiquidStop() { + return controlMotorCmd(CmdDevice.motor_liquid, CmdAction.stop, null, "加液位电机 停止"); + } + + /** + * 加液机械臂 + * + * @param position 位置 单位 mm + */ + public static DeviceCommandBundle motorLiquidMove(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return controlMotorCmd(CmdDevice.motor_liquid, CmdAction.move, params, "加液机械臂 移动"); + + } + + /** + * 加液泵 泵1设置参数 + * + * @param speed 速度 单位 mm/s + */ + public static DeviceCommandBundle acidPump1Set(Double speed) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setSpeed(speed); + return setInfoCmd(CmdDevice.liquid_pump, CmdAction.set, params, "加液泵 设置参数"); + } + + /** + * 加液泵 泵1旋转 + * + */ + public static DeviceCommandBundle acidPump1Rotate(CmdDirection direction) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setDirection(direction); + return setInfoCmd(CmdDevice.liquid_pump, CmdAction.rotate, params, "加液泵 旋转"); + } + + /** + * 加液泵 相对移动 + * + * @param position 加液量 单位mL + */ + public static DeviceCommandBundle liquidPumpMoveBy(Double position) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setPosition(position); + return setInfoCmd(CmdDevice.liquid_pump, CmdAction.move_by, params, "加液泵 相对移动"); + } + + /** + * 加液泵 停止 + */ + public static DeviceCommandBundle liquidPumpStop() { + return setInfoCmd(CmdDevice.liquid_pump, CmdAction.stop, null, "加液泵 停止"); + } + + + /** + * 四路电磁控制阀转换 todo + */ + public static DeviceCommandBundle valveTurn() { + DeviceCommandParams params = new DeviceCommandParams(); + return controlCmd(CmdDevice.four_ways_valve, CmdAction.open, null, "电磁转换阀 转换"); + } + /** + * 风扇 1打开 + */ + public static DeviceCommandBundle fan1Open() { + return controlCmd(CmdDevice.fan_1, CmdAction.open, null, "风扇1 打开"); + } + + /** + * 风扇 2打开 + */ + public static DeviceCommandBundle fan2Open() { + return controlCmd(CmdDevice.fan_2, CmdAction.open, null, "风扇2 打开"); + } + + /** + * 风扇 3打开 + */ + public static DeviceCommandBundle fan3Open() { + return controlCmd(CmdDevice.fan_3, CmdAction.open, null, "风扇3 打开"); + } + + /** + * 风扇 4打开 + */ + public static DeviceCommandBundle fan4Open() { + return controlCmd(CmdDevice.fan_4, CmdAction.open, null, "风扇4 打开"); + } + + /** + * 风扇 1关闭 + */ + public static DeviceCommandBundle fan1Close() { + return controlCmd(CmdDevice.fan_1, CmdAction.close, null, "风扇1 关闭"); + } + + /** + * 风扇 2关闭 + */ + public static DeviceCommandBundle fan2Close() { + return controlCmd(CmdDevice.fan_2, CmdAction.close, null, "风扇2 关闭"); + } + + /** + * 风扇 3关闭 + */ + public static DeviceCommandBundle fan3Close() { + return controlCmd(CmdDevice.fan_3, CmdAction.close, null, "风扇3 关闭"); + } + + /** + * 风扇 4关闭 + */ + public static DeviceCommandBundle fan4Close() { + return controlCmd(CmdDevice.fan_4, CmdAction.close, null, "风扇4 关闭"); + } + + + /** + * 水泵电源 开启 + */ + public static DeviceCommandBundle waterPumpPowerOpen() { + return controlCmd(CmdDevice.water_pump_power, CmdAction.open, null, "水泵 电源开启"); + } + + /** + * 水泵电源 关闭 + */ + public static DeviceCommandBundle waterPumpPowerClose() { + return controlCmd(CmdDevice.water_pump_power, CmdAction.close, null, "水泵 电源关闭"); + } + + /** + * 风机电源 开启 + */ + public static DeviceCommandBundle ventilatorPowerOpen() { + return controlCmd(CmdDevice.ventilator_power, CmdAction.open, null, " 风机 电源开启"); + } + + /** + * 风机电源 关闭 + */ + public static DeviceCommandBundle ventilatorPowerClose() { + return controlCmd(CmdDevice.ventilator_power, CmdAction.close, null, "风机 电源关闭"); + } + + /** + * 加热棒1 获取温度 + */ + public static DeviceCommandBundle heatRod1Get() { + return getInfoCmd(CmdDevice.heat_rod_1, "加热棒1 获取温度"); + } + + /** + * 加热棒2 获取温度 + */ + public static DeviceCommandBundle heatRod2Get() { + return getInfoCmd(CmdDevice.heat_rod_2, "加热棒2 获取温度"); + } + + /** + * 加热棒3 获取温度 + */ + public static DeviceCommandBundle heatRod3Get() { + return getInfoCmd(CmdDevice.heat_rod_3, "加热棒3 获取温度"); + } + + /** + * 加热棒4 获取温度 + */ + public static DeviceCommandBundle heatRod4Get() { + return getInfoCmd(CmdDevice.heat_rod_4, "加热棒4 获取温度"); + } + + + /** + * 加热棒1 开启加热 + * + * @param temperature 目标温度 + */ + public static DeviceCommandBundle heatRod1Open(Double temperature) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setTemperature(temperature); + return controlCmd(CmdDevice.heat_rod_1, CmdAction.open, params, "加热棒1 开启加热"); + } + + /** + * 加热棒2 开启加热 + * + * @param temperature 目标温度 + */ + public static DeviceCommandBundle heatRod2Open(Double temperature) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setTemperature(temperature); + return controlCmd(CmdDevice.heat_rod_2, CmdAction.open, params, "加热棒2 开启加热"); + } + + /** + * 加热棒3 开启加热 + * + * @param temperature 目标温度 + */ + public static DeviceCommandBundle heatRod3Open(Double temperature) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setTemperature(temperature); + return controlCmd(CmdDevice.heat_rod_3, CmdAction.open, params, "加热棒3 开启加热"); + } + + /** + * 加热棒4 开启加热 + * + * @param temperature 目标温度 + */ + public static DeviceCommandBundle heatRod4Open(Double temperature) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setTemperature(temperature); + return controlCmd(CmdDevice.heat_rod_4, CmdAction.open, params, "加热棒4 开启加热"); + } + + + /** + * 加热棒1 关闭加热 + */ + public static DeviceCommandBundle heatRod1Close() { + return controlCmd(CmdDevice.heat_rod_1, CmdAction.close, null, "加热棒1 关闭加热"); + } + + /** + * 加热棒2 关闭加热 + */ + public static DeviceCommandBundle heatRod2Close() { + return controlCmd(CmdDevice.heat_rod_2, CmdAction.close, null, "加热棒2 关闭加热"); + } + + /** + * 加热棒3 关闭加热 + */ + public static DeviceCommandBundle heatRod3Close() { + return controlCmd(CmdDevice.heat_rod_3, CmdAction.close, null, "加热棒3 关闭加热"); + } + + /** + * 加热棒4 关闭加热 + */ + public static DeviceCommandBundle heatRod4Close() { + return controlCmd(CmdDevice.heat_rod_4, CmdAction.close, null, "加热棒4 关闭加热"); + } + + /** + * 三色灯开启 + * + * @param color red | green | blue + */ + public static DeviceCommandBundle tricolorLightOpen(CmdColor color) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setColor(color); + return controlCmd(CmdDevice.tricolor_light, CmdAction.open, params, "三色灯 开启"); + } + + /** + * 三色灯关闭 + */ + public static DeviceCommandBundle tricolorLightClose() { + return controlCmd(CmdDevice.tricolor_light, CmdAction.close, null, "三色灯 关闭"); + } + + /** + * 相机补灯光 开启 + * + * @param brightness 亮度 0-100 + */ + public static DeviceCommandBundle fillLightOpen(Double brightness) { + DeviceCommandParams params = new DeviceCommandParams(); + params.setBrightness(brightness); + return controlCmd(CmdDevice.fill_light, CmdAction.open, params, "相机补光灯 开启"); + } + + /** + * 相机补光灯 关闭 + */ + public static DeviceCommandBundle fillLightClose() { + return controlCmd(CmdDevice.fill_light, CmdAction.close, null, "相机补光灯 关闭"); + } + + + /** + * 拍照 + */ + public static DeviceCommandBundle takePhoto() { + return controlCmd(CmdDevice.photo, CmdAction.take_photo, null, "拍照"); + } + + + /** + * 控制设备电机指令 + */ + private static DeviceCommandBundle controlMotorCmd(CmdDevice device, CmdAction action, DeviceCommandParams params, String commandName) { + return deviceCmd("controlMotorCmd", device, action, params, commandName); + } + + /** + * 设置参数指令 + */ + private static DeviceCommandBundle setInfoCmd(CmdDevice device, CmdAction action, DeviceCommandParams params, String commandName) { + return deviceCmd("setInfoCmd", device, action, params, commandName); + } + + /** + * 获取参数指令 + */ + private static DeviceCommandBundle getInfoCmd(CmdDevice device, String commandName) { + return deviceCmd("getInfoCmd", device, CmdAction.get, null, commandName); + } + + + /** + * 设备控制指令 + */ + private static DeviceCommandBundle controlCmd(CmdDevice device, CmdAction action, DeviceCommandParams params, String commandName) { + return deviceCmd("controlCmd", device, action, params, commandName); + } + + /** + * 设备指令包装 + */ + private static DeviceCommandBundle deviceCmd(String code, CmdDevice device, CmdAction action, DeviceCommandParams params, String commandName) { + DeviceCommandBundle deviceCommandBundle = new DeviceCommandBundle(); + deviceCommandBundle.setCmdName(commandName); + DeviceCommand deviceCommand = new DeviceCommand(); + deviceCommand.setCmdCode(code); + deviceCommand.setDevice(device); + deviceCommand.setAction(action); + deviceCommand.setParam(params); + deviceCommandBundle.setDeviceCommand(deviceCommand); + return deviceCommandBundle; + } +} diff --git a/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandParams.java b/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandParams.java new file mode 100644 index 0000000..e0ba09f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/cmd/DeviceCommandParams.java @@ -0,0 +1,26 @@ +package com.iflytop.sgs.common.cmd; + + +import com.iflytop.sgs.common.enums.cmd.CmdAxis; +import com.iflytop.sgs.common.enums.cmd.CmdColor; +import com.iflytop.sgs.common.enums.cmd.CmdDirection; +import lombok.Data; + +@Data +public class DeviceCommandParams { + private String device; + private Integer current; + private CmdDirection direction; + private Double position; + private Double speed; + private Double angle; + private Double volume; + private Double distance; + private Double temperature; + private CmdAxis axis; + private CmdColor color; + private Double brightness; + private Double x; + private Double y; + private Double z; +} diff --git a/src/main/java/com/iflytop/sgs/common/constant/CommandStatus.java b/src/main/java/com/iflytop/sgs/common/constant/CommandStatus.java new file mode 100644 index 0000000..88f14f8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/constant/CommandStatus.java @@ -0,0 +1,47 @@ +package com.iflytop.sgs.common.constant; + +public class CommandStatus { + /** + * 普通消息反馈 + */ + public static final String SEND = "send"; + + /** + * 业务指令 收到指令 + */ + public static final String RECEIVE = "receive"; + /** + * 业务指令 开始执行业务指令 + */ + public static final String START = "start"; + /** + * 业务指令 执行成功 + */ + public static final String SUCCESS = "success"; + /** + * 业务指令 执行失败 + */ + public static final String FAIL = "fail"; + /** + * 业务指令 喷涂完毕 + */ + public static final String SPRAY_TASK_FINISH = "spray_task_finish"; + /** + * 业务指令 指令处理完毕反馈 + */ + public static final String FINISH = "finish"; + + + /** + * 设备指令 发送设备指令 + */ + public static final String DEVICE_SEND = "device_send"; + /** + * 设备指令 收到设备指令反馈 + */ + public static final String DEVICE_RESULT = "device_result"; + /** + * 设备指令 收到设备指令反馈 错误 + */ + public static final String DEVICE_ERROR = "device_error"; +} diff --git a/src/main/java/com/iflytop/sgs/common/constant/WebSocketMessageType.java b/src/main/java/com/iflytop/sgs/common/constant/WebSocketMessageType.java new file mode 100644 index 0000000..45f2439 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/constant/WebSocketMessageType.java @@ -0,0 +1,42 @@ +package com.iflytop.sgs.common.constant; + +public class WebSocketMessageType { + /** + * 设备状态 + */ + public static final String STATUS = "status"; + /** + * 设备报警 + */ + public static final String ALARM = "alarm"; + /** + * 指令反馈 + */ + public static final String CMD_RESPONSE = "cmd_response"; + + /** + * 工艺执行步骤反馈 + */ + public static final String CRAFTS_STEP = "crafts_step"; + + /** + * 工艺执行状态反馈 + */ + public static final String CRAFTS_STATE = "crafts_state"; + + /** + * 工艺DEBUG + */ + public static final String CRAFTS_DEBUG = "crafts_debug"; + + /** + * 容器剩余状态 + */ + public static final String CONTAINER = "container"; + + /** + * DEBUG消息推送 + */ + public static final String CMD_DEBUG = "cmd_debug"; + +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/AcidPumpDeviceCode.java b/src/main/java/com/iflytop/sgs/common/enums/AcidPumpDeviceCode.java new file mode 100644 index 0000000..6294ca6 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/AcidPumpDeviceCode.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.common.enums; + +import lombok.Getter; + +/** + * 泵设备id枚举 + * 格式为 "acid_pump_01" ~ "acid_pump_08" + */ +@Getter +public enum AcidPumpDeviceCode { + liquid_pump; +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/ContainerCode.java b/src/main/java/com/iflytop/sgs/common/enums/ContainerCode.java new file mode 100644 index 0000000..a3f3141 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/ContainerCode.java @@ -0,0 +1,19 @@ +package com.iflytop.sgs.common.enums; + +import lombok.Getter; + +/** + * 容器 + */ +@Getter +public enum ContainerCode { + container_01, + container_02, + container_03, + container_04, + container_05, + container_06, + container_07, + container_08, + container_09, +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/ContainerType.java b/src/main/java/com/iflytop/sgs/common/enums/ContainerType.java new file mode 100644 index 0000000..678516d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/ContainerType.java @@ -0,0 +1,18 @@ +package com.iflytop.sgs.common.enums; + +import lombok.Getter; + +/** + * 容器类型 + */ +@Getter +public enum ContainerType { + /** + * 溶液容器 + */ + solution, + /** + * 中和容器 + */ + neutralization +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/HeatModuleCode.java b/src/main/java/com/iflytop/sgs/common/enums/HeatModuleCode.java new file mode 100644 index 0000000..f1f3272 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/HeatModuleCode.java @@ -0,0 +1,15 @@ +package com.iflytop.sgs.common.enums; + +import lombok.Getter; + +/** + * 加热模块code枚举 + * 格式为 "heat_module_01" ~ "heat_module_06" + */ +@Getter +public enum HeatModuleCode { + heat_module_01, + heat_module_02, + heat_module_03, + heat_module_04; +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/automaton/CraftEvents.java b/src/main/java/com/iflytop/sgs/common/enums/automaton/CraftEvents.java new file mode 100644 index 0000000..3c1b82b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/automaton/CraftEvents.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.common.enums.automaton; + +/** + * 工艺状态机事件枚举 + */ +public enum CraftEvents { + /** 启动工艺 */ + START, + /** 单步完成 */ + STEP_COMPLETE, + /** 暂停执行 */ + PAUSE, + /** 恢复执行 */ + RESUME, + /** 用户手动停止 */ + STOP, + /** 执行过程中发生错误 */ + ERROR_OCCUR, + /** 工艺正常完成 */ + FINISH + +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/automaton/CraftStates.java b/src/main/java/com/iflytop/sgs/common/enums/automaton/CraftStates.java new file mode 100644 index 0000000..951bd8d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/automaton/CraftStates.java @@ -0,0 +1,19 @@ +package com.iflytop.sgs.common.enums.automaton; + +/** + * 工艺状态机状态枚举 + */ +public enum CraftStates { + /** 初始状态,尚未启动工艺 */ + READY, + /** 工艺正在执行中 */ + RUNNING, + /** 工艺已暂停,等待恢复 */ + PAUSED, + /** 工艺已被手动停止 */ + STOPPED, + /** 工艺执行过程中发生错误 */ + ERROR, + /** 工艺已正常执行完毕 */ + FINISHED +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdAction.java b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdAction.java new file mode 100644 index 0000000..dccb6f0 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdAction.java @@ -0,0 +1,9 @@ +package com.iflytop.sgs.common.enums.cmd; + +public enum CmdAction { + move, move_by, move_joint, move_point, origin, rotate, + + open, close, stop, start, set, get, + open_power, close_power, open_circle, close_circle, + open_heart, close_heart, open_cool, close_cool, take_photo +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdAxis.java b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdAxis.java new file mode 100644 index 0000000..6049b6a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdAxis.java @@ -0,0 +1,5 @@ +package com.iflytop.sgs.common.enums.cmd; + +public enum CmdAxis { + joint1,joint2 +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdColor.java b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdColor.java new file mode 100644 index 0000000..dd21158 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdColor.java @@ -0,0 +1,5 @@ +package com.iflytop.sgs.common.enums.cmd; + +public enum CmdColor { + red, green, blue +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdDevice.java b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdDevice.java new file mode 100644 index 0000000..72e1a93 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdDevice.java @@ -0,0 +1,47 @@ +package com.iflytop.sgs.common.enums.cmd; + +public enum CmdDevice { + door_motor, + shake_motor, + tray_motor, + dual_robot, + motor_liquid,//新增加液升降电机 todo 需要与小何同步 + four_ways_valve,// 新增4桶电磁阀 todo 需要与小何同步 + liquid_pump, + gantry_x, + gantry_y, + gantry_z, + heater_motor_1, + heater_motor_2, + heater_motor_3, + heater_motor_4, + heater_motor_5, + heater_motor_6, + acid_pump_1, + acid_pump_2, + acid_pump_3, + acid_pump_4, + acid_pump_5, + acid_pump_6, + acid_pump_7, + acid_pump_8, + claw, + fan_1, + fan_2, + fan_3, + fan_4, + fan_5, + fan_6, + water_pump_power, + ventilator_power, + heat_rod_1, + heat_rod_2, + heat_rod_3, + heat_rod_4, + heat_rod_5, + heat_rod_6, + tricolor_light, + fill_light, + cold_trap, + photo +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdDirection.java b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdDirection.java new file mode 100644 index 0000000..4b1a339 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/cmd/CmdDirection.java @@ -0,0 +1,5 @@ +package com.iflytop.sgs.common.enums.cmd; + +public enum CmdDirection { + forward, backward +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/data/Deleted.java b/src/main/java/com/iflytop/sgs/common/enums/data/Deleted.java new file mode 100644 index 0000000..9de9a9d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/data/Deleted.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.common.enums.data; + +import lombok.Getter; + +/** + * 删除状态枚举 + */ +@Getter +public enum Deleted { + ENABLE, + DISABLE +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/data/DevicePositionCode.java b/src/main/java/com/iflytop/sgs/common/enums/data/DevicePositionCode.java new file mode 100644 index 0000000..b762003 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/data/DevicePositionCode.java @@ -0,0 +1,186 @@ +package com.iflytop.sgs.common.enums.data; + +import lombok.Getter; + +/** + * 设备位置代码 + */ +@Getter +public enum DevicePositionCode { + + /** + * 预充防滴落距离 + */ + antiDripDistance(DevicePositionType.DISTANCE, "预充防滴落距离"), + /** + * 试管间距距离 + */ + testTubeSpacing(DevicePositionType.DISTANCE, "试管间距距离"), + + /** + * 夹爪托盘夹取距离 + */ + //clawTrayPick(DevicePositionType.DISTANCE, "夹爪托盘夹取距离"), + + /** + * 加液机械臂脱离试管的距离 + */ + liquidMotorSafeDistance(DevicePositionType.DISTANCE, "加液机械臂脱离试管的距离"), + /** + * 机械臂夹紧距离 + */ + clawTrayGrip(DevicePositionType.DISTANCE, "夹紧托盘横向距离"), + /** + * 托盘和夹爪分离高度 + */ + clawTrayHeight(DevicePositionType.DISTANCE, "夹紧托盘纵向高度"), + + + /** + * 夹爪试管夹取距离 + */ + //clawTestTubePick(DevicePositionType.DISTANCE, "夹爪试管夹取距离"), + + /** + * 夹爪试管夹紧距离 + */ + //clawTestTubeGrip(DevicePositionType.DISTANCE, "夹爪试管夹紧距离"), + /** + * 夹爪拍子夹取距离 + */ + //clawCapPick(DevicePositionType.DISTANCE, "夹爪拍子夹取距离"), + + /** + * 夹爪拍子夹紧距离 + */ + //clawCapGrip(DevicePositionType.DISTANCE, "夹爪拍子夹紧距离"), + + /** + * 开门距离 + */ + doorOpen(DevicePositionType.DISTANCE, "开门距离"), + + /** + * 托盘升降抬升距离 + */ + trayLift(DevicePositionType.DISTANCE, "托盘升降抬升距离"), + + /** + * 托盘升降下降距离 + */ + trayLower(DevicePositionType.DISTANCE, "托盘升降下降距离"), + + /** + * 加热模块拍子移动高度 + */ + heatModuleCapMoveHeight(DevicePositionType.DISTANCE, "加热模块拍子移动高度"), + + /** + * 拍子升降模块拍子移动高度 + */ + capModuleCapMoveHeight(DevicePositionType.DISTANCE, "拍子升降模块拍子移动高度"), + + /** + * 加热模块托盘移动高度 + */ + heatModuleTrayMoveHeight(DevicePositionType.DISTANCE, "加热模块托盘移动高度"), + + /** + * 加液模块托盘移动高度 + */ + solutionModuleTrayMoveHeight(DevicePositionType.DISTANCE, "加液模块托盘移动高度"), + +// /** +// * 移动试管安全高度 +// */ +// testTubeSafetyHeight(DevicePositionType.DISTANCE, "移动试管安全高度"), + + /** + * 拍子升降高度 + */ + capLiftingHeight(DevicePositionType.DISTANCE, "拍子升降高度"), + + ////////////////////////点 + + /** + * 拍子存放区拍子夹爪点 + */ + capStorageCapClawPoint(DevicePositionType.POINT_3D, "拍子存放区拍子夹爪点"), + /** + * 上料区托盘夹爪点 + */ + materialAreaTrayPoint(DevicePositionType.POINT_3D, "上料区托盘夹爪点"), + /** + * 加液区托盘夹爪点 + */ + liquidAreaTrayPoint(DevicePositionType.POINT_3D, "加液区托盘夹爪点"), + + /** + * 加热区1托盘夹爪点 + */ + heatArea1TrayClawPoint(DevicePositionType.POINT_3D, "加热区1托盘夹爪点"), + + /** + * 加热区2托盘夹爪点 + */ + heatArea2TrayClawPoint(DevicePositionType.POINT_3D, "加热区2托盘夹爪点"), + + /** + * 加热区3托盘夹爪点 + */ + heatArea3TrayClawPoint(DevicePositionType.POINT_3D, "加热区3托盘夹爪点"), + + /** + * 加热区4托盘夹爪点 + */ + heatArea4TrayClawPoint(DevicePositionType.POINT_3D, "加热区4托盘夹爪点"), + + /** + * 加热区5托盘夹爪点 + */ + heatArea5TrayClawPoint(DevicePositionType.POINT_3D, "加热区5托盘夹爪点"), + + /** + * 加热区6托盘夹爪点 + */ + heatArea6TrayClawPoint(DevicePositionType.POINT_3D, "加热区6托盘夹爪点"), + + /** + * 加热区1拍子夹爪点 + */ + heatArea1CapClawPoint(DevicePositionType.POINT_3D, "加热区1拍子夹爪点"), + + /** + * 加热区2拍子夹爪点 + */ + heatArea2CapClawPoint(DevicePositionType.POINT_3D, "加热区2拍子夹爪点"), + + /** + * 加热区3拍子夹爪点 + */ + heatArea3CapClawPoint(DevicePositionType.POINT_3D, "加热区3拍子夹爪点"), + + /** + * 加热区4拍子夹爪点 + */ + heatArea4CapClawPoint(DevicePositionType.POINT_3D, "加热区4拍子夹爪点"), + + /** + * 加热区5拍子夹爪点 + */ + heatArea5CapClawPoint(DevicePositionType.POINT_3D, "加热区5拍子夹爪点"), + + /** + * 加热区6拍子夹爪点 + */ + heatArea6CapClawPoint(DevicePositionType.POINT_3D, "加热区6拍子夹爪点"); + + + private final DevicePositionType type; + private final String name; + + DevicePositionCode(DevicePositionType type, String name) { + this.type = type; + this.name = name; + } +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/data/DevicePositionType.java b/src/main/java/com/iflytop/sgs/common/enums/data/DevicePositionType.java new file mode 100644 index 0000000..5230a82 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/data/DevicePositionType.java @@ -0,0 +1,31 @@ +package com.iflytop.sgs.common.enums.data; + +import lombok.Getter; + +/** + * 设备位置类型 + */ +@Getter +public enum DevicePositionType { + /** + * 直线距离 + */ + DISTANCE("直线距离"), + /** + * 二维点 + */ + POINT_2D("二维点"), + /** + * 三维点 + */ + POINT_3D("三维点"); + + + private final String name; + + // 构造函数 + DevicePositionType(String name) { + this.name = name; + } + +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/data/FixedUser.java b/src/main/java/com/iflytop/sgs/common/enums/data/FixedUser.java new file mode 100644 index 0000000..448dbcf --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/data/FixedUser.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.common.enums.data; + +import lombok.Getter; + +/** + * 系統固定用户 + */ +@Getter +public enum FixedUser { + ENABLE, + DISABLE +} diff --git a/src/main/java/com/iflytop/sgs/common/enums/data/UsrRole.java b/src/main/java/com/iflytop/sgs/common/enums/data/UsrRole.java new file mode 100644 index 0000000..9f6507a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/data/UsrRole.java @@ -0,0 +1,7 @@ +package com.iflytop.sgs.common.enums.data; + +public enum UsrRole { + ADMIN, + DEVELOPER, + USER; +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/common/enums/data/ValveWaysCode.java b/src/main/java/com/iflytop/sgs/common/enums/data/ValveWaysCode.java new file mode 100644 index 0000000..3b1e01d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/enums/data/ValveWaysCode.java @@ -0,0 +1,15 @@ +package com.iflytop.sgs.common.enums.data; + +import lombok.Getter; + +/** + * 加热模块code枚举 + * 格式为 "heat_module_01" ~ "heat_module_06" + */ +@Getter +public enum ValveWaysCode { + thin, + thick, + water, + waste; +} diff --git a/src/main/java/com/iflytop/sgs/common/exception/AppException.java b/src/main/java/com/iflytop/sgs/common/exception/AppException.java new file mode 100644 index 0000000..6d6d7a8 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/exception/AppException.java @@ -0,0 +1,21 @@ +package com.iflytop.sgs.common.exception; + +import com.iflytop.sgs.common.result.IResultCode; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AppException extends RuntimeException { + private final IResultCode resultCode; + + public AppException(IResultCode resultCode) { + super(resultCode.getMsg()); + this.resultCode = resultCode; + } + + @Override + public String toString() { + return "AppException{" + "code='" + resultCode.getCode() + ", msg=" + resultCode.getMsg() + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/common/exception/CommandExecTimeoutException.java b/src/main/java/com/iflytop/sgs/common/exception/CommandExecTimeoutException.java new file mode 100644 index 0000000..02d9267 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/exception/CommandExecTimeoutException.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.common.exception; + +public class CommandExecTimeoutException extends Exception{ + public CommandExecTimeoutException() { + } + + public CommandExecTimeoutException(String message) { + super(message); + } + + public CommandExecTimeoutException(String message, Throwable cause) { + super(message, cause); + } + + public CommandExecTimeoutException(Throwable cause) { + super(cause); + } + + public CommandExecTimeoutException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/iflytop/sgs/common/exception/HardwareErrorException.java b/src/main/java/com/iflytop/sgs/common/exception/HardwareErrorException.java new file mode 100644 index 0000000..e29bc01 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/exception/HardwareErrorException.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.common.exception; + +public class HardwareErrorException extends Exception{ + public HardwareErrorException() { + } + + public HardwareErrorException(String message) { + super(message); + } + + public HardwareErrorException(String message, Throwable cause) { + super(message, cause); + } + + public HardwareErrorException(Throwable cause) { + super(cause); + } + + public HardwareErrorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/iflytop/sgs/common/exception/UnSupportCommandException.java b/src/main/java/com/iflytop/sgs/common/exception/UnSupportCommandException.java new file mode 100644 index 0000000..1314415 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/exception/UnSupportCommandException.java @@ -0,0 +1,25 @@ +package com.iflytop.sgs.common.exception; + +/** + * 不支持的命令异常 + */ +public class UnSupportCommandException extends Exception{ + public UnSupportCommandException() { + } + + public UnSupportCommandException(String message) { + super(message); + } + + public UnSupportCommandException(String message, Throwable cause) { + super(message, cause); + } + + public UnSupportCommandException(Throwable cause) { + super(cause); + } + + public UnSupportCommandException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/iflytop/sgs/common/handler/GlobalExceptionHandler.java b/src/main/java/com/iflytop/sgs/common/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..e4cb075 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/handler/GlobalExceptionHandler.java @@ -0,0 +1,82 @@ +package com.iflytop.sgs.common.handler; + +import com.iflytop.sgs.common.exception.AppException; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.common.result.ResultCode; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public Result handleMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) { + String method = request.getMethod(); + String url = request.getRequestURL().toString(); + String allowed = ex.getSupportedHttpMethods() != null + ? ex.getSupportedHttpMethods().toString() + : "[]"; + String msg = String.format( + "请求方法 '%s' 不支持 (URL: %s)。支持的方法有:%s", + method, url, allowed); + log.error(msg, ex); + return Result.failed(ResultCode.METHOD_NOT_ALLOWED.getCode(), msg); + } + + // 1) JSON Body 校验失败 + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleBodyValid(MethodArgumentNotValidException ex) { + String msg = ex.getBindingResult().getFieldErrors().stream() + .map(f -> f.getField() + ": " + f.getDefaultMessage()) + .collect(Collectors.joining("; ")); + return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg); + } + + // 2) 方法级参数校验失败(PathVariable/RequestParam) + @ExceptionHandler(ConstraintViolationException.class) + public Result handleParamValid(ConstraintViolationException ex) { + String msg = ex.getConstraintViolations().stream() + .map(v -> { + String path = v.getPropertyPath().toString(); + String field = path.substring(path.lastIndexOf('.') + 1); + return field + ": " + v.getMessage(); + }) + .collect(Collectors.joining("; ")); + return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg); + } + + // 3) 表单绑定失败 + @ExceptionHandler(BindException.class) + public Result handleBind(BindException ex) { + String msg = ex.getFieldErrors().stream() + .map(f -> f.getField() + ": " + f.getDefaultMessage()) + .collect(Collectors.joining("; ")); + return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg); + } + + @ExceptionHandler(Exception.class) + public Result handleException(Exception ex, HttpServletRequest request) { + // 获取请求方法和请求路径 + String method = request.getMethod(); + String url = request.getRequestURL().toString(); + + if (ex instanceof AppException ae) { + log.warn("AppException at {} {}:", method, url, ae); + return Result.failed(ae.getResultCode()); + } + + // 在日志中打印出请求地址 + log.error("Unhandled exception at {} {}:", method, url, ex); + return Result.failed(ResultCode.SYSTEM_ERROR.getCode(), ex.getMessage()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/common/handler/MyMetaObjectHandler.java b/src/main/java/com/iflytop/sgs/common/handler/MyMetaObjectHandler.java new file mode 100644 index 0000000..b08bbb2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/handler/MyMetaObjectHandler.java @@ -0,0 +1,36 @@ +package com.iflytop.sgs.common.handler; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * mybatis-plus 字段自动填充 + */ +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + /** + * 新增填充创建时间 + * + * @param metaObject 元数据 + */ + @Override + public void insertFill(MetaObject metaObject) { + this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class); + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class); + } + + /** + * 更新填充更新时间 + * + * @param metaObject 元数据 + */ + @Override + public void updateFill(MetaObject metaObject) { + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class); + } + +} diff --git a/src/main/java/com/iflytop/sgs/common/result/IResultCode.java b/src/main/java/com/iflytop/sgs/common/result/IResultCode.java new file mode 100644 index 0000000..a338fa2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/result/IResultCode.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.common.result; + +/** + * 响应码接口 + **/ +public interface IResultCode { + + String getCode(); + + String getMsg(); + +} diff --git a/src/main/java/com/iflytop/sgs/common/result/PageResult.java b/src/main/java/com/iflytop/sgs/common/result/PageResult.java new file mode 100644 index 0000000..675da34 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/result/PageResult.java @@ -0,0 +1,43 @@ +package com.iflytop.sgs.common.result; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 分页响应结构体 + */ +@Data +public class PageResult implements Serializable { + + private String code; + + private Data data; + + private String msg; + + public static PageResult success(IPage page) { + PageResult result = new PageResult<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + + Data data = new Data<>(); + data.setList(page.getRecords()); + data.setTotal(page.getTotal()); + + result.setData(data); + result.setMsg(ResultCode.SUCCESS.getMsg()); + return result; + } + + @lombok.Data + public static class Data { + + private List list; + + private long total; + + } + +} diff --git a/src/main/java/com/iflytop/sgs/common/result/Result.java b/src/main/java/com/iflytop/sgs/common/result/Result.java new file mode 100644 index 0000000..8528492 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/result/Result.java @@ -0,0 +1,75 @@ +package com.iflytop.sgs.common.result; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; + +import java.io.Serializable; + +/** + * 统一响应结构体 + **/ +@Data +public class Result implements Serializable { + + private String code; + + private T data; + + private String msg; + + public static Result success() { + return success(null); + } + + public static Result success(T data) { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMsg(ResultCode.SUCCESS.getMsg()); + result.setData(data); + return result; + } + + public static Result failed() { + return result(ResultCode.SYSTEM_ERROR.getCode(), ResultCode.SYSTEM_ERROR.getMsg(), null); + } + + public static Result failed(String msg) { + return result(ResultCode.SYSTEM_ERROR.getCode(), msg, null); + } + + public static Result judge(boolean status) { + if (status) { + return success(); + } else { + return failed(); + } + } + + public static Result failed(IResultCode resultCode) { + return result(resultCode.getCode(), resultCode.getMsg(), null); + } + + public static Result failed(IResultCode resultCode, String msg) { + return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), null); + } + + public static Result failed(String code, String msg) { + return result(code, msg, null); + } + + private static Result result(IResultCode resultCode, T data) { + return result(resultCode.getCode(), resultCode.getMsg(), data); + } + + private static Result result(String code, String msg, T data) { + Result result = new Result<>(); + result.setCode(code); + result.setData(data); + result.setMsg(msg); + return result; + } + + public static boolean isSuccess(Result result) { + return result != null && ResultCode.SUCCESS.getCode().equals(result.getCode()); + } +} diff --git a/src/main/java/com/iflytop/sgs/common/result/ResultCode.java b/src/main/java/com/iflytop/sgs/common/result/ResultCode.java new file mode 100644 index 0000000..f388c83 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/result/ResultCode.java @@ -0,0 +1,90 @@ +package com.iflytop.sgs.common.result; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.Serializable; + +/** + * 统一前端响应码定义 + */ +@Getter +@AllArgsConstructor +public enum ResultCode implements IResultCode, Serializable { + //================================ 通用 ================================= + SUCCESS("0", "成功"), + FAILED("-1", "未知错误"), + + //============================ 1xxx:请求 & 参数 ============================ + INVALID_PARAMETER("1000", "参数无效或缺失"), + PARAMETER_TYPE_MISMATCH("1001", "参数类型不匹配"), + PARAMETER_OUT_OF_RANGE("1002", "参数超出允许范围"), + + //============================ 2xxx:认证 & 授权 ============================ + UNAUTHORIZED("2000", "未认证或登录失效"), + FORBIDDEN("2001", "无访问权限"), + TOKEN_EXPIRED("2002", "Token 已过期"), + TOKEN_INVALID("2003", "Token 无效"), + + //============================ 3xxx:资源访问 ============================ + NOT_FOUND("3000", "资源不存在"), + METHOD_NOT_ALLOWED("3001", "不支持的请求方法"), + + //============================ 4xxx:业务错误 ============================ + USER_NOT_FOUND("4000", "用户不存在"), + USER_ALREADY_EXISTS("4001", "用户已存在"), + INVALID_CREDENTIALS("4002", "用户名或密码错误"), + OPERATION_NOT_ALLOWED("4003", "业务操作不允许"), + DATA_ALREADY_EXISTS("4004", "数据已存在"), + + CRAFT_RUNNING("4101", "工艺正在执行"), + CRAFT_CONTEXT_NULL("4102", "请先配置该加热区工艺"), + CRAFT_CONTAINER_NOT_FOUND("4005", "工艺未找到对应溶液容器"), + //============================ 5xxx:系统 & 第三方 ============================ + SYSTEM_ERROR("5000", "系统内部错误"), + SERVICE_UNAVAILABLE("5001", "服务暂不可用"), + EXTERNAL_API_ERROR("5002", "第三方服务调用失败"), + COMMAND_EXEC_TIMEOUT("5003", "命令执行超时"), + HARDWARE_ERROR("5004", "硬件错误"), + EMERGENCY_STOP("5555", "设备急停中"), + //============================ 6xxx:指令相关 ============================ + COMMAND_NOT_FOUND("6000", "指令未找到"), + COMMAND_ALREADY_EXECUTING("6001", "指令正在执行,无法重复执行"), + SENSOR_STATUS_FAILED("6010", "获取传感器状态失败"), + TARGET_HEAT_MODULE_OCCUPIED("6021", "目标加热模块被占用"), + TARGET_HEAT_MODULE_NO_TRAY("6022", "目标加热模块无托盘"), + CAP_LIFT_ERROR("6023", "拍子升降错误"), + CMD_BUSY("6024", "设备忙,请稍后"), + ; + /** 状态码 */ + private final String code; + /** 提示信息 */ + private final String msg; + + @Override + public String getCode() { + return code; + } + + @Override + public String getMsg() { + return msg; + } + + /** + * 根据 code 获取枚举 + */ + public static ResultCode parse(String code) { + for (ResultCode item : values()) { + if (item.code.equals(code)) { + return item; + } + } + return FAILED; + } + + @Override + public String toString() { + return "{\"code\":\"" + code + "\", \"msg\":\"" + msg + "\"}"; + } +} diff --git a/src/main/java/com/iflytop/sgs/common/utils/ByteArray.java b/src/main/java/com/iflytop/sgs/common/utils/ByteArray.java new file mode 100644 index 0000000..25ca1e7 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/utils/ByteArray.java @@ -0,0 +1,125 @@ +// Source code is decompiled from a .class file using FernFlower decompiler. +package com.iflytop.sgs.common.utils; + +import org.springframework.lang.NonNull; + +public class ByteArray { + public ByteArray() { + } + + public static int readU8bit(byte[] code, int index) { + if (index >= code.length) + return 0; + return code[index] & 255; + } + + public static int readS8bit(byte[] code, int index) { + if (index >= code.length) + return 0; + + return code[index]; + } + + public static int readU16bit(byte[] code, int index) { + if (index + 1 >= code.length) + return 0; + return (code[index + 1] & 255) << 8 | code[index] & 255; + } + + public static void setU16bit(byte[] code, int index, int value) { + code[index + 1] = (byte) (value >> 8); + code[index] = (byte) value; + } + + public static void setU8(byte[] code, int off, int value) { + code[off] = (byte) value; + } + + public static int readS16bit(byte[] code, int index) { + if (index + 1 >= code.length) + return 0; + + return code[index + 1] << 8 | code[index] & 255; + } + + public static int read32bit(byte[] code, int index) { + if (index + 3 >= code.length) + return 0; + return code[index + 3] << 24 | (code[index + 2] & 255) << 16 | (code[index + 1] & 255) << 8 | code[index] & 255; + } + + public static Integer[] read32bitArray(byte[] code) { + int count = code.length / 4; + Integer[] array = new Integer[count]; + for (int i = 0; i < count; i++) { + array[i] = read32bit(code, i * 4); + } + return array; + } + + public static Integer[] read16bitArray(byte[] code) { + int count = code.length / 2; + Integer[] array = new Integer[count]; + for (int i = 0; i < count; i++) { + array[i] = readS16bit(code, i * 2); + } + return array; + } + + public static Integer[] readU16bitArray(byte[] code) { + int count = code.length / 2; + Integer[] array = new Integer[count]; + for (int i = 0; i < count; i++) { + array[i] = readU16bit(code, i * 2); + } + return array; + } + + + public static String toByteString(byte[] arrary) { + StringBuilder sb = new StringBuilder(); + for (byte b : arrary) { + sb.append(String.format("%02X", b)); + } + return sb.toString(); + } + + public static byte[] hexStringToBytes(@NonNull String str) { + if (str.isEmpty()) { + return new byte[0]; + } else { + byte[] byteArray = new byte[str.length() / 2]; + for (int i = 0; i < byteArray.length; i++) { + int high = Character.digit(str.charAt(i * 2), 16); + int low = Character.digit(str.charAt(i * 2 + 1), 16); + if (high == -1 || low == -1) { + return null; + } + byteArray[i] = (byte) (high * 16 + low); + } + return byteArray; + } + } + + + public static byte[] concat(byte[]... arrays) { + int length = 0; + for (byte[] array : arrays) { + length += array.length; + } + + byte[] result = new byte[length]; + int destPos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, destPos, array.length); + destPos += array.length; + } + + return result; + } + + // public static void main(String[] args) { + // byte[] bytes = new byte[]{0x01, 0x02, 0x03, 0x04}; + // System.out.println(toByteString(bytes)); + // } +} diff --git a/src/main/java/com/iflytop/sgs/common/utils/LambdaUtil.java b/src/main/java/com/iflytop/sgs/common/utils/LambdaUtil.java new file mode 100644 index 0000000..82b7e13 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/common/utils/LambdaUtil.java @@ -0,0 +1,15 @@ +package com.iflytop.sgs.common.utils; + +import com.iflytop.sgs.common.annotation.CheckedRunnable; + +public class LambdaUtil { + public static Runnable unchecked(CheckedRunnable runnable) { + return () -> { + try { + runnable.run(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/hardware/HardwareService.java b/src/main/java/com/iflytop/sgs/hardware/HardwareService.java new file mode 100644 index 0000000..ed40483 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/HardwareService.java @@ -0,0 +1,95 @@ +package com.iflytop.sgs.hardware; + + +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.iflytop.sgs.app.core.event.CommandFeedbackEvent; +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.command.DeviceResponse; +import com.iflytop.sgs.hardware.command.checker.SupportMethod; +import com.iflytop.sgs.hardware.exception.HardwareException; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ThreadPoolExecutor; + + +@Slf4j +@Component +@RequiredArgsConstructor +public class HardwareService { + private final ApplicationEventPublisher eventPublisher; + private final ApplicationContext applicationContext; + private final Map cmdHandlers = new HashMap<>(); + + ThreadPoolExecutor executor = ThreadUtil.newFixedExecutor(50, "hardware-service", true); + + + @PostConstruct + public void postInit() { + Map handlers = applicationContext.getBeansOfType(CommandHandler.class); + handlers.values().forEach(this::registerCommandHandler); + } + + void registerCommandHandler(CommandHandler handler) { + Set devices = handler.getSupportedDevices(); + for (CmdDevice device : devices) { + cmdHandlers.put(device, handler); + } + } + + public boolean sendCommand(DeviceCommand cmd) { + log.trace("sendCommand: {}", cmd); + if (executor.getQueue().remainingCapacity() == 0) { + log.error("线程池队列已满,无法提交任务"); + return false; + } + executor.submit(() -> handleCommand(cmd)); + return true; + } + + void handleCommand(DeviceCommand cmd) { + DeviceResponse response = new DeviceResponse(); + response.setCmdId(cmd.getCmdId()); + try { + String strMethod = cmd.getCmdCode(); + + SupportMethod.checkMethod(strMethod); + + if (cmdHandlers.containsKey(cmd.getDevice())) { + log.trace("cmdHandlers: {}", cmd); + cmdHandlers.get(cmd.getDevice()).sendCommand(cmd); + response.setSuccess(Boolean.TRUE); + } else { + log.error("不支持的设备类型: {}", cmd.getDevice()); + throw new InvalidParameterException(StrUtil.format("[Device]: {}", cmd.getDevice())); + } + + } catch (HardwareException e) { + String errorStr = e.getError().toString(); + log.error("Hardware ERROR {}", errorStr); + response.setSuccess(Boolean.FALSE); + response.setErrorMsg(errorStr); + } catch (Exception e) { + log.error("指令执行失败: {}", e.getMessage(), e); + response.setSuccess(Boolean.FALSE); + response.setErrorMsg(e.getMessage()); + } + finally { + JSONObject jsonResponse = JSONUtil.parseObj(response); + eventPublisher.publishEvent(new CommandFeedbackEvent(this, jsonResponse)); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/comm/can/A8kCanBusConnection.java b/src/main/java/com/iflytop/sgs/hardware/comm/can/A8kCanBusConnection.java new file mode 100644 index 0000000..58e6a7c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/comm/can/A8kCanBusConnection.java @@ -0,0 +1,360 @@ +package com.iflytop.sgs.hardware.comm.can; + +import com.iflytop.sgs.common.utils.ByteArray; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.factory.A8kPacketFactory; +import com.iflytop.sgs.hardware.service.AppEventBusService; +import com.iflytop.sgs.hardware.type.*; +import com.iflytop.sgs.hardware.type.appevent.A8kCanBusOnConnectEvent; +import com.iflytop.sgs.hardware.type.error.A8kEcode; +import com.iflytop.sgs.hardware.type.error.AEHardwareError; +import com.iflytop.sgs.hardware.utils.OS; +import com.iflytop.sgs.hardware.utils.ZList; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.enums.ReadyState; +import org.java_websocket.handshake.ServerHandshake; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + + +@Slf4j +public class A8kCanBusConnection extends WebSocketClient { + @Resource + AppEventBusService eventBus; + + String datachurl = null; + String cmdchurl = null; + Boolean firstCall = true; + + @Value("${device.enableCanBus}") + Boolean enableCanBus; + + + static class ProcessContext { + BlockingQueue receiptQueue = new LinkedBlockingQueue<>(); // + A8kPacket cmdPacket; + Map txcmdcache = new HashMap<>(); + + + public A8kPacket getReceipt(int overtime) { + long end = System.currentTimeMillis() + overtime; + A8kPacket packet = null; + while (System.currentTimeMillis() < end) { + try { + packet = receiptQueue.poll(10, TimeUnit.MILLISECONDS); + } catch (InterruptedException ignored) { + } + if (packet != null) { + return packet; + } + } + + synchronized (this) { + try { + packet = receiptQueue.poll(1, TimeUnit.MILLISECONDS); + } catch (InterruptedException ignored) { + } + receiptQueue.clear(); + return packet; + } + } + + synchronized void pushReceipt(A8kPacket receipt) { + if (this.cmdPacket != null && this.cmdPacket.getPacketIndex() == receipt.getPacketIndex()) { + if (this.cmdPacket.getModuleId() == receipt.getModuleId() && this.cmdPacket.getCmdId() == receipt.getCmdId()) { + receiptQueue.add(receipt); + this.cmdPacket = null; + } else { + log.warn("RX index equal, but cmdId not equal, {} {}", this.cmdPacket, receipt); + } + } else { + log.warn("RX unmatched index receipt, {} {}", this.cmdPacket, receipt); + log.warn("."); + } + } + + synchronized void setWaitingReceiptIndex(A8kPacket cmdPacket) { + receiptQueue.clear(); + this.cmdPacket = cmdPacket; + } + + + synchronized void storageTxLastCmd(A8kPacket pack) { + MId mid = MId.valueOf(pack.getModuleId()); + CmdId cmdId = CmdId.valueOf(pack.getCmdId()); + if (cmdId != null && cmdId.isActionCmd()) + txcmdcache.put(mid, cmdId); + } + + synchronized CmdId getLastTxCmd(MId mid) { + return txcmdcache.get(mid); + } + } + + + ProcessContext context = new ProcessContext(); + int packetIndex = 0;//发送包的packetIndex + + + public A8kCanBusConnection(String cmdchurl, String datachurl) { + super(URI.create(datachurl)); + log.info("new A8kCanBusConnection: {} {}", cmdchurl, datachurl); + + this.datachurl = datachurl; + this.cmdchurl = cmdchurl; + packetIndex = 0; + + } + + @Override public void onOpen(ServerHandshake serverHandshake) { + log.info("a8k canbus connect sucess"); + eventBus.pushEvent(new A8kCanBusOnConnectEvent()); + } + + @Override public void onMessage(String s) { + log.trace("RX-RAW: {}", s); + processCanRxMessage(s); + } + + @Override public void onClose(int i, String s, boolean b) { + log.warn("a8k canbus lost connection..."); + } + + @Override public void onError(Exception e) { + log.info("a8k can-websocket-channel on error"); + } + + + synchronized public A8kPacket callcmd(MId moduleId, CmdId cmdId, Integer... param) throws HardwareException { + var packet = packParamsToPacket(moduleId.toInt(), cmdId.toInt(), ZList.of(param)); + return autoReSend(packet, A8kPacket.CMD_OVERTIME); + } + + synchronized public A8kPacket callcmd2(MId moduleId, CmdId cmdId, Integer overtime, Integer... params) throws HardwareException { + var packet = packParamsToPacket(moduleId.toInt(), cmdId.toInt(), ZList.of(params)); + return autoReSend(packet, overtime); + } + + synchronized public A8kPacket send(A8kPacket pack, int overtime) throws HardwareException { + return priSend(pack, overtime); + } + + synchronized public ModuleStatus moduleGetStatus(MId id) throws HardwareException { + var getStatusPacket = packParamsToPacket(id.toInt(), CmdId.module_get_status.toInt(), ZList.of()); + for (int i = 0; i < 10; i++) { + try { + return ModuleStatus.valueOf(priSend(getStatusPacket, 30).getContentI32(0)); + } catch (HardwareException ignored) { + } + restartCanif(); + OS.hsleep(10); + } + throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, + MId.valueOf(getStatusPacket.getModuleId()), CmdId.valueOf(getStatusPacket.getCmdId()))); + + } + + synchronized public Boolean ping(MId id) { + var getStatusPacket = packParamsToPacket(id.toInt(), CmdId.module_ping.toInt(), ZList.of()); + try { + priSend(getStatusPacket, 30); + } catch (HardwareException e) { + return false; + } + return true; + } + + // + // PRIVATE + // + @Scheduled(fixedRate = 10000) + private void autoConnect() { + if (!enableCanBus) + return; + + if (!isOpen()) { + if (getReadyState().equals(ReadyState.NOT_YET_CONNECTED)) { + try { + connect(); + } catch (IllegalStateException ignored) { + } + } else if (getReadyState().equals(ReadyState.CLOSED)) { + reconnect(); + } + } + } + + /** + * 强制关闭websocket连接,让其与canbus服务重连 + */ + private A8kPacket packParamsToPacket(Integer moduleId, Integer cmdId, List params) { + return A8kPacketFactory.buildCMDPacket(moduleId, cmdId, params); + } + + + private A8kPacket autoReSend(A8kPacket pack, int overtime) throws HardwareException { + if (firstCall) { + firstCall = false; + restartCanif(); + } + + for (int i = 0; i < 5; i++) { + try { + return this.priSend(pack, overtime); + } catch (HardwareException e) { + if (!e.error.code.equals(A8kEcode.LOW_ERROR_OVERTIME)) { + throw e; + } + } + restartCanif(); + OS.hsleep(50); + + + log.error("send cmd {} {} fail, retry {}", pack, pack.toByteString(), i); + } + throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, MId.valueOf(pack.getModuleId()), CmdId.valueOf(pack.getCmdId()))); + } + + + private A8kPacket priSend(A8kPacket pack, int overtime) throws HardwareException { + try { + return _priSend(pack, overtime); + } catch (HardwareException e) { + throw e; + } catch (Exception e) { + log.error("priSend error", e); + throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, MId.valueOf(pack.getModuleId()), CmdId.valueOf(pack.getCmdId()))); + } + } + + + HardwareException buildOvertimeError(A8kPacket pack) { + return HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, MId.valueOf(pack.getModuleId()), CmdId.valueOf(pack.getCmdId()))); + } + + private A8kPacket _priSend(A8kPacket pack, int overtime) throws HardwareException { + // alloc new packetIndex + packetIndex = packetIndex + 1; + if (packetIndex > 30000) { + packetIndex = 1; + } + + //set waiting receipt index + pack.setPacketIndex(packetIndex); + context.storageTxLastCmd(pack); + context.setWaitingReceiptIndex(pack); + + // TX packet + String txpacket = pack.toByteString(); + log.trace("TX-RAW: {} | {}", txpacket, pack); + send(txpacket); + + + A8kPacket receipt; + receipt = context.getReceipt(overtime); + if (receipt == null) { + throw buildOvertimeError(pack); + } + + if (receipt.getPacketType() == A8kPacket.PACKET_TYPE_ERROR_ACK) { + throw HardwareException.of(new AEHardwareError(A8kEcode.fromInt(receipt.getContentI32(0)), + MId.valueOf(pack.getModuleId()), + CmdId.valueOf(pack.getCmdId()), + txpacket, + receipt.toByteString() + ) + ); + } + return receipt; + } + + /** + * 处理接收到的消息 + */ + private void processCanRxMessage(String s) { + byte[] rx = ByteArray.hexStringToBytes(s); + if (rx == null || rx.length == 0) { + log.warn("rx is empty"); + return; + } + + if (rx.length < A8kPacket.PACKET_MIN_LEN) { + log.warn("rx is too short,{}", s); + return; + } + + A8kPacket packet = new A8kPacket(rx); + + if (packet.getCheckcode() != packet.computeCheckcode()) { + log.warn("Rx packet checkcode error: {}, expect: {} but got: {}", packet, packet.computeCheckcode(), packet.getCheckcode()); + return; + } + + if (!packet.isSupportPacket()) { + log.warn("Rx packet not support: {}", packet); + return; + } + + + if (packet.getPacketType() == A8kPacket.PACKET_TYPE_ACK || packet.getPacketType() == A8kPacket.PACKET_TYPE_ERROR_ACK) { + log.trace("RX-ACK |RAW:{}| {}", s, packet); + context.pushReceipt(packet); + } else if (packet.getPacketType() == A8kPacket.PACKET_TYPE_EVENT) { + log.trace("RX-REPORT |RAW:{}| {}", s, packet); +// eventBus.pushEvent(new A8kHardwareReport(packet)); + } else { + log.warn("RX-UNPROCESSABLE: |RAW:{}| {}", s, packet); + } + } + + public synchronized CmdId getLastTxCmd(MId mid) { + return context.getLastTxCmd(mid); + } + + public void restartCanif() { + callLocalCmd("restart"); + } + + + public HttpResponse callLocalCmd(String path) { + // 创建一个HttpClient实例 + HttpClient httpClient = HttpClient.newHttpClient(); + HttpRequest httpRequest = HttpRequest.newBuilder().uri(URI.create(String.format("%s/%s", cmdchurl, path))).build(); + HttpResponse ret = null; + try { + ret = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + } catch (Exception e) { + log.error("callcmd error", e); + return null; + } + return ret; + } + + public static void main(String[] args) { + HttpClient httpClient = HttpClient.newHttpClient(); + HttpRequest httpRequest = HttpRequest.newBuilder().uri(URI.create("http://192.168.8.10:19004/zexcan/restart")).build(); + HttpResponse ret = null; + try { + ret = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + log.info("ret: \t{}", ret.body()); + } catch (Exception e) { + log.error("callcmd error", e); + } + } + +} + + diff --git a/src/main/java/com/iflytop/sgs/hardware/comm/can/A8kCanBusService.java b/src/main/java/com/iflytop/sgs/hardware/comm/can/A8kCanBusService.java new file mode 100644 index 0000000..7fa165b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/comm/can/A8kCanBusService.java @@ -0,0 +1,132 @@ +package com.iflytop.sgs.hardware.comm.can; + +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.*; +import com.iflytop.sgs.hardware.type.error.A8kEcode; +import com.iflytop.sgs.hardware.type.error.AEHardwareError; +import com.iflytop.sgs.hardware.utils.OS; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.net.URISyntaxException; + +@Component +@Slf4j +public class A8kCanBusService { + @Resource + A8kCanBusConnection connection; + + @Value("${device.enableCanBus}") + Boolean enableCanBus; + + @PostConstruct + public void init() throws URISyntaxException { + if (enableCanBus) { + connection.connect(); + }else{ + log.warn("canBus is disabled"); + } + } + + public Boolean ping(MId id) { + return connection.ping(id); + } + + /** + * 强制关闭websocket连接,让其与canbus服务重连 + */ + public void forceShutdown() { + connection.close(); + } + + // + // BASE_OPERATION + // + + public A8kPacket callcmd(MId moduleId, CmdId cmdId, Integer... param) throws HardwareException { + return connection.callcmd(moduleId, cmdId, param); + } + + public A8kPacket callcmd2(MId moduleId, CmdId cmdId, Integer overtime, Integer... params) throws HardwareException { + return connection.callcmd2(moduleId, cmdId, overtime, params); + } + + synchronized public A8kPacket send(A8kPacket pack, int overtime) throws HardwareException { + return connection.send(pack, overtime); + } + + // + // MODULE FUNCTION + // + + public void moduleStop(MId id) throws HardwareException { + connection.callcmd(id, CmdId.module_stop); + } + + public void moduleStopNoException(MId id) { + try { + moduleStop(id); + } catch (HardwareException ignored) { + } + } + + public ModuleStatus moduleGetStatus(MId id) throws HardwareException { + return connection.moduleGetStatus(id); + } + + public void moduleSetReg(MId id, RegIndex regindex, Integer reg) throws HardwareException { + connection.callcmd2(id, CmdId.module_set_reg, 100, regindex.index, reg); + } + + public Integer moduleGetReg(MId id, RegIndex regindex) throws HardwareException { + var packet = connection.callcmd2(id, CmdId.module_get_reg, 100, regindex.index); + return packet.getContentI32(0); + } + + public A8kEcode moduleGetError(MId id) throws HardwareException { + var packet = connection.callcmd(id, CmdId.module_get_error); + return A8kEcode.fromInt(packet.getContentI32(0)); + } + + public Integer moduleGetDetailError(MId id) throws HardwareException { + return connection.callcmd(id, CmdId.module_get_detail_error).getContentI32(0); + } + + public Integer moduleReadVersion(MId id) throws HardwareException { + return connection.callcmd(id, CmdId.module_get_version).getContentI32(0); + } + + public ModuleType moduleReadType(MId id) throws HardwareException { + var packet = connection.callcmd(id, CmdId.module_get_type); + return ModuleType.of(packet.getContentI32(0)); + } + + public void waitForMod(MId mid, Integer acitionOvertime) throws HardwareException { + long startedAt = System.currentTimeMillis(); + CmdId action = connection.getLastTxCmd(mid); + do { + ModuleStatus statu = moduleGetStatus(mid); + if (statu == ModuleStatus.IDLE) { + break; + } else if (statu == ModuleStatus.ERROR) { + log.error("{} waitting for action {} , catch error {}, defail ecode {}", mid, action, moduleGetError(mid), moduleGetDetailError(mid)); + throw HardwareException.of(new AEHardwareError(moduleGetError(mid), mid, action)); + } + long now = System.currentTimeMillis(); + if (now - startedAt > acitionOvertime) { + log.error("{} waitting for action {} overtime({})", mid, action, acitionOvertime); + moduleStopNoException(mid); + throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_ACTION_OVERTIME, mid, action)); + } + OS.hsleep(30); + } while (true); + } + + public boolean isConnect() { + return connection.isOpen(); + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/comm/modbus/JSerialCommWrapper.java b/src/main/java/com/iflytop/sgs/hardware/comm/modbus/JSerialCommWrapper.java new file mode 100644 index 0000000..49190b3 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/comm/modbus/JSerialCommWrapper.java @@ -0,0 +1,85 @@ +package com.iflytop.sgs.hardware.comm.modbus; + +import com.fazecast.jSerialComm.SerialPort; +import com.serotonin.modbus4j.serial.SerialPortWrapper; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +@Slf4j +public class JSerialCommWrapper implements SerialPortWrapper { + private final int baudRate; + private final int dataBits; + private final int stopBits; + private final int parity; + + private final SerialPort serialPort; + + public JSerialCommWrapper(String portName, int baudRate, int dataBits, int stopBits, int parity) { + this.baudRate = baudRate; + this.dataBits = dataBits; + this.stopBits = stopBits; + this.parity = parity; + + serialPort = SerialPort.getCommPort(portName); + serialPort.setBaudRate(this.baudRate); + serialPort.setNumDataBits(this.dataBits); + serialPort.setNumStopBits(this.stopBits); + serialPort.setParity(this.parity); + } + + @Override + public void open() throws Exception { + if (!serialPort.openPort()) { + log.error("Could not open serial port {}", serialPort.getSystemPortName()); + throw new IOException("Failed to open serial port: " + serialPort.getSystemPortName()); + } + else{ + log.info("Opened serial port: " + serialPort.getSystemPortName()); + } + } + + @Override + public void close() throws Exception { + if (serialPort != null) { + serialPort.closePort(); + log.info("Closed serial port: {}", serialPort.getSystemPortName()); + } + else + { + log.warn("Could not close serial port: {} null", serialPort.getSystemPortName()); + } + } + + @Override + public InputStream getInputStream() { + return serialPort.getInputStream(); + } + + @Override + public OutputStream getOutputStream() { + return serialPort.getOutputStream(); + } + + @Override + public int getBaudRate() { + return this.baudRate; + } + + @Override + public int getStopBits() { + return this.stopBits; + } + + @Override + public int getParity() { + return this.parity; + } + + @Override + public int getDataBits() { + return this.dataBits; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/comm/modbus/ModbusMasterFactory.java b/src/main/java/com/iflytop/sgs/hardware/comm/modbus/ModbusMasterFactory.java new file mode 100644 index 0000000..fb4eba6 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/comm/modbus/ModbusMasterFactory.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.hardware.comm.modbus; + +import com.serotonin.modbus4j.ModbusFactory; +import com.serotonin.modbus4j.ModbusMaster; + +public class ModbusMasterFactory { + static final ModbusFactory factory = new ModbusFactory(); + + public static ModbusMaster createModbusRtuMaster(String portName, int baudRate) { + JSerialCommWrapper wrapper = new JSerialCommWrapper(portName, baudRate, 8, 1, 0); + return factory.createRtuMaster(wrapper); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/comm/modbus/ModbusMasterService.java b/src/main/java/com/iflytop/sgs/hardware/comm/modbus/ModbusMasterService.java new file mode 100644 index 0000000..940df55 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/comm/modbus/ModbusMasterService.java @@ -0,0 +1,283 @@ +package com.iflytop.sgs.hardware.comm.modbus; + +import com.serotonin.modbus4j .ModbusMaster; +import com.serotonin.modbus4j.msg.*; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ModbusMasterService { + private ModbusMaster modbusMaster_; + private final ReentrantLock lock = new ReentrantLock(); + + @Value("${modbus.port}") + private String port; + + @Value("${modbus.baudrate}") + private int baudrate; + + @PostConstruct + public void init() { + modbusMaster_ = ModbusMasterFactory.createModbusRtuMaster(port, baudrate); + log.info("============== ============== ============== ============== =============="); + log.info("============== ============== ============== ============== =============="); + try { + this.connect(); + log.info("Modbus master 端口: {} 波特率:{} initialized successfully.", port, baudrate); + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to initialize Modbus master: {} 端口: {} 波特率: {}", + e.getMessage(), port, baudrate); + } + log.info("============== ============== ============== ============== =============="); + log.info("============== ============== ============== ============== =============="); + } + + /** + * 检查连接状态 + */ + private boolean isConnectionInvalid() { + if (modbusMaster_ == null) { + log.error("Modbus master is not initialized."); + return true; + } +// if (!modbusMaster_.isConnected()) { +// log.error("Modbus master is not connected."); +// return true; +// } + return false; + } + + // ==== ==== ==== ==== ==== ==== Modbus 功能封装 ==== ==== ==== ==== ==== ==== + + /** + * 读取线圈状态 (功能码: 1, READ_COILS) + */ + public boolean[] readCoils(int slaveId, int startAddress, int length) { + lock.lock(); + try { + if (isConnectionInvalid()) return null; + ReadCoilsRequest request = new ReadCoilsRequest(slaveId, startAddress, length); + ReadCoilsResponse response = (ReadCoilsResponse) modbusMaster_.send(request); + if (response.isException()) { + modbusMaster_.setConnected(false); + log.error("Error reading coils: {}", response.getExceptionMessage()); + return null; + } + return response.getBooleanData(); + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to read coils: {}", e.getMessage()); + return null; + } finally { + lock.unlock(); + } + } + + /** + * 读取离散输入 (功能码: 2, READ_DISCRETE_INPUTS) + */ + public boolean[] readDiscreteInputs(int slaveId, int startAddress, int length) { + lock.lock(); + try { + if (isConnectionInvalid()) return null; + ReadDiscreteInputsRequest request = new ReadDiscreteInputsRequest(slaveId, startAddress, length); + ReadDiscreteInputsResponse response = (ReadDiscreteInputsResponse) modbusMaster_.send(request); + if (response.isException()) { + modbusMaster_.setConnected(false); + log.error("Error reading discrete inputs: {}", response.getExceptionMessage()); + return null; + } + return response.getBooleanData(); + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to read discrete inputs: {}", e.getMessage()); + return null; + } finally { + lock.unlock(); + } + } + + /** + * 读取保持寄存器 (功能码: 3, READ_HOLDING_REGISTERS) + */ + public short[] readHoldingRegisters(int slaveId, int startAddress, int length) { + lock.lock(); + try { + if (isConnectionInvalid()) return null; + ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, startAddress, length); + ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) modbusMaster_.send(request); + if (response.isException()) { + modbusMaster_.setConnected(false); + log.error("Error reading holding registers: {}", response.getExceptionMessage()); + return null; + } + return response.getShortData(); + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to read holding registers: {}", e.getMessage()); + return null; + } finally { + lock.unlock(); + } + } + + /** + * 读取输入寄存器 (功能码: 4, READ_INPUT_REGISTERS) + */ + public short[] readInputRegisters(int slaveId, int startAddress, int length) { + lock.lock(); + try { + if (isConnectionInvalid()) return null; + ReadInputRegistersRequest request = new ReadInputRegistersRequest(slaveId, startAddress, length); + ReadInputRegistersResponse response = (ReadInputRegistersResponse) modbusMaster_.send(request); + if (response.isException()) { + modbusMaster_.setConnected(false); + log.error("Error reading input registers: {}", response.getExceptionMessage()); + return null; + } + return response.getShortData(); + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to read input registers: {}", e.getMessage()); + return null; + } finally { + lock.unlock(); + } + } + + /** + * 写单个线圈 (功能码: 5, WRITE_COIL) + */ + public boolean writeCoil(int slaveId, int address, boolean value) { + lock.lock(); + try { + if (isConnectionInvalid()) return false; + WriteCoilRequest request = new WriteCoilRequest(slaveId, address, value); + WriteCoilResponse response = (WriteCoilResponse) modbusMaster_.send(request); + if (response.isException()) { + modbusMaster_.setConnected(false); + log.error("Error writing coil: {}", response.getExceptionMessage()); + return false; + } + return true; + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to write coil: {}", e.getMessage()); + return false; + } finally { + lock.unlock(); + } + } + + /** + * 写单个寄存器 (功能码: 6, WRITE_REGISTER) + */ + public boolean writeRegister(int slaveId, int address, int value) { + lock.lock(); + try { + if (isConnectionInvalid()) return false; + WriteRegisterRequest request = new WriteRegisterRequest(slaveId, address, value); + WriteRegisterResponse response = (WriteRegisterResponse) modbusMaster_.send(request); + if (response.isException()) { + modbusMaster_.setConnected(false); + log.error("Error writing register: {}", response.getExceptionMessage()); + return false; + } + return true; + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to write register: {}", e.getMessage()); + return false; + } finally { + lock.unlock(); + } + } + + /** + * 写多个线圈 (功能码: 15, WRITE_COILS) + */ + public boolean writeCoils(int slaveId, int startAddress, boolean[] values) { + lock.lock(); + try { + if (isConnectionInvalid()) return false; + WriteCoilsRequest request = new WriteCoilsRequest(slaveId, startAddress, values); + WriteCoilsResponse response = (WriteCoilsResponse) modbusMaster_.send(request); + if (response.isException()) { + modbusMaster_.setConnected(false); + log.error("Error writing coils: {}", response.getExceptionMessage()); + return false; + } + return true; + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to write coils: {}", e.getMessage()); + return false; + } finally { + lock.unlock(); + } + } + + /** + * 写多个寄存器 (功能码: 16, WRITE_REGISTERS) + */ + public boolean writeRegisters(int slaveId, int startAddress, short[] values) { + lock.lock(); + try { + if (isConnectionInvalid()) return false; + WriteRegistersRequest request = new WriteRegistersRequest(slaveId, startAddress, values); + WriteRegistersResponse response = (WriteRegistersResponse) modbusMaster_.send(request); + if (response.isException()) { + modbusMaster_.setConnected(false); + log.error("Error writing registers: {}", response.getExceptionMessage()); + return false; + } + return true; + } catch (Exception e) { + modbusMaster_.setConnected(false); + log.error("Failed to write registers: {}", e.getMessage()); + return false; + } finally { + lock.unlock(); + } + } + + // ==== ==== ==== ==== ==== ==== 自动重连 ==== ==== ==== ==== ==== ==== + void connect() throws Exception + { + modbusMaster_.init(); + modbusMaster_.setRetries(3); +// modbusMaster_.setIoLog(new BasicLogger()); + modbusMaster_.setTimeout(2000); + modbusMaster_.setConnected(true); + } + + /** + * 自动重连 + */ + @Scheduled(fixedRate = 1000) + public void autoReconnect() { + lock.lock(); + try { + if(modbusMaster_ ==null || !modbusMaster_.isInitialized() || modbusMaster_.isConnected()) + return; + + this.connect(); + + log.info("Modbus master 重连成功 端口: {} 波特率:{}.", port, baudrate); + } catch (Exception e) { + log.error("Modbus master : {}", e.getMessage()); + log.info("Modbus master 重连失败 端口: {} 波特率:{}. {}", port, baudrate, e.getMessage()); + } finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/CommandHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/CommandHandler.java new file mode 100644 index 0000000..6324808 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/CommandHandler.java @@ -0,0 +1,70 @@ +package com.iflytop.sgs.hardware.command; + +import cn.hutool.core.util.StrUtil; +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.type.MId; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 命令处理器 + * H - 硬件 C - 命令 + */ +public abstract class CommandHandler { + + abstract protected Map getSupportCmdDeviceMIdMap(); + abstract protected Set getSupportActions(); + + /** + * 发送指令 + */ + public void sendCommand(DeviceCommand command) throws Exception + { + // 校验动作是否合法 + checkAction(command.getAction()); + // 校验参数是否合法 + checkParams(command); + + // 处理指令 + handleCommand(command); + } + + abstract protected void handleCommand(DeviceCommand command) throws Exception; + + /** + * 获取支持的设备 + */ + public Set getSupportedDevices() { + Map cmdDeviceMIdMap = getSupportCmdDeviceMIdMap(); + return cmdDeviceMIdMap.keySet(); + } + + /** + * 检查Action 是否合法 + * @param action + */ + protected void checkAction(CmdAction action) throws Exception + { + Set supportedActions = getSupportActions(); + if (!supportedActions.contains(action)) { + // 生成支持的动作列表字符串 + String supported = supportedActions.stream() + .map(Enum::name) + .collect(Collectors.joining(", ")); + + throw new IllegalArgumentException( + StrUtil.format("action [{}] not supported! Supported actions: {}", + action.name(), supported)); + } + } + + /** + * 检查参数是否合法 + */ + protected void checkParams(DeviceCommand command) throws Exception + {} +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/DeviceResponse.java b/src/main/java/com/iflytop/sgs/hardware/command/DeviceResponse.java new file mode 100644 index 0000000..9be768c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/DeviceResponse.java @@ -0,0 +1,12 @@ +package com.iflytop.sgs.hardware.command; + +import cn.hutool.json.JSONObject; +import lombok.Data; + +@Data +public class DeviceResponse { + Integer cmdId; + Boolean success; + String errorMsg; + JSONObject data; +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/checker/SupportMethod.java b/src/main/java/com/iflytop/sgs/hardware/command/checker/SupportMethod.java new file mode 100644 index 0000000..69317b9 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/checker/SupportMethod.java @@ -0,0 +1,31 @@ +package com.iflytop.sgs.hardware.command.checker; + +public class SupportMethod { + public enum CommandMethod { + CMD_CONTROL("controlCmd"), + CMD_CONTROL_MOTOR("controlMotorCmd"), + CMD_GET_INFO("getInfoCmd"), + CMD_SET_INFO("setInfoCmd"); + + private final String cmdName; + CommandMethod(String commandName) { + this.cmdName = commandName; + } + public String getCmdName() { + return cmdName; + } + } + + public static void checkMethod(String strMethod) { + if (strMethod == null || strMethod.isEmpty()) { + throw new NoSuchMethodError("* Empty Method *"); + } + + for (CommandMethod type : CommandMethod.values()) { + if (type.getCmdName().equals(strMethod)) { + return; + } + } + throw new NoSuchMethodError(strMethod); + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/AcidPumpHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/AcidPumpHandler.java new file mode 100644 index 0000000..7a3d59c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/AcidPumpHandler.java @@ -0,0 +1,92 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.common.enums.cmd.CmdDirection; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.AcidPumpDriver; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorDirect; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AcidPumpHandler extends CommandHandler { + private final AcidPumpDriver driver_; + + private final Map stepMotorMIdMap_ = Map.ofEntries( + Map.entry(CmdDevice.acid_pump_1, StepMotorMId.ACID_PUMP_1_MOTOR_MID), + Map.entry(CmdDevice.acid_pump_2, StepMotorMId.ACID_PUMP_2_MOTOR_MID), + Map.entry(CmdDevice.acid_pump_3, StepMotorMId.ACID_PUMP_3_MOTOR_MID), + Map.entry(CmdDevice.acid_pump_4, StepMotorMId.ACID_PUMP_4_MOTOR_MID), + Map.entry(CmdDevice.acid_pump_5, StepMotorMId.ACID_PUMP_5_MOTOR_MID), + Map.entry(CmdDevice.acid_pump_6, StepMotorMId.ACID_PUMP_6_MOTOR_MID), + Map.entry(CmdDevice.acid_pump_7, StepMotorMId.ACID_PUMP_7_MOTOR_MID), + Map.entry(CmdDevice.acid_pump_8, StepMotorMId.ACID_PUMP_8_MOTOR_MID) + ); + private final Map supportCmdDeviceMIdMap = stepMotorMIdMap_.entrySet().stream(). + collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + private final Set supportActions = Set.of(CmdAction.set, CmdAction.move_by, CmdAction.stop, CmdAction.rotate); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + public StepMotorDirect getMotorDirect(CmdDirection cmdDirection) + { + return switch (cmdDirection) { + case forward -> StepMotorDirect.FORWARD; + case backward -> StepMotorDirect.BACKWARD; + }; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + StepMotorMId stepMotorMId = stepMotorMIdMap_.get(command.getDevice()); + if (command.getAction() == CmdAction.origin) { + driver_.moveToHome(stepMotorMId); + } + else if(command.getAction() == CmdAction.stop) { + driver_.stop(stepMotorMId); + } + else if(command.getAction() == CmdAction.move) { + double position = command.getParam().getPosition(); + driver_.moveTo(stepMotorMId, position); + } + else if(command.getAction() == CmdAction.move_by) { + + double distance = command.getParam().getPosition(); + + driver_.moveBy(stepMotorMId, distance); + } + else if(command.getAction() == CmdAction.rotate) { + CmdDirection cmdDirection = command.getParam().getDirection(); + + StepMotorDirect direct = getMotorDirect(cmdDirection); + driver_.rotate(stepMotorMId, direct); + } + else if(command.getAction() == CmdAction.set) { + Double speed = command.getParam().getSpeed(); + if(speed != null) { + driver_.setSpeed(stepMotorMId, speed); + } + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/CameraHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/CameraHandler.java new file mode 100644 index 0000000..afda886 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/CameraHandler.java @@ -0,0 +1,50 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.CameraDriver; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CameraHandler extends CommandHandler { + private final CameraDriver cameraDriver_; + + private final Map supportCmdDeviceMIdMap = Map.ofEntries( + Map.entry(CmdDevice.photo, MId.NotSet) + ); + + private final Set supportActions = Set.of(CmdAction.open, CmdAction.close); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + MId getMId(CmdDevice cmdDevice){ + return supportCmdDeviceMIdMap.get(cmdDevice); + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + if (command.getAction() == CmdAction.take_photo) { + cameraDriver_.takePhoto(); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/ClawHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/ClawHandler.java new file mode 100644 index 0000000..df9fb12 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/ClawHandler.java @@ -0,0 +1,64 @@ +package com.iflytop.sgs.hardware.command.handlers; + + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.ClawDriver; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ClawHandler extends CommandHandler { + private final ClawDriver clawDriver_; + + private final Map servoMIdMap_ = Map.ofEntries( + Map.entry(CmdDevice.claw, MiniServoMId.CLAW_MID) + ); + private final Map supportCmdDeviceMIdMap = servoMIdMap_.entrySet().stream(). + collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + private final Set supportActions = Set.of(CmdAction.set, CmdAction.origin, CmdAction.move, CmdAction.stop); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + MiniServoMId servoMId = servoMIdMap_.get(command.getDevice()); + if (command.getAction() == CmdAction.origin) { + clawDriver_.moveToHome(servoMId); + } + else if(command.getAction() == CmdAction.stop) { + clawDriver_.stop(servoMId); + } + else if(command.getAction() == CmdAction.move) { + double position = command.getParam().getPosition(); + clawDriver_.moveTo(servoMId, position); + } + else if(command.getAction() == CmdAction.set) { + Double speed = command.getParam().getSpeed(); + if(speed != null) { + clawDriver_.setSpeed(servoMId, speed); + } + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/ColdTrapHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/ColdTrapHandler.java new file mode 100644 index 0000000..8876279 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/ColdTrapHandler.java @@ -0,0 +1,79 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.ColdTrapDriver; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ColdTrapHandler extends CommandHandler { + private final ColdTrapDriver coldTrapDriver_; + + private final Map supportCmdDeviceMIdMap = Map.ofEntries( + Map.entry(CmdDevice.cold_trap, MId.NotSet) + ); + + private final Set supportActions = Set.of( + CmdAction.open_power, CmdAction.close_power, + CmdAction.set, + CmdAction.open_circle, CmdAction.close_circle, + CmdAction.open_heart, CmdAction.close_heart, + CmdAction.open_cool, CmdAction.close_cool); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + MId getMId(CmdDevice cmdDevice){ + return supportCmdDeviceMIdMap.get(cmdDevice); + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + if (command.getAction() == CmdAction.open_power) { + coldTrapDriver_.openPower(); + } else if (command.getAction() == CmdAction.close_power) { + coldTrapDriver_.closePower(); + } + else if (command.getAction() == CmdAction.set) { + double temperature = command.getParam().getTemperature(); + coldTrapDriver_.setTemperature(temperature); + } + else if (command.getAction() == CmdAction.open_circle) { + coldTrapDriver_.openCircle(); + } + else if (command.getAction() == CmdAction.close_circle) { + coldTrapDriver_.closeCircle(); + } + else if (command.getAction() == CmdAction.open_heart) { + coldTrapDriver_.openHeart(); + } + else if (command.getAction() == CmdAction.close_heart) { + coldTrapDriver_.closeHeart(); + } + else if (command.getAction() == CmdAction.open_cool) { + coldTrapDriver_.openCool(); + } + else if (command.getAction() == CmdAction.close_cool) { + coldTrapDriver_.closeCool(); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/DoorHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/DoorHandler.java new file mode 100644 index 0000000..43a3b22 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/DoorHandler.java @@ -0,0 +1,72 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.DoorDriver; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DoorHandler extends CommandHandler { + private final DoorDriver driver_; + + private final Map stepMotorMIdMap_ = Map.ofEntries( + Map.entry(CmdDevice.door_motor, StepMotorMId.DOOR_MOTOR_MID) + ); + private final Map supportCmdDeviceMIdMap = stepMotorMIdMap_.entrySet().stream(). + collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + private final Set supportActions = Set.of(CmdAction.set, CmdAction.origin, CmdAction.move, CmdAction.move_by, CmdAction.stop); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + StepMotorMId stepMotorMId = stepMotorMIdMap_.get(command.getDevice()); + if (command.getAction() == CmdAction.origin) { + driver_.moveToHome(stepMotorMId); + } + else if(command.getAction() == CmdAction.stop) { + driver_.stop(stepMotorMId); + } + else if(command.getAction() == CmdAction.move) { + double position = command.getParam().getPosition(); + driver_.moveTo(stepMotorMId, position); + } + else if(command.getAction() == CmdAction.move_by) { + double distance = command.getParam().getPosition(); + + driver_.moveBy(stepMotorMId, distance); + } + else if(command.getAction() == CmdAction.set) { + Double speed = command.getParam().getSpeed(); + if(speed != null) { + driver_.setSpeed(stepMotorMId, speed); + } + } + } +} + + + + diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/DualRobotHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/DualRobotHandler.java new file mode 100644 index 0000000..3b48ee3 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/DualRobotHandler.java @@ -0,0 +1,90 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdAxis; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.DualRobotDriver; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + + +@Slf4j +@Component +@RequiredArgsConstructor +public class DualRobotHandler extends CommandHandler { + private final DualRobotDriver dualRobotDriver_; + + private final Map servoMIdMap_ = Map.ofEntries( + Map.entry(CmdDevice.dual_robot, MiniServoMId.NotSet) + ); + private final Map supportCmdDeviceMIdMap = servoMIdMap_.entrySet().stream(). + collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + private final Set supportActions = Set.of(CmdAction.set, CmdAction.origin, CmdAction.move, CmdAction.stop, CmdAction.move_joint); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + private DualRobotDriver.Joint getJoint(CmdAxis axis) { + switch (axis) { + case CmdAxis.joint1 -> { + return DualRobotDriver.Joint.JOINT_1; + } + case CmdAxis.joint2 -> { + return DualRobotDriver.Joint.JOINT_2; + } + default -> { + return null; + } + } + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + if (command.getAction() == CmdAction.origin) { + dualRobotDriver_.moveToHome(MiniServoMId.DUAL_ROBOT_AXIS1_MID); + } + else if(command.getAction() == CmdAction.stop) { + CmdAxis axis = command.getParam().getAxis(); + DualRobotDriver.Joint joint = getJoint(axis); + dualRobotDriver_.stop(joint); + } + else if(command.getAction() == CmdAction.move_joint) { + CmdAxis axis = command.getParam().getAxis(); + double position = command.getParam().getPosition(); + + DualRobotDriver.Joint joint = getJoint(axis); + dualRobotDriver_.moveJointTo(joint, position); + } + else if(command.getAction() == CmdAction.move) { + double pos_x = command.getParam().getPosition(); + double pos_y = command.getParam().getPosition(); + dualRobotDriver_.moveToPoint(pos_x, pos_y); + } + else if(command.getAction() == CmdAction.set) { + CmdAxis axis = command.getParam().getAxis(); + DualRobotDriver.Joint joint = getJoint(axis); + Double speed = command.getParam().getSpeed(); + if(speed != null) { + dualRobotDriver_.setSpeed(joint, speed); + } + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/FanHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/FanHandler.java new file mode 100644 index 0000000..166297e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/FanHandler.java @@ -0,0 +1,59 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.DODriver.FanDriver; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +// + +@Slf4j +@Component +@RequiredArgsConstructor +public class FanHandler extends CommandHandler { + private final FanDriver fanDriver_; + private final Map supportCmdDeviceIOOutputMap = Map.ofEntries( + Map.entry(CmdDevice.fan_1, OutputIOMId.DO_FAN4), + Map.entry(CmdDevice.fan_2, OutputIOMId.DO_FAN5), + Map.entry(CmdDevice.fan_3, OutputIOMId.DO_FAN6), + Map.entry(CmdDevice.fan_4, OutputIOMId.DO_FAN1), + Map.entry(CmdDevice.fan_5, OutputIOMId.DO_FAN2), + Map.entry(CmdDevice.fan_6, OutputIOMId.DO_FAN3) + ); + + private final Map supportCmdDeviceMIdMap = supportCmdDeviceIOOutputMap.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + + private final Set supportActions = Set.of(CmdAction.open, CmdAction.close); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + if (command.getAction() == CmdAction.open) { + fanDriver_.open(supportCmdDeviceIOOutputMap.get(command.getDevice())); + } else if (command.getAction() == CmdAction.close) { + fanDriver_.close(supportCmdDeviceIOOutputMap.get(command.getDevice())); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/FillLightHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/FillLightHandler.java new file mode 100644 index 0000000..7d17cc2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/FillLightHandler.java @@ -0,0 +1,60 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.FillLightDriver; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; + +/** + * 补光灯处理 + */ +@Slf4j +@Component +@RequiredArgsConstructor +class FillLightHandler extends CommandHandler { + private final FillLightDriver fillLightDriver_; + + private final Map supportCmdDeviceMIdMap = Map.ofEntries( + Map.entry(CmdDevice.fill_light, MId.PWMLight) + ); + + private final Set supportActions = Set.of(CmdAction.open, CmdAction.close); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + MId getMId(CmdDevice cmdDevice){ + return supportCmdDeviceMIdMap.get(cmdDevice); + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + if (command.getAction() == CmdAction.open) { + Double d_brightness = command.getParam().getBrightness(); + int i_brightnessInt = d_brightness.intValue(); + + if (d_brightness == null) { + fillLightDriver_.open(getMId(command.getDevice()), i_brightnessInt); + } + } else if (command.getAction() == CmdAction.close) { + fillLightDriver_.close(getMId(command.getDevice())); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/HBotHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/HBotHandler.java new file mode 100644 index 0000000..d3416c4 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/HBotHandler.java @@ -0,0 +1,135 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.LeisaiServoDriver; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.HBotDriver; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.Servo.LeisaiServoMId; +import com.iflytop.sgs.hardware.type.Servo.LeisaiServoSpeedLevel; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.utils.Math.StepMotorConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HBotHandler extends CommandHandler { + private final HBotDriver driver_; + private final LeisaiServoDriver servoDriver_; + + private final Map supportCmdDeviceIOOutputMap = Map.ofEntries( + Map.entry(CmdDevice.gantry_z, OutputIOMId.DO_HBOTZ_MOTOR_CLAMP) + ); + + private final Map stepMotorMIdMap_ = Map.ofEntries( + Map.entry(CmdDevice.gantry_x, StepMotorMId.HBOT_X_MOTOR_MID), + Map.entry(CmdDevice.gantry_y, StepMotorMId.HBOT_Y_MOTOR_MID), + Map.entry(CmdDevice.gantry_z, StepMotorMId.HBOT_Z_MOTOR_MID) + ); + + + + private final Map leisaiServoMIdMap = Map.ofEntries( + Map.entry(CmdDevice.gantry_x, LeisaiServoMId.MainXSV), + Map.entry(CmdDevice.gantry_y, LeisaiServoMId.MainYSV) + ); + private final Map supportCmdDeviceMIdMap = stepMotorMIdMap_.entrySet().stream(). + collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + private final Set supportActions = Set.of(CmdAction.set, CmdAction.origin, CmdAction.move, CmdAction.move_by, CmdAction.stop); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + StepMotorMId stepMotorMId = stepMotorMIdMap_.get(command.getDevice()); + LeisaiServoMId servoMId = leisaiServoMIdMap.get(command.getDevice()); + + + + boolean isServo = command.getDevice() != CmdDevice.gantry_z; + + OutputIOMId clampMId = supportCmdDeviceIOOutputMap.get(command.getDevice()); + + if (command.getAction() == CmdAction.origin) { + if(isServo) + { + log.info("HBotHandler origin servo"); + servoDriver_.enable(servoMId, 1); + servoDriver_.moveToZeroBlock(servoMId); + } + else + { + driver_.moveToHome(stepMotorMId); + + } + + } + else if(command.getAction() == CmdAction.stop) { + if(isServo) + { + log.info("HBotHandler stop servo"); + servoDriver_.moduleStop(servoMId); + } + else + { + driver_.stop(stepMotorMId); + } + } + else if(command.getAction() == CmdAction.move) { + if(isServo) + { + double userPos = command.getParam().getPosition(); + int motor_Pos = StepMotorConverter.toMotorPosition(userPos); + log.info("HBotHandler move to userPos: {}, motor_Pos: {}", userPos, motor_Pos); + servoDriver_.enable(servoMId, 1); + servoDriver_.moveToBlock(servoMId, LeisaiServoSpeedLevel.DEFAULT, motor_Pos); + } + else + { + driver_.moveTo(stepMotorMId, command.getParam().getPosition()); + } + } + else if(command.getAction() == CmdAction.move_by) { + double distance = command.getParam().getPosition(); + + if(isServo) + { + int motor_Pos = StepMotorConverter.toMotorPosition(distance); + log.info("HBotHandler move by distance: {}, motor_Pos: {}", distance, motor_Pos); + servoDriver_.enable(servoMId, 1); + servoDriver_.moveByBlock(servoMId, LeisaiServoSpeedLevel.DEFAULT, motor_Pos); + } + else + { + driver_.moveBy(stepMotorMId, distance); + } + } + else if(command.getAction() == CmdAction.set) { + Double speed = command.getParam().getSpeed(); + if(speed != null) { + driver_.setSpeed(stepMotorMId, speed); + } + } + } +} + diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/HeatRodHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/HeatRodHandler.java new file mode 100644 index 0000000..711ce8c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/HeatRodHandler.java @@ -0,0 +1,77 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.HeaterRodDriver; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.driver.HeaterRodSlavedId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; +import java.util.Map; +import java.util.Set; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HeatRodHandler extends CommandHandler { + private final HeaterRodDriver driver_; + + private final Map supportCmdDeviceMIdMap = Map.ofEntries( + Map.entry(CmdDevice.heat_rod_1, MId.NotSet), + Map.entry(CmdDevice.heat_rod_2, MId.NotSet), + Map.entry(CmdDevice.heat_rod_3, MId.NotSet), + Map.entry(CmdDevice.heat_rod_4, MId.NotSet), + Map.entry(CmdDevice.heat_rod_5, MId.NotSet), + Map.entry(CmdDevice.heat_rod_6, MId.NotSet) + ); + + private final Set supportActions = Set.of(CmdAction.open, CmdAction.close); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + protected void checkParams(DeviceCommand command) throws Exception { + // 检查参数 + if (command.getAction() == CmdAction.open) { + // 检查参数 + } + + } + + MId getMId(CmdDevice cmdDevice){ + return supportCmdDeviceMIdMap.get(cmdDevice); + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + + HeaterRodSlavedId heaterRodId = HeaterRodSlavedId.getByCmdDevice(command.getDevice()); + if(heaterRodId == null) + { + throw new InvalidParameterException("HeatRodSlavedId not found"); + } + + if (command.getAction() == CmdAction.open) { + Double temp = command.getParam().getTemperature(); + driver_.open(heaterRodId, temp); + + } else if (command.getAction() == CmdAction.close) { + driver_.close(heaterRodId); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/HeaterMotorHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/HeaterMotorHandler.java new file mode 100644 index 0000000..16e9a74 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/HeaterMotorHandler.java @@ -0,0 +1,73 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.HeaterMotorDriver; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HeaterMotorHandler extends CommandHandler { + private final HeaterMotorDriver driver_; + + private final Map stepMotorMIdMap_ = Map.ofEntries( + Map.entry(CmdDevice.heater_motor_1, StepMotorMId.HEATER_4_MOTOR_MID),//前后端顺序定义不一致,这里做了映射关系 + Map.entry(CmdDevice.heater_motor_2, StepMotorMId.HEATER_5_MOTOR_MID), + Map.entry(CmdDevice.heater_motor_3, StepMotorMId.HEATER_6_MOTOR_MID), + Map.entry(CmdDevice.heater_motor_4, StepMotorMId.HEATER_1_MOTOR_MID), + Map.entry(CmdDevice.heater_motor_5, StepMotorMId.HEATER_2_MOTOR_MID), + Map.entry(CmdDevice.heater_motor_6, StepMotorMId.HEATER_3_MOTOR_MID) + ); + private final Map supportCmdDeviceMIdMap = stepMotorMIdMap_.entrySet().stream(). + collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + private final Set supportActions = Set.of(CmdAction.set, CmdAction.origin, CmdAction.move, CmdAction.move_by, CmdAction.stop); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + StepMotorMId stepMotorMId = stepMotorMIdMap_.get(command.getDevice()); + if (command.getAction() == CmdAction.origin) { + driver_.moveToHome(stepMotorMId); + } + else if(command.getAction() == CmdAction.stop) { + driver_.stop(stepMotorMId); + } + else if(command.getAction() == CmdAction.move) { + double position = command.getParam().getPosition(); + driver_.moveTo(stepMotorMId, position); + } + else if(command.getAction() == CmdAction.move_by) { + double distance = command.getParam().getPosition(); + + driver_.moveBy(stepMotorMId, distance); + } + else if(command.getAction() == CmdAction.set) { + Double speed = command.getParam().getSpeed(); + if(speed != null) { + driver_.setSpeed(stepMotorMId, speed); + } + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/ShakeMotorHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/ShakeMotorHandler.java new file mode 100644 index 0000000..cd52a0a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/ShakeMotorHandler.java @@ -0,0 +1,69 @@ +package com.iflytop.sgs.hardware.command.handlers; + + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.ShakeMotorDriver; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorDirect; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ShakeMotorHandler extends CommandHandler { + private final ShakeMotorDriver driver_; + + private final Map stepMotorMIdMap_ = Map.ofEntries( + Map.entry(CmdDevice.shake_motor, StepMotorMId.SHAKE_MOTOR_MID) + ); + private final Map supportCmdDeviceMIdMap = stepMotorMIdMap_.entrySet().stream(). + collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + + private final Set supportActions = Set.of(CmdAction.start, CmdAction.stop, CmdAction.set, CmdAction.origin); + + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + StepMotorMId stepMotorMId = stepMotorMIdMap_.get(command.getDevice()); + + if(command.getAction() == CmdAction.stop) { + driver_.stopShake(stepMotorMId); + } + else if(command.getAction() == CmdAction.set) { + Double speed = command.getParam().getSpeed(); + if(speed != null) { + driver_.setSpeed(stepMotorMId, speed); + } + } + else if (command.getAction() == CmdAction.start) { + StepMotorDirect direct = StepMotorDirect.FORWARD; + driver_.startShake(stepMotorMId, direct); + } + else if(command.getAction() == CmdAction.origin){ + driver_.moveToHome(stepMotorMId); + } + } +} + diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/TrayMotorHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/TrayMotorHandler.java new file mode 100644 index 0000000..d3f32ae --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/TrayMotorHandler.java @@ -0,0 +1,77 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.TrayMotorDriver; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TrayMotorHandler extends CommandHandler { + private final TrayMotorDriver driver_; + + + private final Map supportCmdDeviceIOOutputMap = Map.ofEntries( + Map.entry(CmdDevice.tray_motor, OutputIOMId.DO_TRAY_MOTOR_CLAMP) + ); + + private final Map stepMotorMIdMap_ = Map.ofEntries( + Map.entry(CmdDevice.tray_motor, StepMotorMId.TRAY_MOTOR_MID) + ); + private final Map supportCmdDeviceMIdMap = stepMotorMIdMap_.entrySet().stream(). + collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + + private final Set supportActions = Set.of(CmdAction.set, CmdAction.origin, CmdAction.move, CmdAction.move_by, CmdAction.stop); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + StepMotorMId stepMotorMId = stepMotorMIdMap_.get(command.getDevice()); + + OutputIOMId clampMId = supportCmdDeviceIOOutputMap.get(command.getDevice()); + + if (command.getAction() == CmdAction.origin) { + driver_.moveToHome(stepMotorMId); + } + else if(command.getAction() == CmdAction.stop) { + driver_.stop(stepMotorMId); + } + else if(command.getAction() == CmdAction.move) { + double position = command.getParam().getPosition(); + driver_.moveTo(stepMotorMId, position); + } + else if(command.getAction() == CmdAction.move_by) { + double distance = command.getParam().getPosition(); + driver_.moveBy(stepMotorMId, distance); + } + else if(command.getAction() == CmdAction.set) { + Double speed = command.getParam().getSpeed(); + if(speed != null) { + driver_.setSpeed(stepMotorMId, speed); + } + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/TricolorLightHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/TricolorLightHandler.java new file mode 100644 index 0000000..127b425 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/TricolorLightHandler.java @@ -0,0 +1,83 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdColor; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.TricolorLightDriver; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; +import java.util.Map; +import java.util.Set; + +/** + * 三色灯 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class TricolorLightHandler extends CommandHandler { + private final TricolorLightDriver tricolorLightDriver; + + private final Map supportCmdDeviceMIdMap = Map.ofEntries( + Map.entry(CmdDevice.tricolor_light, MId.TriColorLight) + ); + + private final Set supportActions = Set.of(CmdAction.open, CmdAction.close); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + protected void checkParams(DeviceCommand command) throws Exception { + // 检查参数 + if (command.getAction() == CmdAction.open) { + // 检查参数 + CmdColor cmdColor = command.getParam().getColor(); + getCmdColor(cmdColor); + } + + } + + TricolorLightDriver.Color getCmdColor(CmdColor cmdColor){ + switch (cmdColor){ + case red: + return TricolorLightDriver.Color.RED; + case green: + return TricolorLightDriver.Color.GREEN; + case blue: + return TricolorLightDriver.Color.BLUE; + default: + throw new InvalidParameterException("颜色参数错误"); + } + } + + MId getMId(CmdDevice cmdDevice){ + return supportCmdDeviceMIdMap.get(cmdDevice); + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + if (command.getAction() == CmdAction.open) { + CmdColor cmdColor = command.getParam().getColor(); + TricolorLightDriver.Color color = getCmdColor(cmdColor); + tricolorLightDriver.open(getMId(command.getDevice()), color); + } else if (command.getAction() == CmdAction.close) { + tricolorLightDriver.close(getMId(command.getDevice())); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/VentilatorHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/VentilatorHandler.java new file mode 100644 index 0000000..2eecb61 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/VentilatorHandler.java @@ -0,0 +1,52 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.DODriver.VentilatorDriver; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class VentilatorHandler extends CommandHandler { + private final VentilatorDriver ventilatorDriver_; + private final Map supportCmdDeviceIOOutputMap = Map.ofEntries( + Map.entry(CmdDevice.ventilator_power, OutputIOMId.DO_VENTILATOR) + ); + + private final Map supportCmdDeviceMIdMap = supportCmdDeviceIOOutputMap.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + + private final Set supportActions = Set.of(CmdAction.open_power, CmdAction.close_power); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + if (command.getAction() == CmdAction.open) { + ventilatorDriver_.openPower(supportCmdDeviceIOOutputMap.get(command.getDevice())); + } else if (command.getAction() == CmdAction.close) { + ventilatorDriver_.closePower(supportCmdDeviceIOOutputMap.get(command.getDevice())); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/command/handlers/WaterPumpHandler.java b/src/main/java/com/iflytop/sgs/hardware/command/handlers/WaterPumpHandler.java new file mode 100644 index 0000000..58af418 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/command/handlers/WaterPumpHandler.java @@ -0,0 +1,52 @@ +package com.iflytop.sgs.hardware.command.handlers; + +import com.iflytop.sgs.common.cmd.DeviceCommand; +import com.iflytop.sgs.common.enums.cmd.CmdAction; +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.command.CommandHandler; +import com.iflytop.sgs.hardware.drivers.DODriver.WaterPumpDriver; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class WaterPumpHandler extends CommandHandler { + private final WaterPumpDriver waterPumpDriver_; + private final Map supportCmdDeviceIOOutputMap = Map.ofEntries( + Map.entry(CmdDevice.ventilator_power, OutputIOMId.DO_VENTILATOR) + ); + + private final Map supportCmdDeviceMIdMap = supportCmdDeviceIOOutputMap.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid)); + + private final Set supportActions = Set.of(CmdAction.open_power, CmdAction.close_power); + + @Override + protected Map getSupportCmdDeviceMIdMap() + { + return supportCmdDeviceMIdMap; + } + + @Override + protected Set getSupportActions() { + return supportActions; + } + + @Override + public void handleCommand(DeviceCommand command) throws Exception { + // 发送命令 + if (command.getAction() == CmdAction.open) { + waterPumpDriver_.openPower(supportCmdDeviceIOOutputMap.get(command.getDevice())); + } else if (command.getAction() == CmdAction.close) { + waterPumpDriver_.closePower(supportCmdDeviceIOOutputMap.get(command.getDevice())); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/config/A8kSubModuleInitRegConfig.java b/src/main/java/com/iflytop/sgs/hardware/config/A8kSubModuleInitRegConfig.java new file mode 100644 index 0000000..e4bfbe1 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/config/A8kSubModuleInitRegConfig.java @@ -0,0 +1,112 @@ +package com.iflytop.sgs.hardware.config; + +import com.iflytop.sgs.hardware.type.ModuleType; +import com.iflytop.sgs.hardware.type.RegIndex; +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class A8kSubModuleInitRegConfig { + static class ModuleRegMarker { + public ModuleType moduleType; + public RegIndex regIndex; + + ModuleRegMarker(ModuleType moduleType, RegIndex regIndex) { + this.moduleType = moduleType; + this.regIndex = regIndex; + } + } + + private final List regMarkers = new ArrayList(); + + public List findRegIndexByModuleType(ModuleType moduleType) { + List regIndexes = new ArrayList<>(); + for (ModuleRegMarker marker : regMarkers) { + if (marker.moduleType == moduleType) { + regIndexes.add(marker.regIndex); + } + } + return regIndexes; + } + + public Boolean isNeeded(ModuleType moduleType, RegIndex regIndex) { + for (ModuleRegMarker marker : regMarkers) { + if (marker.moduleType == moduleType && marker.regIndex == regIndex) { + return true; + } + } + return false; + } + + @PostConstruct + void init() { + regMarkers(); + } + + + private void regMarkers() { + // + // 标记数据库关心的寄存器,被标记的寄存器,会在设备初始化时侯,初始化其数值 + // + //HBOT + //TMCStepMotor + // addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_shift); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_shaft); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_one_circle_pulse); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_one_circle_pulse_denominator); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_default_velocity); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_low_velocity); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_mid_velocity); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_high_velocity); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_ihold); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_irun); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_iholddelay); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_iglobalscaler); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_mres); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_run_to_zero_speed); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_look_zero_edge_speed); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_max_d); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_min_d); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_in_debug_mode); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_vstart); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_a1); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_amax); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_v1); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_dmax); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_d1); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_vstop); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_tzerowait); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_enc_resolution); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_enable_enc); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_dzero_pos); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kret_step_motor_pos_devi_tolerance); + addRegMarker(ModuleType.TMCStepMotor, RegIndex.kret_step_motor_io_trigger_append_distance); + + + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_limit_velocity); + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_limit_torque); + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_protective_torque); + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_target_pos_tolerance); + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_min_angle); // 最小角度限制 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_max_angle); // 最大角度限制 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_max_temp); // 最高温度上限 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_max_voltage); // 最高输入电压 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_min_voltage); // 最低输入电压 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_max_torque); // 最大扭矩 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_unload_condition); // 卸载条件 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_protect_current); // 保护电流 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_protect_torque); // 保护扭矩 0->100 ,触发后,需要写入与组转方向相反的位置指令,进行解除 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_protect_time); // 保护时间 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_overload_torque); // 过载扭矩 + addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_acc); // 加速度 + + } + + private void addRegMarker(ModuleType moduleTYpe, RegIndex regIndex) { + regMarkers.add(new ModuleRegMarker(moduleTYpe, regIndex)); + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/config/StepMotorConfig.java b/src/main/java/com/iflytop/sgs/hardware/config/StepMotorConfig.java new file mode 100644 index 0000000..2ddf165 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/config/StepMotorConfig.java @@ -0,0 +1,261 @@ +package com.iflytop.sgs.hardware.config; + +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Slf4j +@Component +public class StepMotorConfig { + // 导程 配置 + private final static double DEFAULT_LEAD = 10.0; // mm + final static Map stepMotorLeadMap = Map.ofEntries( + Map.entry(StepMotorMId.TRAY_MOTOR_MID, 5.0), + Map.entry(StepMotorMId.HBOT_X_MOTOR_MID, 5.0), + Map.entry(StepMotorMId.HBOT_Y_MOTOR_MID, 4.0), + Map.entry(StepMotorMId.HBOT_Z_MOTOR_MID, 5.0), + Map.entry(StepMotorMId.HEATER_1_MOTOR_MID, 5.0) + ); + // 电流配置 + final static int DEFAULT_CURRENT = 8; // [0-31] + final static int HEATER_MOTOR_DEFAULT_CURRENT = 20; + final static int ACID_PUMP_MOTOR_DEFAULT_CURRENT = 8; + final static Map stepMotorCurrentMap = Map.ofEntries( + Map.entry(StepMotorMId.SHAKE_MOTOR_MID, 8), + Map.entry(StepMotorMId.HBOT_X_MOTOR_MID, 31), + Map.entry(StepMotorMId.HBOT_Y_MOTOR_MID, 31), + Map.entry(StepMotorMId.HBOT_Z_MOTOR_MID, 31), + Map.entry(StepMotorMId.HEATER_1_MOTOR_MID, HEATER_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.HEATER_2_MOTOR_MID, HEATER_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.HEATER_3_MOTOR_MID, HEATER_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.HEATER_4_MOTOR_MID, HEATER_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.HEATER_5_MOTOR_MID, HEATER_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.HEATER_6_MOTOR_MID, HEATER_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.ACID_PUMP_1_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.ACID_PUMP_2_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.ACID_PUMP_3_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.ACID_PUMP_4_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.ACID_PUMP_5_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.ACID_PUMP_6_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.ACID_PUMP_7_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_CURRENT), + Map.entry(StepMotorMId.ACID_PUMP_8_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_CURRENT) + ); + + // 速度配置 + final static double DEFAULT_SPEED = 1.0; // mm/s + final static double HEATER_MOTOR_DEFAULT_SPEED = 1.0; + final static double ACID_PUMP_MOTOR_DEFAULT_SPEED = 1.0; + final static Map stepMotorSpeedMap = Map.ofEntries( + Map.entry(StepMotorMId.SHAKE_MOTOR_MID, 1.0), + Map.entry(StepMotorMId.HBOT_X_MOTOR_MID, 20.0), + Map.entry(StepMotorMId.HBOT_Y_MOTOR_MID, 20.0), + Map.entry(StepMotorMId.HBOT_Z_MOTOR_MID, 20.0), + Map.entry(StepMotorMId.HEATER_1_MOTOR_MID, HEATER_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.HEATER_2_MOTOR_MID, HEATER_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.HEATER_3_MOTOR_MID, HEATER_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.HEATER_4_MOTOR_MID, HEATER_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.HEATER_5_MOTOR_MID, HEATER_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.HEATER_6_MOTOR_MID, HEATER_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.ACID_PUMP_1_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.ACID_PUMP_2_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.ACID_PUMP_3_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.ACID_PUMP_4_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.ACID_PUMP_5_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.ACID_PUMP_6_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.ACID_PUMP_7_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_SPEED), + Map.entry(StepMotorMId.ACID_PUMP_8_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_SPEED) + ); + + // 最小限位配置 + static final double DEFAULT_MIN_LIMIT = 0.0; // mm + static final double HEATER_MOTOR_DEFAULT_MIN_LIMIT = 0.0; + static final double ACID_PUMP_MOTOR_DEFAULT_MIN_LIMIT = 0.0; + final static Map stepMotorMinLimitMap = Map.ofEntries( + Map.entry(StepMotorMId.HBOT_X_MOTOR_MID, 0.0), + Map.entry(StepMotorMId.HBOT_Y_MOTOR_MID, 0.0), + Map.entry(StepMotorMId.HBOT_Z_MOTOR_MID, 0.0) + ); + + // 最大限位配置 + static final double DEFAULT_MAX_LIMIT = 0.0; // mm + static final double HEATER_MOTOR_DEFAULT_MAX_LIMIT = 100.0; + static final double ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT = 1000000; + final static Map stepMotorMaxLimitMap = Map.ofEntries( + Map.entry(StepMotorMId.DOOR_MOTOR_MID, 500.0), + Map.entry(StepMotorMId.SHAKE_MOTOR_MID, 20000.0), + Map.entry(StepMotorMId.TRAY_MOTOR_MID, 385.0), + Map.entry(StepMotorMId.HBOT_X_MOTOR_MID, 1400.0), + Map.entry(StepMotorMId.HBOT_Y_MOTOR_MID, 700.0), + Map.entry(StepMotorMId.HBOT_Z_MOTOR_MID, 300.0), + Map.entry(StepMotorMId.HEATER_1_MOTOR_MID, HEATER_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.HEATER_2_MOTOR_MID, HEATER_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.HEATER_3_MOTOR_MID, HEATER_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.HEATER_4_MOTOR_MID, HEATER_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.HEATER_5_MOTOR_MID, HEATER_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.HEATER_6_MOTOR_MID, HEATER_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.ACID_PUMP_1_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.ACID_PUMP_2_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.ACID_PUMP_3_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.ACID_PUMP_4_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.ACID_PUMP_5_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.ACID_PUMP_6_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.ACID_PUMP_7_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT), + Map.entry(StepMotorMId.ACID_PUMP_8_MOTOR_MID, ACID_PUMP_MOTOR_DEFAULT_MAX_LIMIT) + ); + + // 是否安装编码器 + private final static boolean DEFAULT_ENCODER_INSTALLED = false; + static Map stepMotorEncoderMap = Map.ofEntries( + Map.entry(StepMotorMId.HBOT_X_MOTOR_MID, true), + Map.entry(StepMotorMId.HBOT_Y_MOTOR_MID, true), + Map.entry(StepMotorMId.HBOT_Z_MOTOR_MID, true) + ); + + /* ******************** ******************** ******************** */ + /* ******************** 导程 ******************** */ + /* ******************** ******************** ******************** */ + + // 从数据库拉取导程信息 + private Double getLeadFromDB(StepMotorMId stepMotorId) + { + return null; + } + + // 从默认配置中获取导程信息 + private Double getLeadFromDefault(StepMotorMId stepMotorId) + { + return stepMotorLeadMap.getOrDefault(stepMotorId, DEFAULT_LEAD); + } + + // 获取导程信息 + public double getLead(StepMotorMId stepMotorId) + { + Double lead = getLeadFromDB(stepMotorId); + if (lead != null) { + return lead; + } else { + return getLeadFromDefault(stepMotorId); + } + } + + /* ******************** ******************** ******************** */ + /* ******************** 电流 ******************** */ + /* ******************** ******************** ******************** */ + // 从数据库拉取电流信息 + private Integer getCurrentFromDB(StepMotorMId stepMotorId) + { + return null; + } + + private Integer getCurrentFromDefault(StepMotorMId stepMotorId) + { + return stepMotorCurrentMap.getOrDefault(stepMotorId, DEFAULT_CURRENT); + } + + public int getCurrent(StepMotorMId stepMotorId) + { + Integer current = getCurrentFromDB(stepMotorId); + if (current != null) { + return current; + } else { + return getCurrentFromDefault(stepMotorId); + } + } + + /* ******************** ******************** ******************** */ + /* ******************** 速度 ******************** */ + /* ******************** ******************** ******************** */ + // 从数据库拉取速度信息 + private Double getSpeedFromDB(StepMotorMId stepMotorId) + { + return null; + } + private Double getSpeedFromDefault(StepMotorMId stepMotorId) + { + return stepMotorSpeedMap.getOrDefault(stepMotorId, DEFAULT_SPEED); + } + + public double getSpeed(StepMotorMId stepMotorId) + { + Double speed = getSpeedFromDB(stepMotorId); + if (speed != null) { + return speed; + } else { + return getSpeedFromDefault(stepMotorId); + } + } + + /* ******************** ******************** ******************** */ + /* ******************** 最小限位 ******************** */ + /* ******************** ******************** ******************** */ + // 从数据库拉取最小限位信息 + private Double getMinLimitFromDB(StepMotorMId stepMotorId) + { + return null; + } + + private Double getMinLimitFromDefault(StepMotorMId stepMotorId) + { + return stepMotorMinLimitMap.getOrDefault(stepMotorId, DEFAULT_MIN_LIMIT); + } + + public double getMinLimit(StepMotorMId stepMotorId) + { + Double minLimit = getMinLimitFromDB(stepMotorId); + if (minLimit != null) { + return minLimit; + } else { + return getMinLimitFromDefault(stepMotorId); + } + } + + /* ******************** ******************** ******************** */ + /* ******************** 最大限位 ******************** */ + /* ******************** ******************** ******************** */ + // 从数据库拉取最大限位信息 + private Double getMaxLimitFromDB(StepMotorMId stepMotorId) + { + return null; + } + + private Double getMaxLimitFromDefault(StepMotorMId stepMotorId) + { + return stepMotorMaxLimitMap.getOrDefault(stepMotorId, DEFAULT_MAX_LIMIT); + } + + public double getMaxLimit(StepMotorMId stepMotorId) + { + Double maxLimit = getMaxLimitFromDB(stepMotorId); + if (maxLimit != null) { + return maxLimit; + } else { + return getMaxLimitFromDefault(stepMotorId); + } + } + + /* ******************** ******************** ******************** */ + /* ******************** 编码器 ******************** */ + /* ******************** ******************** ******************** */ + // 从数据库拉取编码器信息 + private Boolean getEncoderFromDB(StepMotorMId stepMotorId) + { + return null; + } + + private Boolean getEncoderFromDefault(StepMotorMId stepMotorId) + { + return stepMotorEncoderMap.getOrDefault(stepMotorId, DEFAULT_ENCODER_INSTALLED); + } + + public boolean isEncoderInstalled(StepMotorMId stepMotorId) + { + Boolean encoder = getEncoderFromDB(stepMotorId); + if (encoder != null) { + return encoder; + } else { + return getEncoderFromDefault(stepMotorId); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/constants/ActionOvertimeConstant.java b/src/main/java/com/iflytop/sgs/hardware/constants/ActionOvertimeConstant.java new file mode 100644 index 0000000..e711b91 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/constants/ActionOvertimeConstant.java @@ -0,0 +1,61 @@ +package com.iflytop.sgs.hardware.constants; + + +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.Servo.LeisaiServoMId; +import com.iflytop.sgs.hardware.type.Servo.LiquidArmMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class ActionOvertimeConstant { + final Integer defaultOvertime = 80 * 1000; + + static class OvertimeConfigItem { + public MId mid; + public CmdId cmdId; + public Integer overtime; + } + + List overtimeConfigItems = new ArrayList<>(); + + public Integer get(MId mid, CmdId cmdId) { + return priGetOvertime(mid, cmdId); + } + + public Integer get(StepMotorMId mid, CmdId cmdId) { + return priGetOvertime(mid.mid, cmdId); + } + + public void pushNewConfig(LeisaiServoMId mid, CmdId cmdId, Integer overtime) { + pushNewConfig(mid.mid, cmdId, overtime); + } + public void pushNewConfig(LiquidArmMId mid, CmdId cmdId, Integer overtime) { + pushNewConfig(mid.mid, cmdId, overtime); + } + public void pushNewConfig(StepMotorMId mid, CmdId cmdId, Integer overtime) { + pushNewConfig(mid.mid, cmdId, overtime); + } + + private void pushNewConfig(MId mid, CmdId cmdId, Integer overtime) { + OvertimeConfigItem item = new OvertimeConfigItem(); + item.mid = mid; + item.cmdId = cmdId; + item.overtime = overtime; + overtimeConfigItems.add(item); + } + + private Integer priGetOvertime(MId mid, CmdId cmdId) { + for (OvertimeConfigItem item : overtimeConfigItems) { + if (item.mid == mid && item.cmdId == cmdId) { + return item.overtime; + } + } + return defaultOvertime; + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/constants/FilePathConstant.java b/src/main/java/com/iflytop/sgs/hardware/constants/FilePathConstant.java new file mode 100644 index 0000000..d91b8ba --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/constants/FilePathConstant.java @@ -0,0 +1,5 @@ +package com.iflytop.sgs.hardware.constants; + +public class FilePathConstant { + public static final String FILE_DB_EXPORT_PATH = "runenv/files/db"; //存储数据库导出文件 +} diff --git a/src/main/java/com/iflytop/sgs/hardware/constants/MiniServoConstant.java b/src/main/java/com/iflytop/sgs/hardware/constants/MiniServoConstant.java new file mode 100644 index 0000000..c22e92f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/constants/MiniServoConstant.java @@ -0,0 +1,51 @@ +package com.iflytop.sgs.hardware.constants; + +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import org.springframework.util.Assert; + +import java.util.Map; + +public class MiniServoConstant { + static public final Integer actionOvertime = 20000; + static class MiniServoFixPosConfig { + public Integer zeroPos; + public Integer refPos; + } + + static Map map = Map.of( +// //摇匀模组Y轴 +// MiniServoMId.ShakeModGripperYSV, new MiniServoFixPosConfig() {{ +// zeroPos = 110; +// refPos = 100;//Y轴最外侧的位置 +// }}, +// //摇匀模组夹爪 +// MiniServoMId.ShakeModGripperSV, new MiniServoFixPosConfig() {{ +// zeroPos = 1600; +// refPos = 1800;//完全张开的位置 +// }}, +// //试管架夹紧 +// MiniServoMId.ShakeModTubeScanerClampingSV, new MiniServoFixPosConfig() {{ +// zeroPos = 1800; +// refPos = 1800;//水平位置 +// }}, +// //试管顶升舵机 +// MiniServoMId.ShakeModLiftingSV, new MiniServoFixPosConfig() {{ +// zeroPos = 644;//底部 644/3000 = 220/1024 +// refPos = 644;//底部 +// }} + ); + + static public Integer getZeroPos(MiniServoMId mid) { + var config = map.get(mid); + Assert.notNull(config, "getZeroPos fail"); + return config.zeroPos; + } + + static public Integer getRefPos(MiniServoMId mid) { + var config = map.get(mid); + Assert.notNull(config, "getRefPos fail"); + return config.refPos; + } + + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/controller/AcidPumpController.java b/src/main/java/com/iflytop/sgs/hardware/controller/AcidPumpController.java new file mode 100644 index 0000000..4b7fd60 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/controller/AcidPumpController.java @@ -0,0 +1,84 @@ +package com.iflytop.sgs.hardware.controller; + +import com.iflytop.sgs.app.service.DeviceParamConfigService; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.AcidPumpDriver; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "加酸泵控制") +@RestController +@RequestMapping("/api/acid-pump") +@RequiredArgsConstructor +@Slf4j +public class AcidPumpController { + private final AcidPumpDriver driver; + private final DeviceParamConfigService deviceParamConfigService; + + @PostMapping("/set-speed") + public void setSpeed(@RequestParam StepMotorMId stepMotorMId, @RequestParam int speed) { + log.info("Setting acid pump speed to: {}", speed); + try { + driver.setSpeed(stepMotorMId, speed); + deviceParamConfigService.setModuleAndReg(stepMotorMId.mid.name(), StepMotorRegIndex.kreg_step_motor_default_velocity.regIndex.name(),speed); + } catch (Exception e) { + log.error("Failed to set speed for acid pump: {}", e.getMessage()); + } + } + + @PostMapping("/motor-enable") + public void motorEnable(@RequestParam StepMotorMId stepMotorMId, @RequestParam boolean enable) { + log.info("Setting acid pump motor enable to: {}", enable); + try { + driver.motorEnable(stepMotorMId, enable); + } catch (Exception e) { + log.error("Failed to enable motor for acid pump: {}", e.getMessage()); + } + } + + @PostMapping("/move-to-home") + public void moveToHome(@RequestParam StepMotorMId stepMotorMId) { + log.info("Moving acid pump to home position"); + try { + driver.moveToHome(stepMotorMId); + } catch (Exception e) { + log.error("Failed to move acid pump to home: {}", e.getMessage()); + } + } + + @PostMapping("/move-to") + public void moveTo(@RequestParam StepMotorMId stepMotorMId, @RequestParam int position) { + log.info("Moving acid pump to position: {}", position); + try { + driver.moveTo(stepMotorMId, position); + } catch (Exception e) { + log.error("Failed to move acid pump to position: {}", e.getMessage()); + } + } + + @PostMapping("/move-by") + public void moveBy(@RequestParam StepMotorMId stepMotorMId, @RequestParam int offset) { + log.info("Moving acid pump by offset: {}", offset); + try { + driver.moveBy(stepMotorMId, offset); + } catch (Exception e) { + log.error("Failed to move acid pump by offset: {}", e.getMessage()); + } + } + + @PostMapping("/stop") + public void stop(@RequestParam StepMotorMId stepMotorMId) { + log.info("Stopping acid pump"); + try { + driver.stop(stepMotorMId); + } catch (Exception e) { + log.error("Failed to stop acid pump: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/controller/DualRobotController.java b/src/main/java/com/iflytop/sgs/hardware/controller/DualRobotController.java new file mode 100644 index 0000000..8416451 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/controller/DualRobotController.java @@ -0,0 +1,91 @@ +package com.iflytop.sgs.hardware.controller; + +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.DualRobotDriver; +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.DualRobotDriver.Joint; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "双轴机械臂控制") +@RestController +@RequestMapping("/api/dual-robot") +@RequiredArgsConstructor +@Slf4j +public class DualRobotController { + private final DualRobotDriver driver; + + @PostMapping("/move-joint-to") + public void moveJointTo(@RequestParam MiniServoMId sevoMid, @RequestParam double position) { + log.info("Moving joint {} to position {}", sevoMid, position); + try { + driver.moveJointTo(sevoMid, position); + } catch (Exception e) { + log.error("Failed to move joint {} to position {}: {}", sevoMid, position, e.getMessage()); + } + } + + @PostMapping("/stop-servo") + public void stop(@RequestParam MiniServoMId sevoMid) { + log.info("Stopping servo {}", sevoMid); + try { + driver.stop(sevoMid); + } catch (Exception e) { + log.error("Failed to stop servo {}: {}", sevoMid, e.getMessage()); + } + } + + @PostMapping("/set-speed") + public void setSpeed(@RequestParam Joint joint, @RequestParam double speed) { + log.info("Setting speed for joint {} to {}", joint, speed); + try { + driver.setSpeed(joint, speed); + } catch (Exception e) { + log.error("Failed to set speed for joint {}: {}", joint, e.getMessage()); + } + } + + @PostMapping("/move-to-point") + public void moveToPoint(@RequestParam double pointX, @RequestParam double pointY) { + log.info("Moving to point ({}, {})", pointX, pointY); + try { + driver.moveToPoint(pointX, pointY); + } catch (Exception e) { + log.error("Failed to move to point ({}, {}): {}", pointX, pointY, e.getMessage()); + } + } + + @PostMapping("/move-to-home") + public void moveToHome(@RequestParam Joint joint) { + log.info("Moving joint {} to home position", joint); + try { + driver.moveToHome(joint); + } catch (Exception e) { + log.error("Failed to move joint {} to home position: {}", joint, e.getMessage()); + } + } + + @PostMapping("/move-joint-to-position") + public void moveJointTo(@RequestParam Joint joint, @RequestParam double position) { + log.info("Moving joint {} to position {}", joint, position); + try { + driver.moveJointTo(joint, position); + } catch (Exception e) { + log.error("Failed to move joint {} to position {}: {}", joint, position, e.getMessage()); + } + } + + @PostMapping("/stop-joint") + public void stop(@RequestParam Joint joint) { + log.info("Stopping joint {}", joint); + try { + driver.stop(joint); + } catch (Exception e) { + log.error("Failed to stop joint {}: {}", joint, e.getMessage()); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/controller/HeaterMotorController.java b/src/main/java/com/iflytop/sgs/hardware/controller/HeaterMotorController.java new file mode 100644 index 0000000..df259c6 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/controller/HeaterMotorController.java @@ -0,0 +1,70 @@ +package com.iflytop.sgs.hardware.controller; + +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.HeaterMotorDriver; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "加热电机控制") +@RestController +@RequestMapping("/api/heater-motor") +@RequiredArgsConstructor +@Slf4j +public class HeaterMotorController { + private final HeaterMotorDriver driver; + + @PostMapping("/motor-enable") + public void motorEnable(@RequestParam StepMotorMId stepMotorMId, @RequestParam boolean enable) { + log.info("Setting heater motor enable to: {}", enable); + try { + driver.motorEnable(stepMotorMId, enable); + } catch (Exception e) { + log.error("Failed to enable motor for heater motor: {}", e.getMessage()); + } + } + + @PostMapping("/move-to-home") + public void moveToHome(@RequestParam StepMotorMId stepMotorMId) { + log.info("Moving heater motor to home position"); + try { + driver.moveToHome(stepMotorMId); + } catch (Exception e) { + log.error("Failed to move heater motor to home: {}", e.getMessage()); + } + } + + @PostMapping("/move-to") + public void moveTo(@RequestParam StepMotorMId stepMotorMId, @RequestParam int position) { + log.info("Moving heater motor to position: {}", position); + try { + driver.moveTo(stepMotorMId, position); + } catch (Exception e) { + log.error("Failed to move heater motor to position: {}", e.getMessage()); + } + } + + @PostMapping("/move-by") + public void moveBy(@RequestParam StepMotorMId stepMotorMId, @RequestParam int offset) { + log.info("Moving heater motor by offset: {}", offset); + try { + driver.moveBy(stepMotorMId, offset); + } catch (Exception e) { + log.error("Failed to move heater motor by offset: {}", e.getMessage()); + } + } + + @PostMapping("/stop") + public void stop(@RequestParam StepMotorMId stepMotorMId) { + log.info("Stopping heater motor"); + try { + driver.stop(stepMotorMId); + } catch (Exception e) { + log.error("Failed to stop heater motor: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/controller/HeaterRodController.java b/src/main/java/com/iflytop/sgs/hardware/controller/HeaterRodController.java new file mode 100644 index 0000000..9bc932c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/controller/HeaterRodController.java @@ -0,0 +1,61 @@ +package com.iflytop.sgs.hardware.controller; + +import com.iflytop.sgs.hardware.drivers.HeaterRodDriver; +import com.iflytop.sgs.hardware.type.driver.HeaterRodSlavedId; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "加热棒控制") +@RestController +@RequestMapping("/api/heater-rod") +@RequiredArgsConstructor +@Slf4j +public class HeaterRodController { + private final HeaterRodDriver driver; + + @PostMapping("/get-temperature") + public double getTemperature(@RequestParam HeaterRodSlavedId heaterRodId) { + log.info("Getting temperature for heater rod with ID: {}", heaterRodId.name()); + try { + return driver.getTemperature(heaterRodId); + } catch (Exception e) { + log.error("Failed to get temperature for heater rod: {}", e.getMessage()); + return Double.NaN; + } + } + + @PostMapping("/open") + public void open(@RequestParam HeaterRodSlavedId heaterRodId, @RequestParam double temperature) { + log.info("Opening heater rod with ID: {} and setting temperature to: {}", heaterRodId.name(), temperature); + try { + driver.open(heaterRodId, temperature); + } catch (Exception e) { + log.error("Failed to open heater rod: {}", e.getMessage()); + } + } + + @PostMapping("/close") + public void close(@RequestParam HeaterRodSlavedId heaterRodId) { + log.info("Closing heater rod with ID: {}", heaterRodId.name()); + try { + driver.close(heaterRodId); + } catch (Exception e) { + log.error("Failed to close heater rod: {}", e.getMessage()); + } + } + + @PostMapping("/set-device-address") + public void setDeviceAddress(@RequestParam HeaterRodSlavedId heaterRodId, @RequestParam int newAddress) { + log.info("Setting device address for heater rod with ID: {} to: {}", heaterRodId.name(), newAddress); + try { + driver.setDeviceAddress(heaterRodId, newAddress); + } catch (Exception e) { + log.error("Failed to set device address for heater rod: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/controller/IOController.java b/src/main/java/com/iflytop/sgs/hardware/controller/IOController.java new file mode 100644 index 0000000..f5a9150 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/controller/IOController.java @@ -0,0 +1,32 @@ +package com.iflytop.sgs.hardware.controller; + +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.hardware.drivers.DODriver.OutputIOCtrlDriver; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "IO控制") +@RestController +@RequestMapping("/api/io") +@RequiredArgsConstructor +@Slf4j +public class IOController { + private final OutputIOCtrlDriver driver; + + @PostMapping("/set-output") + public Result setOutput(@RequestParam OutputIOMId index, @RequestParam boolean state) { + log.info("Setting IO output at index: {} to state: {}", index, state); + try { + driver.setIOState(index, state); + } catch (Exception e) { + log.error("Failed to set IO output: {}", e.getMessage()); + } + return Result.success(); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/controller/ServoController.java b/src/main/java/com/iflytop/sgs/hardware/controller/ServoController.java new file mode 100644 index 0000000..c3016b6 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/controller/ServoController.java @@ -0,0 +1,161 @@ +package com.iflytop.sgs.hardware.controller; + +import com.iflytop.sgs.app.service.DeviceParamConfigService; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.service.ServoService; +import com.iflytop.sgs.hardware.type.Servo.DeviceServoId; +import com.iflytop.sgs.hardware.type.Servo.MiniServoRegIndex; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Tag(name = "伺服电机控制") +@RestController +@RequestMapping("/api/servo") +@RequiredArgsConstructor +@Slf4j +public class ServoController { + private final ServoService servoService; + private final DeviceParamConfigService deviceParamConfigService; + + // 获取设备列表 + @PostMapping("/get-device-list") + @Operation(summary = "获取设备列表") + public Map getDeviceList() { + Map map = new HashMap<>(); + for(DeviceServoId id : DeviceServoId.values()) { + map.put(id.name(), id.getDescription()); + } + return map; + } + + // 获取寄存器列表 + @PostMapping("/get-reg-list") + @Operation(summary = "获取寄存器列表") + public List getRegList() { + List list = new ArrayList<>(); + for(MiniServoRegIndex reg : MiniServoRegIndex.values()) { + list.add(reg.name()); + } + return list; + } + + // 基础操作 + @PostMapping("/enable") + @Operation(summary = "电源开启") + public Result enable(@RequestParam DeviceServoId deviceId) throws HardwareException { + servoService.enable(deviceId, true); + return Result.success(); + } + + @PostMapping("/disable") + @Operation(summary = "电源关闭") + public Result disable(@RequestParam DeviceServoId deviceId) throws HardwareException { + servoService.enable(deviceId, false); + return Result.success(); + } + + @PostMapping("/stop") + @Operation(summary = "停止") + public Result stop(@RequestParam DeviceServoId deviceId) throws HardwareException { + servoService.stop(deviceId); + return Result.success(); + } + + @PostMapping("/move-to") + @Operation(summary = "移动(绝对)") + public Result moveTo(@RequestParam DeviceServoId deviceId, @RequestParam Integer pos) throws HardwareException { + servoService.moveTo(deviceId, pos); + return Result.success(); + } + + @PostMapping("/rotate-forward") + @Operation(summary = "正转") + public Result rotateForward(@RequestParam DeviceServoId deviceId) throws HardwareException { + servoService.rotate(deviceId, 1); + return Result.success(); + } + + @PostMapping("/rotate-backward") + @Operation(summary = "反转") + public Result rotateBackward(@RequestParam DeviceServoId deviceId) throws HardwareException { + servoService.rotate(deviceId, 0); + return Result.success(); + } + @PostMapping("/rotate-with-torque") + @Operation(summary = "旋转(力矩)") + public Result rotateWithTorque(@RequestParam DeviceServoId deviceId, @RequestParam Integer pos) throws HardwareException { + servoService.rotateWithTorque(deviceId, pos); + return Result.success(); + } + + @PostMapping("/move-to-zero") + @Operation(summary = "回Home") + public Result moveToZero(@RequestParam DeviceServoId deviceId) throws HardwareException { + servoService.moveToZero(deviceId); + return Result.success(); + } + + // 寄存器操作 + @PostMapping("/limit-velocity") + @Operation(summary = "设置限速") + public Result setLimitVelocity( + @RequestParam DeviceServoId deviceId, + @RequestParam Integer val) throws HardwareException { + servoService.setLimitVelocity(deviceId, val); + deviceParamConfigService.setModuleAndReg(deviceId.getServoMId().mid.name(), MiniServoRegIndex.kreg_mini_servo_limit_velocity.regIndex.name(),val); + return Result.success(); + } + + @PostMapping("/limit-torque") + @Operation(summary = "设置限力矩") + public Result setLimitTorque( + @RequestParam DeviceServoId deviceId, + @RequestParam Integer val) throws HardwareException { + servoService.setLimitTorque(deviceId, val); + deviceParamConfigService.setModuleAndReg(deviceId.getServoMId().mid.name(), MiniServoRegIndex.kreg_mini_servo_limit_torque.regIndex.name(),val); + return Result.success(); + } + + @PostMapping("/protective-torque") + @Operation(summary = "设置保护力矩") + public Result setProtectiveTorque( + @RequestParam DeviceServoId deviceId, + @RequestParam Integer val) throws HardwareException { + servoService.setProtectiveTorque(deviceId, val); + deviceParamConfigService.setModuleAndReg(deviceId.getServoMId().mid.name(), MiniServoRegIndex.kreg_mini_servo_protective_torque.regIndex.name(),val); + return Result.success(); + } + + @PostMapping("/reg") + @Operation(summary = "设置寄存器") + public Result setReg( + @RequestParam DeviceServoId deviceId, + @RequestParam MiniServoRegIndex reg, + @RequestParam Integer val) throws HardwareException { + servoService.setReg(deviceId, reg, val); + deviceParamConfigService.setModuleAndReg(deviceId.getServoMId().mid.name(), reg.regIndex.name(),val); + return Result.success(); + } + + // 状态查询 + @GetMapping("/position") + @Operation(summary = "读取位置") + public Integer readPos(@RequestParam DeviceServoId deviceId) throws HardwareException { + return servoService.readPos(deviceId); + } + + @GetMapping("/regs") + @Operation(summary = "获取所有寄存器") + public Map getAllReg(@RequestParam DeviceServoId deviceId) { + return servoService.getAllReg(deviceId); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/controller/StepMotorController.java b/src/main/java/com/iflytop/sgs/hardware/controller/StepMotorController.java new file mode 100644 index 0000000..a0ddfa9 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/controller/StepMotorController.java @@ -0,0 +1,312 @@ +package com.iflytop.sgs.hardware.controller; + +import com.iflytop.sgs.app.service.DeviceParamConfigService; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.service.StepMotorService; +import com.iflytop.sgs.hardware.type.StepMotor.DeviceStepMotorId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorSpeedLevel; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Tag(name = "步进电机控制") +@RestController +@RequestMapping("/api/step-motor") +@RequiredArgsConstructor +@Slf4j +public class StepMotorController { + private final StepMotorService stepMotorService; + private final DeviceParamConfigService deviceParamConfigService; + + // 获取设备列表 + @PostMapping("/get-device-list") + @Operation(summary = "获取设备列表") + public Map getDeviceList() { + Map map = new HashMap<>(); + for(DeviceStepMotorId id : DeviceStepMotorId.values()) { + map.put(id.name(), id.getDescription()); + } + return map; + } + + // 获取寄存器列表 + @PostMapping("/get-reg-list") + @Operation(summary = "获取寄存器列表") + public List getRegList() { + List list = new ArrayList<>(); + for(StepMotorRegIndex reg : StepMotorRegIndex.values()) { + list.add(reg.name()); + } + return list; + } + + // 基础设置 + @PostMapping("/speed-level") + @Operation(summary = "设置速度等级") + public Result setStepMotorSpeedLevel(@RequestParam StepMotorSpeedLevel speedLevel) { + stepMotorService.setStepMotorSpeedLevel(speedLevel); + return Result.success(); + } + + // 基础操作 + @PostMapping("/enable") + @Operation(summary = "电源开启") + public Result enableMotor(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + stepMotorService.enableMotor(deviceId); + return Result.success(); + } + + @PostMapping("/disable") + @Operation(summary = "电源关闭") + public Result disableMotor(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + stepMotorService.disableMotor(deviceId); + return Result.success(); + } + + @PostMapping("/move-to-zero") + @Operation(summary = "回Home") + public Result stepMotorEasyMoveToZero(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + stepMotorService.stepMotorEasyMoveToZero(deviceId); + return Result.success(); + } + + @PostMapping("/move-to-zero-quick") + @Operation(summary = "快速回Home") + public Result stepMotorEasyMoveToZeroPointQuickBlock(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + stepMotorService.stepMotorEasyMoveToZeroPointQuickBlock(deviceId); + return Result.success(); + } + + @PostMapping("/stop") + @Operation(summary = "停止") + public Result stepMotorStop(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + stepMotorService.stepMotorStop(deviceId); + return Result.success(); + } + + // 相对移动 + @PostMapping("/move-by-forward") + @Operation(summary = "正向点动") + public Result stepMotorEasyMoveByForward( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer dpos) throws HardwareException { + stepMotorService.stepMotorEasyMoveByForward(deviceId, dpos); + return Result.success(); + } + + @PostMapping("/move-by-backward") + @Operation(summary = "反向点动") + public Result stepMotorEasyMoveByBackward( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer dpos) throws HardwareException { + stepMotorService.stepMotorEasyMoveByBackward(deviceId, dpos); + return Result.success(); + } + + // 绝对移动 + @PostMapping("/move-to") + @Operation(summary = "移动(绝对)") + public Result stepMotorEasyMoveTo( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer pos) throws HardwareException { + stepMotorService.stepMotorEasyMoveTo(deviceId, pos); + return Result.success(); + } + + // 旋转 + @PostMapping("/rotate-forward") + @Operation(summary = "正转") + public Result rotateForward(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + stepMotorService.rotateForward(deviceId); + return Result.success(); + } + + @PostMapping("/rotate-backward") + @Operation(summary = "反转") + public Result rotateBackward(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + stepMotorService.rotateBackward(deviceId); + return Result.success(); + } + + // 寄存器配置 + @PostMapping("/mres") + @Operation(summary = "设置细分") + public Result setMres( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer mres) throws HardwareException { + stepMotorService.setMres(deviceId, mres); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_mres.regIndex.name(),mres); + return Result.success(); + } + + @PostMapping("/irun") + @Operation(summary = "设置IRUN") + public Result setIRUN( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer irun) throws HardwareException { + stepMotorService.setIRUN(deviceId, irun); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_irun.regIndex.name(),irun); + return Result.success(); + } + + @PostMapping("/ihold") + @Operation(summary = "设置IHOLD") + public Result setIHOLD( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer ihold) throws HardwareException { + stepMotorService.setIHOLD(deviceId, ihold); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_ihold.regIndex.name(),ihold); + return Result.success(); + } + + @PostMapping("/start-stop-vel") + @Operation(summary = "设置起停速度") + public Result setStartAndStopVel( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer v) throws HardwareException { + stepMotorService.setStartAndStopVel(deviceId, v); + //开始速度参数保存到数据库 + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_vstart.regIndex.name(),v); + //停止速度参数保存到数据库 + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_vstop.regIndex.name(),v); + return Result.success(); + } + + @PostMapping("/v1") + @Operation(summary = "设置V1") + public Result setV1( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer v) throws HardwareException { + stepMotorService.setV1(deviceId, v); + //参数保存到数据库 + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_v1.regIndex.name(),v); + return Result.success(); + } + + @PostMapping("/acceleration") + @Operation(summary = "设置加速度") + public Result setA1AndD1( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer acc) throws HardwareException { + stepMotorService.setA1AndD1(deviceId, acc); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_a1.regIndex.name(),acc); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_d1.regIndex.name(),acc); + return Result.success(); + } + + @PostMapping("/max-acceleration") + @Operation(summary = "设置最大加速度") + public Result setAmaxAndDmax( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer acc) throws HardwareException { + stepMotorService.setAmaxAndDmax(deviceId, acc); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_amax.regIndex.name(),acc); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_dmax.regIndex.name(),acc); + return Result.success(); + } + + @PostMapping("/default-velocity") + @Operation(summary = "设置默认速度") + public Result setDefaultVel( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer v) throws HardwareException { + stepMotorService.setDefaultVel(deviceId, v); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_default_velocity.regIndex.name(),v); + return Result.success(); + } + + @PostMapping("/speed") + @Operation(summary = "设置速度") + public Result setSpeed( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer low, + @RequestParam Integer mid, + @RequestParam Integer high) throws HardwareException { + stepMotorService.setSpeed(deviceId, low, mid, high); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_low_velocity.regIndex.name(),low); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_mid_velocity.regIndex.name(),mid); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_high_velocity.regIndex.name(),high); + return Result.success(); + } + + @PostMapping("/one-circle-pulse") + @Operation(summary = "设置一圈脉冲数") + public Result setOneCirclePulse( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer pulse, + @RequestParam Integer denominator) throws HardwareException { + stepMotorService.setOneCirclePulse(deviceId, pulse, denominator); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_one_circle_pulse.regIndex.name(),pulse); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_one_circle_pulse_denominator.regIndex.name(),denominator); + return Result.success(); + } + + @PostMapping("/dzero") + @Operation(summary = "设置DZero") + public Result setDZero( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam Integer dzero) throws HardwareException { + stepMotorService.setDZero(deviceId, dzero); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_dzero_pos.regIndex.name(),dzero); + return Result.success(); + } + + @GetMapping("/reg") + @Operation(summary = "读取寄存器值") + public Integer readReg( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam StepMotorRegIndex reg) { + return stepMotorService.readReg(deviceId, reg); + } + + @PostMapping("/reg") + @Operation(summary = "设置寄存器") + public Result setReg( + @RequestParam DeviceStepMotorId deviceId, + @RequestParam StepMotorRegIndex reg, + @RequestParam Integer val) throws HardwareException { + stepMotorService.setReg(deviceId, reg, val); + deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),reg.regIndex.name(),val); + return Result.success(); + } + + // 状态查询 + @GetMapping("/regs") + @Operation(summary = "读取所有寄存器") + public Map readAllRegs(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + return stepMotorService.readAllRegs(deviceId); + } + + @GetMapping("/position") + @Operation(summary = "读取位置") + public Integer readPos(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + return stepMotorService.readPos(deviceId); + } + + @GetMapping("/encoder-position") + @Operation(summary = "读取编码器位置") + public Integer readEncPos(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + return stepMotorService.readEncPos(deviceId); + } + + @PostMapping("/read-zero-sensor") + @Operation(summary = "读取零点光电") + public Boolean readZeroSensor(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + return stepMotorService.readIoState(deviceId, 0); + } + @PostMapping("/read-limit-sensor") + @Operation(summary = "读取限位光电") + public Boolean readLimitSensor(@RequestParam DeviceStepMotorId deviceId) throws HardwareException { + return stepMotorService.readIoState(deviceId, 1); + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/dao/SubModuleRegInitialValueDao.java b/src/main/java/com/iflytop/sgs/hardware/dao/SubModuleRegInitialValueDao.java new file mode 100644 index 0000000..85d3acb --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/dao/SubModuleRegInitialValueDao.java @@ -0,0 +1,38 @@ +package com.iflytop.sgs.hardware.dao; + +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.RegIndex; +import com.iflytop.sgs.hardware.type.db.SubModuleRegInitialValue; +import com.iflytop.sgs.hardware.utils.ZSqlite; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +@RequiredArgsConstructor +public class SubModuleRegInitialValueDao extends ZSqlite { + private final JdbcTemplate _jdbcTemplate; + + @PostConstruct + void init() { + init(_jdbcTemplate, "zapp_sub_module_reg_initial_value", SubModuleRegInitialValue.class, false); + } + + public SubModuleRegInitialValue findByIDAndRegIndex(MId mid, RegIndex regIndex) { + return queryOne("select * from " + tableName + " where mid = ? and regIndex = ?;", mid, regIndex); + } + + public void update(MId mid, RegIndex regIndex, Integer regInitVal) { + var old = findByIDAndRegIndex(mid, regIndex); + if (old == null) { + add(new SubModuleRegInitialValue(mid, regIndex, regInitVal)); + } else { + old.regInitVal = regInitVal; + update(old); + } + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/CameraDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/CameraDriver.java new file mode 100644 index 0000000..64363bd --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/CameraDriver.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.hardware.drivers; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CameraDriver { + public void takePhoto(){ + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/ColdTrapDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/ColdTrapDriver.java new file mode 100644 index 0000000..9ed4eb4 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/ColdTrapDriver.java @@ -0,0 +1,52 @@ +package com.iflytop.sgs.hardware.drivers; + +import com.iflytop.sgs.hardware.drivers.DODriver.OutputIOCtrlDriver; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import static com.iflytop.sgs.hardware.type.IO.OutputIOMId.DO_COLD_TRAP_POWER; + +/** + * 物理冷阱 + */ +@Component +@RequiredArgsConstructor +public class ColdTrapDriver { + private final OutputIOCtrlDriver outputIOCtrlDriver_; + + public void setTemperature(Double temperature) throws Exception { + + } + + public void openPower() throws Exception{ + outputIOCtrlDriver_.setIOState(DO_COLD_TRAP_POWER, true); + } + + public void closePower() throws Exception{ + outputIOCtrlDriver_.setIOState(DO_COLD_TRAP_POWER, false); + } + + public boolean openCircle() { + return false; + } + + public boolean closeCircle() { + return false; + } + + public boolean openHeart() { + return false; + } + + public boolean closeHeart() { + return false; + } + + public boolean openCool() { + return false; + } + + public boolean closeCool() { + return false; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/DIDriver/InputDetectDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/DIDriver/InputDetectDriver.java new file mode 100644 index 0000000..d57c843 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/DIDriver/InputDetectDriver.java @@ -0,0 +1,37 @@ +package com.iflytop.sgs.hardware.drivers.DIDriver; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.IO.InputIOMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class InputDetectDriver { + + private final A8kCanBusService canBus; + + public Boolean getIOState(InputIOMId ioid) throws HardwareException { + while (true) { + Boolean firstReadIO = priGetIOState(ioid); + Boolean secondReadIO = priGetIOState(ioid); + if (firstReadIO == secondReadIO) { + if (ioid.mirror) { + return !firstReadIO; + } + return firstReadIO; + } else { + log.warn("getIOState {} not match, retry", ioid); + } + } + } + + + private Boolean priGetIOState(InputIOMId ioid) throws HardwareException { + return canBus.callcmd(ioid.mid, CmdId.read_in_io, ioid.ioIndex).getContentI32(0) != 0; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/FanDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/FanDriver.java new file mode 100644 index 0000000..942e719 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/FanDriver.java @@ -0,0 +1,24 @@ +package com.iflytop.sgs.hardware.drivers.DODriver; + +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FanDriver { + private final OutputIOCtrlDriver outputIOCtrlDriver_; + + public void open(OutputIOMId outputIOMId) throws Exception + { + log.info("Opening FanD {}", outputIOMId.mid.getDescription()); + outputIOCtrlDriver_.setIOState(outputIOMId, true); + } + public void close(OutputIOMId outputIOMId) throws Exception + { + log.info("Closing FanD {}", outputIOMId.mid.getDescription()); + outputIOCtrlDriver_.setIOState(outputIOMId, false); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/OutputIOCtrlDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/OutputIOCtrlDriver.java new file mode 100644 index 0000000..1ad247e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/OutputIOCtrlDriver.java @@ -0,0 +1,30 @@ +package com.iflytop.sgs.hardware.drivers.DODriver; + + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class OutputIOCtrlDriver { + + private final A8kCanBusService canBus; + + public void setIOState(OutputIOMId IOOutput, Boolean state) throws HardwareException { + canBus.callcmd(IOOutput.mid, CmdId.write_out_io, IOOutput.ioIndex, state ? 1 : 0); + } + + public void open(OutputIOMId IOOutput) throws HardwareException + { + this.setIOState(IOOutput, true); + } + + public void close(OutputIOMId IOOutput) throws HardwareException + { + this.setIOState(IOOutput, false); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/VentilatorDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/VentilatorDriver.java new file mode 100644 index 0000000..9c9ab55 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/VentilatorDriver.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.hardware.drivers.DODriver; + +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class VentilatorDriver { + private final OutputIOCtrlDriver outputIOCtrlDriver_; + + public void openPower(OutputIOMId outputIOMId) throws Exception + { + outputIOCtrlDriver_.setIOState(outputIOMId, true); + } + public void closePower(OutputIOMId outputIOMId) throws Exception + { + outputIOCtrlDriver_.setIOState(outputIOMId, false); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/WaterPumpDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/WaterPumpDriver.java new file mode 100644 index 0000000..4de6bcc --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/DODriver/WaterPumpDriver.java @@ -0,0 +1,22 @@ +package com.iflytop.sgs.hardware.drivers.DODriver; + +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class WaterPumpDriver { + private final OutputIOCtrlDriver outputIOCtrlDriver_; + + public void openPower(OutputIOMId outputIOMId) throws Exception + { + outputIOCtrlDriver_.setIOState(outputIOMId, true); + } + public void closePower(OutputIOMId outputIOMId) throws Exception + { + outputIOCtrlDriver_.setIOState(outputIOMId, false); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/FillLightDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/FillLightDriver.java new file mode 100644 index 0000000..4b0dc95 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/FillLightDriver.java @@ -0,0 +1,23 @@ +package com.iflytop.sgs.hardware.drivers; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FillLightDriver { + private final A8kCanBusService canBus; + + public void open(MId mId, Integer brightness) throws Exception { + // 模拟打开 + canBus.callcmd(mId, CmdId.pwm_light_on, brightness); + } + public void close(MId mId) throws Exception { + canBus.callcmd(mId, CmdId.pwm_light_off); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/HeaterRodDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/HeaterRodDriver.java new file mode 100644 index 0000000..5161672 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/HeaterRodDriver.java @@ -0,0 +1,122 @@ +package com.iflytop.sgs.hardware.drivers; + +import cn.hutool.core.util.StrUtil; +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.comm.modbus.ModbusMasterService; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.A8kPacket; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.driver.HeaterRodSlavedId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HeaterRodDriver { + private final A8kCanBusService canBus; + private final ModbusMasterService modbusMaster; + + private static final int SV_ADDRESS = 0x2000; + private static final int PV_ADDRESS = 0x2010; + + private static final int CONTROL_REGISTER = 0x2106; + private static final int DP_ADDRESS = 0x2107; + private static final int DEVICE_ADDRESS_REGISTER = 0x2112; + + private final Map dpCache = new ConcurrentHashMap<>(); + private static final int DEFAULT_DP = 1; + + public void setDeviceAddress(HeaterRodSlavedId heaterRodId, int newAddress) throws Exception { + int slaveId = heaterRodId.getSlaveId(); + + if (!modbusMaster.writeRegister(slaveId, DEVICE_ADDRESS_REGISTER, newAddress)) { + throw new Exception("Failed to set device address for heater rod with ID: " + heaterRodId.name()); + } + + log.info("Device address for heater rod with ID: {} set to: {}", heaterRodId.name(), newAddress); + } + + private int getDecimalPoint(int slaveId) throws Exception { + return dpCache.computeIfAbsent(slaveId, id -> { + try { + short[] response = modbusMaster.readHoldingRegisters(id, DP_ADDRESS, 1); + if (response != null && response.length > 0) { + return (int) response[0]; + } + } catch (Exception e) { + log.warn("Failed to read DP for slave ID: {}, using default DP: {}", id, DEFAULT_DP); + } + return DEFAULT_DP; + }); + } + + public void openPower(HeaterRodSlavedId heaterRodId) throws Exception + { + int slaveId = heaterRodId.getSlaveId(); + if (!modbusMaster.writeRegister(slaveId, CONTROL_REGISTER, 1)) { // 1 = Start + throw new Exception("Failed to start heater rod with ID: " + heaterRodId.name()); + } + } + + public void closePower(HeaterRodSlavedId heaterRodId) throws Exception + { + int slaveId = heaterRodId.getSlaveId(); + + if (!modbusMaster.writeRegister(slaveId, CONTROL_REGISTER, 2)) { // 2 = Stop + throw new Exception("Failed to stop heater rod with ID: " + heaterRodId.name()); + } + + log.info("{} stopped.", heaterRodId.name()); + } + + public void setTemperature(HeaterRodSlavedId heaterRodId, double temperature) throws Exception { + int slaveId = heaterRodId.getSlaveId(); + int decimalPoint = getDecimalPoint(slaveId); + int scaledTemperature = (int) (temperature * Math.pow(10, decimalPoint)); + + if (!modbusMaster.writeRegister(slaveId, SV_ADDRESS, scaledTemperature)) { + throw new Exception("Failed to set temperature for heater rod with ID: " + heaterRodId.name()); + } + + log.info("{} started with temperature: {}", heaterRodId.name(), temperature); + } + + public double getTemperature(HeaterRodSlavedId heaterRodId) throws Exception { + int slaveId = heaterRodId.getSlaveId(); + int decimalPoint = getDecimalPoint(slaveId); + + short[] response = modbusMaster.readHoldingRegisters(slaveId, PV_ADDRESS, 1); + if (response == null || response.length == 0) { + throw new Exception(StrUtil.format("Failed to read temperature {}", heaterRodId.name())); + } + double temperature = response[0] / Math.pow(10, decimalPoint); + log.info("{} GET temperature {}", heaterRodId.name(), temperature); + return temperature; + } + + /** + * 获取电流 + * @return + */ + public Integer getCurrent(HeaterRodSlavedId heaterRodId) throws HardwareException + { + A8kPacket packet = canBus.callcmd(heaterRodId.getMid(), CmdId.adc_read_adc, heaterRodId.getAdcCurrentIndex()); + Integer current = packet.getContentI32(0); + return current; + } + + // ==== ==== ==== ==== ==== ==== application ==== ==== ==== ==== ==== ==== + public void open(HeaterRodSlavedId heaterRodId, double temperature) throws Exception { + this.openPower(heaterRodId); + this.setTemperature(heaterRodId, temperature); + } + + public void close(HeaterRodSlavedId heaterRodId) throws Exception { + this.closePower(heaterRodId); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/LeisaiServoDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/LeisaiServoDriver.java new file mode 100644 index 0000000..54188ba --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/LeisaiServoDriver.java @@ -0,0 +1,98 @@ +package com.iflytop.sgs.hardware.drivers; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.constants.ActionOvertimeConstant; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.A8kPacket; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.Servo.LeisaiRegIndex; +import com.iflytop.sgs.hardware.type.Servo.LeisaiServoMId; +import com.iflytop.sgs.hardware.type.Servo.LeisaiServoSpeedLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +@RequiredArgsConstructor +public class LeisaiServoDriver { + final private A8kCanBusService canBus; + private final ActionOvertimeConstant actionOvertimeConstant; + static public final Integer actionOvertime = 10000; + + public void moduleStop(LeisaiServoMId id) throws HardwareException { + log.info("moduleStop called with id: {}", id); + canBus.moduleStop(id.mid); + } + + public void enable(LeisaiServoMId id, Integer enable) throws HardwareException { + log.info("enable called with id: {}, enable: {}", id, enable); + canBus.callcmd(id.mid, CmdId.leisai_servo_enable, enable); + } + + public Integer readPosition(LeisaiServoMId id) throws HardwareException { + log.info("readPosition called with id: {}", id); + A8kPacket packet = canBus.callcmd(id.mid, CmdId.leisai_servo_read_pos); + return packet.getContentI32(0); + } + + public Boolean readIoState(LeisaiServoMId id, Integer io_index) throws HardwareException { + log.info("readIoState called with id: {}, io_index: {}", id, io_index); + A8kPacket packet = canBus.callcmd(id.mid, CmdId.leisai_servo_read_io_state, io_index); + return packet.getContentI32(0) != 0; + } + + public void moveTo(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer pos) throws HardwareException { + log.info("moveTo called with id: {}, speedLevel: {}, pos: {}", id, speedLevel, pos); + canBus.callcmd(id.mid, CmdId.leisai_servo_move_to, speedLevel.ordinal(), pos); + } + + public void moveBy(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer dpos) throws HardwareException { + log.info("moveBy called with id: {}, speedLevel: {}, dpos: {}", id, speedLevel, dpos); + canBus.callcmd(id.mid, CmdId.leisai_servo_move_by, speedLevel.ordinal(), dpos); + } + + public void rotate(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer direction) throws HardwareException { + log.info("rotate called with id: {}, speedLevel: {}, direction: {}", id, speedLevel, direction); + canBus.callcmd(id.mid, CmdId.leisai_servo_rotate, speedLevel.ordinal(), direction); + } + + public void moveToZero(LeisaiServoMId id) throws HardwareException { + log.info("moveToZero called with id: {}", id); + canBus.callcmd(id.mid, CmdId.leisai_servo_move_to_zero); + } + + // ====== ===== ====== ===== ====== ===== Block ====== ===== ====== ===== ====== ===== + public void moveToBlock(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer pos) throws HardwareException { + log.info("moveToBlock called with id: {}, speedLevel: {}, pos: {}", id, speedLevel, pos); + canBus.callcmd(id.mid, CmdId.leisai_servo_move_to, speedLevel.ordinal(), pos); + waitForMod(id, actionOvertimeConstant.get(id.mid, CmdId.leisai_servo_move_to)); + } + + public void moveByBlock(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer dpos) throws HardwareException { + log.info("moveByBlock called with id: {}, speedLevel: {}, dpos: {}", id, speedLevel, dpos); + canBus.callcmd(id.mid, CmdId.leisai_servo_move_by, speedLevel.ordinal(), dpos); + waitForMod(id, actionOvertimeConstant.get(id.mid, CmdId.leisai_servo_move_by)); + } + + public void moveToZeroBlock(LeisaiServoMId id) throws HardwareException { + log.info("moveToZeroBlock called with id: {}", id); + canBus.callcmd(id.mid, CmdId.leisai_servo_move_to_zero); + waitForMod(id, actionOvertimeConstant.get(id.mid, CmdId.leisai_servo_move_to_zero)); + } + + public void waitForMod(LeisaiServoMId id, Integer overtime) throws HardwareException { + canBus.waitForMod(id.mid, overtime); + } + + public void setReg(LeisaiServoMId id, LeisaiRegIndex regindex, int val) throws HardwareException { + log.info("setReg called with id: {}, regindex: {}, val: {}", id, regindex, val); + canBus.moduleSetReg(id.mid, regindex.regIndex, val); + } + + + public Integer getReg(LeisaiServoMId id, LeisaiRegIndex regindex) throws HardwareException { + log.info("getReg called with id: {}, regindex: {}", id, regindex); + return canBus.moduleGetReg(id.mid, regindex.regIndex); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/LiquidDistributionArmDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/LiquidDistributionArmDriver.java new file mode 100644 index 0000000..5c2cd8a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/LiquidDistributionArmDriver.java @@ -0,0 +1,98 @@ +package com.iflytop.sgs.hardware.drivers; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.constants.ActionOvertimeConstant; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.Servo.LiquidArmMId; +import com.iflytop.sgs.hardware.type.Servo.LiquidArmRegIndex; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 加液臂接口 + */ +@Component +@Slf4j +@RequiredArgsConstructor +public class LiquidDistributionArmDriver { + private final ActionOvertimeConstant actionOvertimeConstant; + final private A8kCanBusService canBus; + /** + * 使能 + * @param id + * @param enable + * @throws HardwareException + */ + public void liquidDistributionArmEnable(LiquidArmMId id, int enable) throws HardwareException { + log.info("liquidDistributionArmEnable called with id: {}, enable: {}", id, enable); + canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_enable, enable); + } + + /** + * 移动到指定试管架 + * @param id + * @param index + * @throws HardwareException + */ + public void liquidDistributionArmMoveTo(LiquidArmMId id, int index) throws HardwareException { + log.info("liquidDistributionArmMoveTo called with id: {}, index: {}", id, index); + canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_move_to, index); + } + + public void liquidDistributionArmMoveToBlock(LiquidArmMId id, int index) throws HardwareException { + liquidDistributionArmMoveTo(id,index); + waitForMod(id, actionOvertimeConstant.get(id.mid, CmdId.liquid_distribution_arm_move_to)); + } + public void waitForMod(LiquidArmMId id, Integer overtime) throws HardwareException { + canBus.waitForMod(id.mid, overtime); + } + + public int liquidDistributionReadPos(LiquidArmMId id) throws HardwareException { + log.info("liquidDistributionReadPos called with id: {}", id); + var packet = canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_read_pos); + return packet.getContentI32(0); + } + + public void liquidDistributionSetCurPosAsPresetPos(LiquidArmMId id, int index) throws HardwareException { + log.info("liquidDistributionSetCurPosAsPresetPos called with id: {}, index: {}", id, index); + canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_set_cur_pos_as_preset_pos, index); + } + + public void liquidDistributionSetCurPosAsMidPos(LiquidArmMId id) throws HardwareException { + log.info("liquidDistributionSetCurPosAsMidPos called with id: {}", id); + canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_set_cur_pos_as_mid_pos); + } + + /** + * 停止 + * @param id + * @throws HardwareException + */ + public void moduleStop(LiquidArmMId id) throws HardwareException { + log.info("moduleStop called with id: {}", id); + canBus.moduleStop(id.mid); + } + + public void setReg(LiquidArmMId id, LiquidArmRegIndex regindex, int val) throws HardwareException { + log.info("setReg called with id: {}, regindex: {}, val: {}", id, regindex, val); + canBus.moduleSetReg(id.mid, regindex.regIndex, val); + } + + public Integer getReg(LiquidArmMId id, LiquidArmRegIndex regindex) throws HardwareException { + log.info("getReg called with id: {}, regindex: {}", id, regindex); + return canBus.moduleGetReg(id.mid, regindex.regIndex); + } + +// public Object getAllReg(LiquidArmMId id) throws HardwareException { +// log.info("getAllReg called with id: {}", id); +// ObjectNode node = ZJsonHelper.createObjectNode(); +// for (LiquidArmRegIndex regIndex : LiquidArmRegIndex.values()) { +// Integer regVal = getReg(id, regIndex); +// log.info("read reg {} -> {}", regIndex, regVal); +// node.put(regIndex.name(), getReg(id, regIndex)); +// } +// return node; +// } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/ClawDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/ClawDriver.java new file mode 100644 index 0000000..5a2bcd6 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/ClawDriver.java @@ -0,0 +1,108 @@ +package com.iflytop.sgs.hardware.drivers.MiniServoDriver; + +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import com.iflytop.sgs.hardware.utils.Math.ServoPositionConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ClawDriver { + private static final double MIN_DISTANCE = 34.0; + private static final double MAX_DISTANCE = 54.0; + + private static double MIN_SERVO_POSITION = 2048; + private static double MAX_SERVO_POSITION = 3000; + + private final MiniServoDriver miniServoDriver; + + private double speed_ = 10.0; // mm/s + + // ==== ==== ==== ==== ==== ==== convert ==== ==== ==== ==== ==== ==== + /** + * 将距离转换为舵机位置 + * @param distance 距离值,范围在 MIN_DISTANCE 到 MAX_DISTANCE 之间 + * @return 对应的舵机位置,范围在 MIN_SERVO_POSITION 到 MAX_SERVO_POSITION 之间 + */ + public static double distanceToServoPosition(double distance) { + if (distance < MIN_DISTANCE) { + distance = MIN_DISTANCE; + } else if (distance > MAX_DISTANCE) { + distance = MAX_DISTANCE; + } + return MIN_SERVO_POSITION + (distance - MIN_DISTANCE) * (MAX_SERVO_POSITION - MIN_SERVO_POSITION) / (MAX_DISTANCE - MIN_DISTANCE); + } + + /** + * 将舵机位置转换为距离 + * @param servoPosition 舵机位置值,范围在 MIN_SERVO_POSITION 到 MAX_SERVO_POSITION 之间 + * @return 对应的距离,范围在 MIN_DISTANCE 到 MAX_DISTANCE 之间 + */ + public static double servoPositionToDistance(double servoPosition) { + if (servoPosition < MIN_SERVO_POSITION) { + servoPosition = MIN_SERVO_POSITION; + } else if (servoPosition > MAX_SERVO_POSITION) { + servoPosition = MAX_SERVO_POSITION; + } + return MIN_DISTANCE + (servoPosition - MIN_SERVO_POSITION) * (MAX_DISTANCE - MIN_DISTANCE) / (MAX_SERVO_POSITION - MIN_SERVO_POSITION); + } + + + private int toServoPosition(double position) { + int servoPosition = 0; + servoPosition = (int)distanceToServoPosition(position); + servoPosition = (int)ServoPositionConverter.convert4096To3600(servoPosition); + return servoPosition; + } + + private int toServoSpeed(double speed) { + int servoSpeed = 0; + servoSpeed = (int)speed; + return servoSpeed; + } + + // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ==== + private double getSpeed(MiniServoMId servoMid) + { + return speed_; + } + + // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ==== + public void setSpeed(MiniServoMId servoMid, double speed) { + log.info("[ {} ] Setting speed to {}", servoMid.mid.getDescription(), speed); + // 检查速度是否合法 + speed_ = speed; + } + + // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ==== + public void moveToHome(MiniServoMId servoMid) throws HardwareException + { + log.info("[ {} ] moveToHome", servoMid.mid.getDescription()); + + this.moveTo(servoMid, MIN_DISTANCE); + } + + public void moveTo(MiniServoMId servoMid, double position) throws HardwareException + { + // 检查位置是否合法 + if (position < MIN_DISTANCE || position > MAX_DISTANCE) { + log.error("[ {} ] Position is out of range", servoMid.mid.getDescription()); + throw new InvalidParameterException("Position is out of range"); + } + miniServoDriver.miniServoEnable(servoMid, 1); + int servoPosition = toServoPosition(position); + log.info("[ {} ] moveTo {}, servo position {} ...", servoMid, position, servoPosition); + miniServoDriver.miniServoMoveToBlock(servoMid, servoPosition); + log.info("[ {} ] moveTo {}, servo position {} end", servoMid, position, servoPosition); + } + + public void stop(MiniServoMId servoMid) throws HardwareException { + log.info("[ {} ] stop", servoMid.mid.getDescription()); + miniServoDriver.miniServoStop(servoMid); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/DualRobotDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/DualRobotDriver.java new file mode 100644 index 0000000..0716919 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/DualRobotDriver.java @@ -0,0 +1,178 @@ +package com.iflytop.sgs.hardware.drivers.MiniServoDriver; + +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.awt.geom.Point2D; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DualRobotDriver { + public enum Joint { + JOINT_1, + JOINT_2 + } + + // 舵机角度范围常量(度) + public static final double MIN_ANGLE_DEG = -180.0; + public static final double MAX_ANGLE_DEG = 180.0; + + // 舵机位置值范围常量 + public static final int MIN_SERVO_POSITION = 0; + public static final int MAX_SERVO_POSITION = 3600; + + private final MiniServoDriver miniServoDriver; + + private double speed_ = 10.0; // mm/s + + // ==== ==== ==== ==== ==== ==== convert ==== ==== ==== ==== ==== ==== + + /** + * 将舵机角度(-180 到 180 度)转换为位置值(0 - 4096) + * @param angle 舵机角度,范围在 -180 到 180 度之间 + * @return 对应的位置值,范围在 0 到 4096 之间 + * @throws IllegalArgumentException 如果输入角度超出范围 + */ + public static int angleToServoPosition(double angle) { + if (angle < MIN_ANGLE_DEG || angle > MAX_ANGLE_DEG) { + throw new IllegalArgumentException("角度必须在 " + MIN_ANGLE_DEG + " 到 " + MAX_ANGLE_DEG + " 度之间"); + } + + // 线性映射公式:position = minPos + (angle - minAngle) * (maxPos - minPos) / (maxAngle - minAngle) + return (int) Math.round( + MIN_SERVO_POSITION + + (angle - MIN_ANGLE_DEG) * + (MAX_SERVO_POSITION - MIN_SERVO_POSITION) / + (MAX_ANGLE_DEG - MIN_ANGLE_DEG) + ); + } + + /** + * 将位置值(0 - 4096)转换为舵机角度(-180 到 180 度) + * @param servoPosition 设置值,范围在 0 到 4096 之间 + * @return 对应的舵机角度,范围在 -180 到 180 度之间 + * @throws IllegalArgumentException 如果输入位置值超出范围 + */ + public static double servoPositionToAngle(int servoPosition) { + if (servoPosition < MIN_SERVO_POSITION || servoPosition > MAX_SERVO_POSITION) { + throw new IllegalArgumentException("位置值必须在 " + MIN_SERVO_POSITION + " 到 " + MAX_SERVO_POSITION + " 之间"); + } + + // 线性映射公式:angle = minAngle + (position - minPos) * (maxAngle - minAngle) / (maxPos - minPos) + return MIN_ANGLE_DEG + + (servoPosition - MIN_SERVO_POSITION) * + (MAX_ANGLE_DEG - MIN_ANGLE_DEG) / + (MAX_SERVO_POSITION - MIN_SERVO_POSITION); + } + + + private int toServoPosition(double angle) { + int servoPosition = angleToServoPosition(angle); + return servoPosition; + } + + private double toAngle(Integer position) + { + double angle = servoPositionToAngle(position); + return angle; + } + + private Point2D toServoPosition(Point2D point) { + Point2D servoPosition = new Point2D.Double(0, 0); + servoPosition.setLocation(point.getX(), point.getY()); + return servoPosition; + } + + private int toServoSpeed(double speed) { + int servoSpeed = 0; + servoSpeed = (int)speed; + return servoSpeed; + } + + // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ==== + private double getSpeed(MiniServoMId servoMid) + { + return speed_; + } + + // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ==== + public void setSpeed(MiniServoMId servoMid, double speed) { + // 检查速度是否合法 + speed_ = speed; + } + + public MiniServoMId getMiniServoMId(Joint joint) { + switch (joint) { + case JOINT_1: + return MiniServoMId.DUAL_ROBOT_AXIS1_MID; + case JOINT_2: + return MiniServoMId.DUAL_ROBOT_AXIS2_MID; + default: + return null; + } + } + + // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ==== + public void servoEnable(MiniServoMId servoMId, boolean enable) throws HardwareException + { + miniServoDriver.miniServoEnable(servoMId, enable ? 1 : 0); + } + + public void moveToHome(MiniServoMId servoMid) throws HardwareException + { + log.info("Servo {} Move To Home", servoMid.mid.getDescription()); + this.servoEnable(servoMid, true); + } + + public void moveJointTo(MiniServoMId servoMid, double position) throws HardwareException + { + // 检查位置是否合法 + Integer servoPosition = toServoPosition(position); + log.info("Servo {} Move To position {}, Servo Position {}", servoMid.mid.getDescription(), position, servoPosition); + this.servoEnable(servoMid, true); + miniServoDriver.miniServoMoveToBlock(servoMid, servoPosition); + } + + public void stop(MiniServoMId servoMid) throws HardwareException { + log.info("Servo {} Stop", servoMid.mid.getDescription()); + miniServoDriver.miniServoStop(servoMid); + } + + // ==== ==== ==== ==== ==== ==== Application ==== ==== ==== ==== ==== ==== + + public void setSpeed(Joint joint, double speed) throws HardwareException { + MiniServoMId servoMId = getMiniServoMId(joint); + setSpeed(servoMId, speed); + log.info("Set speed for {} to {}", joint, speed); + } + + public void moveToPoint(double pointX, double pointY) throws HardwareException { +// Point2D point = getServoPosition(new Point2D.Double(pointX, pointY)); +// +// miniServoDriver.miniServoMoveToBlock(MiniServoMId.DUAL_ROBOT_AXIS1_MID, (int)point.getX()); +// miniServoDriver.miniServoMoveToBlock(MiniServoMId.DUAL_ROBOT_AXIS2_MID, (int)point.getY()); + } + + public void moveToHome(Joint joint) throws HardwareException { + MiniServoMId servoMId = getMiniServoMId(joint); + log.info("Servo {} Move To Home", servoMId.mid.getDescription()); + moveToHome(servoMId); + } + + public void moveJointTo(Joint joint, double position) throws HardwareException { + MiniServoMId servoMId = getMiniServoMId(joint); + log.info("Servo {} Move To position {}", servoMId.mid.getDescription(), position); + moveJointTo(servoMId, position); + } + + public void stop(Joint joint) throws HardwareException { + MiniServoMId servoMId = getMiniServoMId(joint); + log.info("Servo {} Stop", servoMId.mid.getDescription()); + stop(servoMId); + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/MiniServoDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/MiniServoDriver.java new file mode 100644 index 0000000..2b114a4 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/MiniServoDriver/MiniServoDriver.java @@ -0,0 +1,162 @@ +package com.iflytop.sgs.hardware.drivers.MiniServoDriver; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.constants.MiniServoConstant; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.RegIndex; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import com.iflytop.sgs.hardware.type.Servo.MiniServoRegIndex; +import com.iflytop.sgs.hardware.type.error.A8kEcode; +import com.iflytop.sgs.hardware.type.error.AEHardwareError; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + + +@Component +@Slf4j +@RequiredArgsConstructor +public class MiniServoDriver { + final private A8kCanBusService canBus; + + public void moduleStop(MiniServoMId id) throws HardwareException { + canBus.moduleStop(id.mid); + } + + public void miniServoEnable(MiniServoMId id, int enable) throws HardwareException { + log.debug("{} miniServoEnable {}", id.mid, enable); + canBus.callcmd(id.mid, CmdId.mini_servo_enable, enable); + } + + public void miniServoForceEnable(MiniServoMId id, int enable) { + try { + miniServoEnable(id, enable); + } catch (HardwareException e) { + log.error("miniServoForceEnable error", e); + } + } + + public Boolean miniServoPing(MiniServoMId id) { + try { + miniServoReadPos(id); + } catch (HardwareException e) { + return false; + } + return true; + } + + public int miniServoReadPos(MiniServoMId id) throws HardwareException { + var packet = canBus.callcmd(id.mid, CmdId.mini_servo_read_pos); + return packet.getContentI32(0); + } + + public void miniServoMoveToZeroBlock(MiniServoMId id) throws HardwareException { + log.debug("{} miniServoMoveToZeroBlock", id.mid); + miniServoMoveToBlock(id, MiniServoConstant.getZeroPos(id)); + } + + public void miniServoMoveToZero(MiniServoMId id) throws HardwareException { + log.debug("{} miniServoMoveToZero", id.mid); + miniServoMoveTo(id, MiniServoConstant.getZeroPos(id)); + } + + + public void miniServoActiveCfg(MiniServoMId id) throws HardwareException { + canBus.callcmd(id.mid, CmdId.mini_servo_active_cfg); + } + + public void miniServoStop(MiniServoMId id) throws HardwareException { + canBus.callcmd(id.mid, CmdId.mini_servo_stop); + } + + public void miniServoSetMidPoint(MiniServoMId id) throws HardwareException { + canBus.callcmd(id.mid, CmdId.mini_servo_set_mid_point); + } + + public int miniServoReadIoState(MiniServoMId id) throws HardwareException { + var packet = canBus.callcmd(id.mid, CmdId.mini_servo_read_io_state); + return packet.getContentI32(0); + } + + public void miniServoMoveTo(MiniServoMId id, int pos) throws HardwareException { + canBus.callcmd(id.mid, CmdId.mini_servo_move_to, pos); + } + + public void miniServoMoveToBlock(MiniServoMId id, int pos) throws HardwareException { + log.debug("{} miniServoMoveTo {}", id.mid, pos); + miniServoMoveTo(id, pos); + canBus.waitForMod(id.mid, MiniServoConstant.actionOvertime); + } + + + public void miniServoRotate(MiniServoMId id, int direction) throws HardwareException { + log.debug("{} miniServoRotate {}", id.mid, direction); + canBus.callcmd(id.mid, CmdId.mini_servo_rotate, direction); + } + + public void miniServoRotateBlock(MiniServoMId id, int direction) throws HardwareException { + miniServoRotate(id, direction); + canBus.waitForMod(id.mid, MiniServoConstant.actionOvertime); + } + + public void miniServoRotateWithTorque(MiniServoMId id, int torque) throws HardwareException { + log.debug("{} miniServoRotateWithTorque {}", id.mid, torque); + canBus.callcmd(id.mid, CmdId.mini_servo_rotate_with_torque, torque); + } + + public void waitForMod(MiniServoMId mid) throws HardwareException { + canBus.waitForMod(mid.mid, MiniServoConstant.actionOvertime); + } + + public void waitForMod(MiniServoMId... mid) throws HardwareException { + for (MiniServoMId m : mid) { + waitForMod(m); + } + } + + + // kmini_servo_set_cur_pos + private void miniServoSetCurPos(MiniServoMId id, Integer pos) throws HardwareException { + log.debug("{} miniServoSetCurPos {}", id.mid, pos); + canBus.callcmd2(id.mid, CmdId.mini_servo_set_cur_pos, 5000, pos); + } + + public Integer miniServoSetRefPos(MiniServoMId id) throws HardwareException { + miniServoSetCurPos(id, MiniServoConstant.getRefPos(id)); + return MiniServoConstant.getRefPos(id); + } + + public void setReg(MiniServoMId id, MiniServoRegIndex regindex, int val) throws HardwareException { + log.debug("{} setReg {} {}", id.mid, regindex, val); + canBus.moduleSetReg(id.mid, regindex.regIndex, val); + } + + public Integer getReg(MiniServoMId id, MiniServoRegIndex regindex) throws HardwareException { + return canBus.moduleGetReg(id.mid, regindex.regIndex); + } + + public Integer getRegNoEx(MiniServoMId id, MiniServoRegIndex regindex) { + try { + return canBus.moduleGetReg(id.mid, regindex.regIndex); + } catch (HardwareException e) { + log.error("readRegNoEx error", e); + return null; + } + } + + public void miniServoWaitIsNotMove(MiniServoMId id, int acitionOvertime) throws HardwareException { + long startedAt = System.currentTimeMillis(); + do { + var isMove = canBus.moduleGetReg(id.mid, RegIndex.kreg_mini_servo_is_move); + if (isMove != 0) { + break; + } + long now = System.currentTimeMillis(); + if (now - startedAt > acitionOvertime) { + throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, id.mid, null)); + } + + } while (true); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/ModuleEnableCtrlDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/ModuleEnableCtrlDriver.java new file mode 100644 index 0000000..ac026d2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/ModuleEnableCtrlDriver.java @@ -0,0 +1,78 @@ +package com.iflytop.sgs.hardware.drivers; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.MiniServoDriver; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.StepMotorCtrlDriver; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +@RequiredArgsConstructor +public class ModuleEnableCtrlDriver { + + private final StepMotorCtrlDriver stepMotorCtrlDriver; + private final MiniServoDriver miniServoDriver; + private final A8kCanBusService canBus; + + public void forceDisableAllMotor() { + // StepMotorMId.values() + for (StepMotorMId stepMotorMId : StepMotorMId.values()) { + try { + stepMotorCtrlDriver.stepMotorEnable(stepMotorMId, 0); + } catch (HardwareException e) { + log.info("disableStepMotor: {} failed, {}", stepMotorMId, e.getMessage()); + } + } + + for (MiniServoMId miniServoMId : MiniServoMId.values()) { + try { + miniServoDriver.miniServoEnable(miniServoMId, 0); + } catch (HardwareException e) { + log.info("disableMiniServo: {} failed, {}", miniServoMId, e.getMessage()); + } + } + } + + public void disableAllModule() throws HardwareException { + + for (var motorId : StepMotorMId.values()) { + try { + stepMotorCtrlDriver.stepMotorStop(motorId); + stepMotorCtrlDriver.stepMotorEnable(motorId, 0); + } catch (HardwareException ignored) { + } + } + + for (var miniServoId : MiniServoMId.values()) { + try { + miniServoDriver.miniServoEnable(miniServoId, 0); + } catch (HardwareException ignored) { + } + } + } + + + public void enableAllModule() throws HardwareException { + try { + for (StepMotorMId stepMotorMId : StepMotorMId.values()) + stepMotorCtrlDriver.stepMotorEnable(stepMotorMId, 1); + + + for (MiniServoMId miniServoMId : MiniServoMId.values()) + miniServoDriver.miniServoEnable(miniServoMId, 1); + + + } catch (HardwareException e) { + forceDisableAllMotor(); + throw e; + } + + } + + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/AcidPumpDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/AcidPumpDriver.java new file mode 100644 index 0000000..cf2bc1c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/AcidPumpDriver.java @@ -0,0 +1,100 @@ +package com.iflytop.sgs.hardware.drivers.StepMotorDriver; + +import com.iflytop.sgs.hardware.config.StepMotorConfig; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorDirect; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.utils.Math.StepMotorConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AcidPumpDriver { + private final StepMotorConfig config_; + private final StepMotorCtrlDriver stepMotorCtrlDriver_; + + // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ==== + private double getSpeed(StepMotorMId stepMotorMId) + { + return 0.0; + } + + // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ==== + public void setSpeed(StepMotorMId stepMotorMId, double speed) throws HardwareException { + // 检查速度是否合法 + // 设置速度到单片机 + int motorSpeed = StepMotorConverter.toMotorSpeed(Math.abs(speed), config_.getLead(stepMotorMId)); + log.info("Motor {} speed set to {}, motorSpeed {}", stepMotorMId.mid.getDescription(), speed, motorSpeed); + stepMotorCtrlDriver_.setReg(stepMotorMId, StepMotorRegIndex.kreg_step_motor_default_velocity, motorSpeed); + } + + // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ==== + public void motorEnable(StepMotorMId stepMotorMId, Boolean enable) throws Exception + { + Integer i_enable = enable ? 1 : 0; + stepMotorCtrlDriver_.stepMotorEnable(stepMotorMId, i_enable); + } + + public void moveToHome(StepMotorMId stepMotorMId) throws Exception + { + log.info("Motor {} Move To Home", stepMotorMId.mid.getDescription()); + motorEnable(stepMotorMId, true); + stepMotorCtrlDriver_.stepMotorEasyMoveToZeroBlock(stepMotorMId); + motorEnable(stepMotorMId, false); + } + + public void moveTo(StepMotorMId stepMotorMId, double position) throws Exception + { + // 检查位置是否合法 + if (Math.abs(position) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move To position out of range, position {} , limit {}", stepMotorMId.mid.getDescription(), position , config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("position is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(position); + log.info("Motor {} Move To position {}, Motor Positon {}", stepMotorMId.mid.getDescription(), position, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveToBlock(stepMotorMId, motorPosition); + motorEnable(stepMotorMId, false); + } + + public void moveBy(StepMotorMId stepMotorMId, double distance) throws Exception + { + // 检查位置是否合法 + + if (Math.abs(distance) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move By position out of range, position {} , limit {}", stepMotorMId.mid.getDescription(), distance , config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("Distance is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(distance); + log.info("Motor {} Move By position {}, Motor Positon {}", stepMotorMId.mid.getDescription(), distance, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveByBlock(stepMotorMId, motorPosition); + motorEnable(stepMotorMId, false); + } + + /** + * 正转加液 反转排液 + * @param stepMotorMId + * @param direct + * @throws Exception + */ + public void rotate(StepMotorMId stepMotorMId, StepMotorDirect direct) throws Exception + { + motorEnable(stepMotorMId, true); + stepMotorCtrlDriver_.stepMotorEasyRotate(stepMotorMId, direct.getValue()); + } + + public void stop(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Stop", stepMotorMId.mid.getDescription()); + stepMotorCtrlDriver_.stepMotorStop(stepMotorMId); + motorEnable(stepMotorMId, true); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/DoorDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/DoorDriver.java new file mode 100644 index 0000000..be87ef1 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/DoorDriver.java @@ -0,0 +1,78 @@ +package com.iflytop.sgs.hardware.drivers.StepMotorDriver; + +import com.iflytop.sgs.hardware.config.StepMotorConfig; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.utils.Math.StepMotorConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DoorDriver{ + private final StepMotorConfig config_; + private final StepMotorCtrlDriver stepMotorCtrlDriver_; + + // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ==== + private double getSpeed(StepMotorMId stepMotorMId) + { + return 0.0; + } + + // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ==== + public void setSpeed(StepMotorMId stepMotorMId, double speed) throws HardwareException { + // 检查速度是否合法 + // 设置速度到单片机 + int motorSpeed = StepMotorConverter.toMotorSpeed(Math.abs(speed), config_.getLead(stepMotorMId)); + stepMotorCtrlDriver_.setReg(stepMotorMId, StepMotorRegIndex.kreg_step_motor_default_velocity, motorSpeed); + } + + // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ==== + public void motorEnable(StepMotorMId stepMotorMId, Boolean enable) throws Exception { + Integer i_enable = enable ? 1 : 0; + stepMotorCtrlDriver_.stepMotorEnable(stepMotorMId, i_enable); + } + + public void moveToHome(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Move To Home", stepMotorMId.mid.getDescription()); + motorEnable(stepMotorMId, true); + stepMotorCtrlDriver_.stepMotorEasyMoveToZeroBlock(stepMotorMId); + } + + public void moveTo(StepMotorMId stepMotorMId, double position) throws Exception { + // 检查位置是否合法 + if (Math.abs(position) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move To position out of range, position {}, limit {}", stepMotorMId.mid.getDescription(), position, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("position is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(position); + log.info("Motor {} Move To position {}, Motor Position {}", stepMotorMId.mid.getDescription(), position, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveToBlock(stepMotorMId, motorPosition); + } + + public void moveBy(StepMotorMId stepMotorMId, double distance) throws Exception { + // 检查位置是否合法 + if (Math.abs(distance) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move By distance out of range, distance {}, limit {}", stepMotorMId.mid.getDescription(), distance, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("Distance is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(distance); + log.info("Motor {} Move By distance {}, Motor Position {}", stepMotorMId.mid.getDescription(), distance, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveByBlock(stepMotorMId, motorPosition); + } + + public void stop(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Stop", stepMotorMId.mid.getDescription()); + stepMotorCtrlDriver_.stepMotorStop(stepMotorMId); + motorEnable(stepMotorMId, true); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/HBotDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/HBotDriver.java new file mode 100644 index 0000000..a55b83e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/HBotDriver.java @@ -0,0 +1,99 @@ +package com.iflytop.sgs.hardware.drivers.StepMotorDriver; + +import com.iflytop.sgs.hardware.config.StepMotorConfig; +import com.iflytop.sgs.hardware.drivers.DODriver.OutputIOCtrlDriver; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.utils.Math.StepMotorConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HBotDriver { + private final StepMotorConfig config_; + private final OutputIOCtrlDriver doDriver_; + private final StepMotorCtrlDriver stepMotorCtrlDriver_; + + // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ==== + private double getSpeed(StepMotorMId stepMotorMId) + { + return 0.0; + } + + // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ==== + public void setSpeed(StepMotorMId stepMotorMId, double speed) throws HardwareException { + // 检查速度是否合法 + // 设置速度到单片机 + int motorSpeed = StepMotorConverter.toMotorSpeed(Math.abs(speed), config_.getLead(stepMotorMId)); + stepMotorCtrlDriver_.setReg(stepMotorMId, StepMotorRegIndex.kreg_step_motor_default_velocity, motorSpeed); + } + + // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ==== + public void motorEnable(StepMotorMId stepMotorMId, Boolean enable) throws Exception { + Integer i_enable = enable ? 1 : 0; + stepMotorCtrlDriver_.stepMotorEnable(stepMotorMId, i_enable); + } + + public void moveToHome(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Move To Home", stepMotorMId.mid.getDescription()); + motorEnable(stepMotorMId, true); + stepMotorCtrlDriver_.stepMotorEasyMoveToZeroBlock(stepMotorMId); + } + + public void moveTo(StepMotorMId stepMotorMId, double position) throws Exception { + // 检查位置是否合法 + if (Math.abs(position) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move To position out of range, position {}, limit {}", stepMotorMId.mid.getDescription(), position, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("position is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(position); + log.info("Motor {} Move To position {}, Motor Position {}", stepMotorMId.mid.getDescription(), position, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveToBlock(stepMotorMId, motorPosition); + } + + public void moveBy(StepMotorMId stepMotorMId, double distance) throws Exception { + // 检查位置是否合法 + if (Math.abs(distance) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move By distance out of range, distance {}, limit {}", stepMotorMId.mid.getDescription(), distance, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("Distance is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(distance); + log.info("Motor {} Move By distance {}, Motor Position {}", stepMotorMId.mid.getDescription(), distance, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveByBlock(stepMotorMId, motorPosition); + } + + public void stop(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Stop", stepMotorMId.mid.getDescription()); + stepMotorCtrlDriver_.stepMotorStop(stepMotorMId); + motorEnable(stepMotorMId, true); + } + + // ==== ==== ==== ==== ==== ==== IO Ctrl ==== ==== ==== ==== ==== + public void openClamp(OutputIOMId iomId) throws Exception + { + if(iomId == null) { + return; + } + doDriver_.open(iomId); + } + + public void closeClamp(OutputIOMId iomId) throws Exception + { + if(iomId == null) { + return; + } + doDriver_.close(iomId); + } +} + diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/HeaterMotorDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/HeaterMotorDriver.java new file mode 100644 index 0000000..93455de --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/HeaterMotorDriver.java @@ -0,0 +1,78 @@ +package com.iflytop.sgs.hardware.drivers.StepMotorDriver; + +import com.iflytop.sgs.hardware.config.StepMotorConfig; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.utils.Math.StepMotorConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HeaterMotorDriver { + private final StepMotorConfig config_; + private final StepMotorCtrlDriver stepMotorCtrlDriver_; + + // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ==== + private double getSpeed(StepMotorMId stepMotorMId) + { + return 0.0; + } + + // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ==== + public void setSpeed(StepMotorMId stepMotorMId, double speed) throws HardwareException { + // 检查速度是否合法 + // 设置速度到单片机 + int motorSpeed = StepMotorConverter.toMotorSpeed(Math.abs(speed), config_.getLead(stepMotorMId)); + stepMotorCtrlDriver_.setReg(stepMotorMId, StepMotorRegIndex.kreg_step_motor_default_velocity, motorSpeed); + } + + // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ==== + public void motorEnable(StepMotorMId stepMotorMId, Boolean enable) throws Exception { + Integer i_enable = enable ? 1 : 0; + stepMotorCtrlDriver_.stepMotorEnable(stepMotorMId, i_enable); + } + + public void moveToHome(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Move To Home", stepMotorMId.mid.getDescription()); + motorEnable(stepMotorMId, true); + stepMotorCtrlDriver_.stepMotorEasyMoveToZeroBlock(stepMotorMId); + } + + public void moveTo(StepMotorMId stepMotorMId, double position) throws Exception { + // 检查位置是否合法 + if (Math.abs(position) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move To position out of range, position {}, limit {}", stepMotorMId.mid.getDescription(), position, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("position is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(position); + log.info("Motor {} Move To position {}, Motor Position {}", stepMotorMId.mid.getDescription(), position, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveToBlock(stepMotorMId, motorPosition); + } + + public void moveBy(StepMotorMId stepMotorMId, double distance) throws Exception { + // 检查位置是否合法 + if (Math.abs(distance) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move By distance out of range, distance {}, limit {}", stepMotorMId.mid.getDescription(), distance, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("Distance is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(distance); + log.info("Motor {} Move By distance {}, Motor Position {}", stepMotorMId.mid.getDescription(), distance, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveByBlock(stepMotorMId, motorPosition); + } + + public void stop(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Stop", stepMotorMId.mid.getDescription()); + stepMotorCtrlDriver_.stepMotorStop(stepMotorMId); + motorEnable(stepMotorMId, true); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/ShakeMotorDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/ShakeMotorDriver.java new file mode 100644 index 0000000..84f8d69 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/ShakeMotorDriver.java @@ -0,0 +1,89 @@ +package com.iflytop.sgs.hardware.drivers.StepMotorDriver; + +import com.iflytop.sgs.hardware.config.StepMotorConfig; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorDirect; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.utils.Math.StepMotorConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ShakeMotorDriver { + private final StepMotorConfig config_; + private final StepMotorCtrlDriver stepMotorCtrlDriver_; + + // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ==== + private double getSpeed(StepMotorMId stepMotorMId) + { + return 0.0; + } + + // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ==== + public void setSpeed(StepMotorMId stepMotorMId, double speed) throws HardwareException { + // 检查速度是否合法 + // 设置速度到单片机 + int motorSpeed = StepMotorConverter.toMotorSpeed(Math.abs(speed), config_.getLead(stepMotorMId)); + stepMotorCtrlDriver_.setReg(stepMotorMId, StepMotorRegIndex.kreg_step_motor_default_velocity, motorSpeed); + } + + // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ==== + public void motorEnable(StepMotorMId stepMotorMId, Boolean enable) throws Exception { + Integer i_enable = enable ? 1 : 0; + stepMotorCtrlDriver_.stepMotorEnable(stepMotorMId, i_enable); + } + + public void moveToHome(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Move To Home", stepMotorMId.mid.getDescription()); + motorEnable(stepMotorMId, true); + stepMotorCtrlDriver_.stepMotorEasyMoveToZeroBlock(stepMotorMId); + } + + public void moveTo(StepMotorMId stepMotorMId, double position) throws Exception { + // 检查位置是否合法 + if (Math.abs(position) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move To position out of range, position {}, limit {}", stepMotorMId.mid.getDescription(), position, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("position is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(position); + log.info("Motor {} Move To position {}, Motor Position {}", stepMotorMId.mid.getDescription(), position, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveToBlock(stepMotorMId, motorPosition); + } + + public void moveBy(StepMotorMId stepMotorMId, double distance) throws Exception { + // 检查位置是否合法 + if (Math.abs(distance) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move By distance out of range, distance {}, limit {}", stepMotorMId.mid.getDescription(), distance, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("Distance is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(distance); + log.info("Motor {} Move By distance {}, Motor Position {}", stepMotorMId.mid.getDescription(), distance, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveByBlock(stepMotorMId, motorPosition); + } + + public void stop(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Stop", stepMotorMId.mid.getDescription()); + stepMotorCtrlDriver_.stepMotorStop(stepMotorMId); + motorEnable(stepMotorMId, true); + } + + public void startShake(StepMotorMId stepMotorMId, StepMotorDirect direct) throws Exception { + motorEnable(stepMotorMId, true); + stepMotorCtrlDriver_.stepMotorEasyRotate(stepMotorMId, direct.getValue()); + } + + public void stopShake(StepMotorMId stepMotorMId) throws Exception { + this.stop(stepMotorMId); + this.moveToHome(stepMotorMId); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/StepMotorCtrlDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/StepMotorCtrlDriver.java new file mode 100644 index 0000000..dab0bff --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/StepMotorCtrlDriver.java @@ -0,0 +1,181 @@ +package com.iflytop.sgs.hardware.drivers.StepMotorDriver; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.constants.ActionOvertimeConstant; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.*; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorSpeedLevel; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class StepMotorCtrlDriver { + static Logger logger = org.slf4j.LoggerFactory.getLogger(StepMotorCtrlDriver.class); + + final private A8kCanBusService canBus; + + private final ActionOvertimeConstant actionOvertimeConstant; + + public void stepMotorEasyMoveBy(StepMotorMId id, Integer dpos) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_easy_move_by, dpos); + } + + public void stepMotorEasyMoveTo(StepMotorMId id, Integer pos) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_easy_move_to, pos); + } + + public void stepMotorEasyMoveToZero(StepMotorMId id) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_easy_move_to_zero); + } + + public void setCurrentPos(StepMotorMId id, Integer pos) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_easy_set_current_pos, pos); + } + + void stepMotorEasyMoveToZeroPointQuick(StepMotorMId id) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_easy_move_to_zero_point_quick); + } + + void stepMotorEasyMoveToEndPoint(StepMotorMId id) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_easy_move_to_end_point); + } + + public void stepMotorEnable(StepMotorMId mid, Integer enable) throws HardwareException { + canBus.callcmd(mid.mid, CmdId.step_motor_enable, enable); + } + + public Integer stepMotorReadPos(StepMotorMId id) throws HardwareException { + A8kPacket packet = canBus.callcmd(id.mid, CmdId.step_motor_read_pos); + return packet.getContentI32(0); + } + + public Integer stepMotorReadEncPos(StepMotorMId id) throws HardwareException { + A8kPacket packet = canBus.callcmd(id.mid, CmdId.step_motor_read_enc_pos); + return packet.getContentI32(0); + } + + public void stepMotorEasyRotate(StepMotorMId id, Integer direction) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_easy_rotate, direction); + } + + public void stepMotorEasyMoveByBlock(StepMotorMId id, Integer dpos) throws HardwareException { + logger.debug("stepMotorEasyMoveByBlock {} {}", id, dpos); + stepMotorEasyMoveBy(id, dpos); + canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_by)); + } + + public void waitForMod(StepMotorMId id, Integer overtime) throws HardwareException { + canBus.waitForMod(id.mid, overtime); + } + + public void stepMotorEasyMoveToBlock(StepMotorMId id, Integer pos) throws HardwareException { + logger.debug("stepMotorEasyMoveToBlock {} {}", id, pos); + stepMotorEasyMoveTo(id, pos); + canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_to)); + } + + public void stepMotorEasyMoveToZeroBlock(StepMotorMId id) throws HardwareException { + logger.debug("stepMotorEasyMoveToZeroBlock {}", id); + stepMotorEasyMoveToZero(id); + canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_to_zero)); + } + + public Integer stepMotorReadPosByMoveToZeroBlock(StepMotorMId id) throws HardwareException { + stepMotorEnable(id, 1); + stepMotorEasyMoveToZeroBlock(id); + Integer nowpos = stepMotorReadPos(id); + Integer measurepos = -canBus.moduleGetReg(id.mid, RegIndex.kreg_step_motor_dpos) + nowpos; + return measurepos; + } + + public void stepMotorEasyMoveToZeroPointQuickBlock(StepMotorMId id) throws HardwareException { + stepMotorEasyMoveToZeroPointQuick(id); + canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_to_zero_point_quick)); + } + + public void stepMotorStop(StepMotorMId id) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_stop, 0); + } + + public void moveByBlock(StepMotorMId id, Integer dpos, StepMotorSpeedLevel speedLevel) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_move_by, dpos, speedLevel.ordinal()); + waitForMod(id, actionOvertimeConstant.get(id, CmdId.step_motor_move_by)); + } + + public void moveToBlock(StepMotorMId id, Integer pos, StepMotorSpeedLevel speedLevel) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_move_to, pos, speedLevel.ordinal()); + waitForMod(id, actionOvertimeConstant.get(id, CmdId.step_motor_move_to)); + } + + public void moveToZeroQuickBlock(StepMotorMId id, StepMotorSpeedLevel speedLevel) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_move_to_zero_point_quick, speedLevel.ordinal()); + waitForMod(id, actionOvertimeConstant.get(id, CmdId.step_motor_move_to_zero_point_quick)); + } + + public void rotate(StepMotorMId id, Integer direction, StepMotorSpeedLevel speedLevel) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_rotate, direction, speedLevel.ordinal()); + + } + + public Boolean stepMotorReadIoState(StepMotorMId id, Integer ioindex) throws HardwareException { + var packet = canBus.callcmd(id.mid, CmdId.step_motor_read_io_state, ioindex); + return packet.getContentI32(0) != 0; + } + + public void stepMotorEasyMoveToEndPointBlock(StepMotorMId id) throws HardwareException { + stepMotorEasyMoveToEndPoint(id); + canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_to_end_point)); + } + + void stepMotorEasyReciprocatingMotion(StepMotorMId id, Integer startpos, Integer endpos, Integer times) throws HardwareException { + canBus.callcmd(id.mid, CmdId.step_motor_easy_reciprocating_motion, startpos, endpos, times); + } + + public void stepMotorEasyReciprocatingMotionBlock(StepMotorMId id, Integer startpos, Integer endpos, Integer times) throws HardwareException { + stepMotorEasyReciprocatingMotion(id, startpos, endpos, times); + canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_reciprocating_motion)); + } + + public Boolean isHasMoveToZero(StepMotorMId id) throws HardwareException { + return getReg(id, StepMotorRegIndex.kreg_step_motor_has_move_zero) != 0; + } + + public void setReg(StepMotorMId id, StepMotorRegIndex regIndex, Integer val) throws HardwareException { + canBus.moduleSetReg(id.mid, regIndex.regIndex, val); + } + + public Integer getReg(StepMotorMId id, StepMotorRegIndex regIndex) throws HardwareException { + return canBus.moduleGetReg(id.mid, regIndex.regIndex); + } + + public void resetRegs(StepMotorMId id) throws HardwareException { + canBus.callcmd(id.mid, CmdId.module_reset_reg); + } + +// public Object getAllReg(StepMotorMId id) throws HardwareException { +// ObjectNode node = ZJsonHelper.createObjectNode(); +// for (StepMotorRegIndex regIndex : StepMotorRegIndex.values()) { +// Integer regVal = getReg(id, regIndex); +// logger.debug("read reg {} -> {}", regIndex, regVal); +// node.put(regIndex.name(), getReg(id, regIndex)); +// } +// return node; +// } + + public Map getAllReg(StepMotorMId id) throws HardwareException { + Map map = new HashMap(); + for (StepMotorRegIndex regIndex : StepMotorRegIndex.values()) { + Integer regVal = getReg(id, regIndex); + logger.debug("read reg {} -> {}", regIndex, regVal); + map.put(regIndex.name(), regVal); + } + return map; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/TrayMotorDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/TrayMotorDriver.java new file mode 100644 index 0000000..0b60ff9 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/StepMotorDriver/TrayMotorDriver.java @@ -0,0 +1,97 @@ +package com.iflytop.sgs.hardware.drivers.StepMotorDriver; + +import com.iflytop.sgs.hardware.config.StepMotorConfig; +import com.iflytop.sgs.hardware.drivers.DODriver.OutputIOCtrlDriver; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.IO.OutputIOMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.utils.Math.StepMotorConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.InvalidParameterException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TrayMotorDriver { + private final StepMotorConfig config_; + private final OutputIOCtrlDriver doDriver_; + private final StepMotorCtrlDriver stepMotorCtrlDriver_; + + // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ==== + private double getSpeed(StepMotorMId stepMotorMId) + { + return 0.0; + } + + // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ==== + public void setSpeed(StepMotorMId stepMotorMId, double speed) throws HardwareException { + // 检查速度是否合法 + // 设置速度到单片机 + int motorSpeed = StepMotorConverter.toMotorSpeed(Math.abs(speed), config_.getLead(stepMotorMId)); + stepMotorCtrlDriver_.setReg(stepMotorMId, StepMotorRegIndex.kreg_step_motor_default_velocity, motorSpeed); + } + + // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ==== + public void motorEnable(StepMotorMId stepMotorMId, Boolean enable) throws Exception { + Integer i_enable = enable ? 1 : 0; + stepMotorCtrlDriver_.stepMotorEnable(stepMotorMId, i_enable); + } + + public void moveToHome(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Move To Home", stepMotorMId.mid.getDescription()); + motorEnable(stepMotorMId, true); + stepMotorCtrlDriver_.stepMotorEasyMoveToZeroBlock(stepMotorMId); + } + + public void moveTo(StepMotorMId stepMotorMId, double position) throws Exception { + // 检查位置是否合法 + if (Math.abs(position) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move To position out of range, position {}, limit {}", stepMotorMId.mid.getDescription(), position, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("position is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(position); + log.info("Motor {} Move To position {}, Motor Position {}", stepMotorMId.mid.getDescription(), position, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveToBlock(stepMotorMId, motorPosition); + } + + public void moveBy(StepMotorMId stepMotorMId, double distance) throws Exception { + // 检查位置是否合法 + if (Math.abs(distance) > Math.abs(config_.getMaxLimit(stepMotorMId))) { + log.error("Motor {} Move By distance out of range, distance {}, limit {}", stepMotorMId.mid.getDescription(), distance, config_.getMaxLimit(stepMotorMId)); + throw new InvalidParameterException("Distance is out of range"); + } + + motorEnable(stepMotorMId, true); + int motorPosition = StepMotorConverter.toMotorPosition(distance); + log.info("Motor {} Move By distance {}, Motor Position {}", stepMotorMId.mid.getDescription(), distance, motorPosition); + stepMotorCtrlDriver_.stepMotorEasyMoveByBlock(stepMotorMId, motorPosition); + } + + public void stop(StepMotorMId stepMotorMId) throws Exception { + log.info("Motor {} Stop", stepMotorMId.mid.getDescription()); + stepMotorCtrlDriver_.stepMotorStop(stepMotorMId); + } + + // ==== ==== ==== ==== ==== ==== IO Ctrl ==== ==== ==== ==== ==== + public void openClamp(OutputIOMId iomId) throws Exception + { + if(iomId == null) { + return; + } + doDriver_.open(iomId); + } + + public void closeClamp(OutputIOMId iomId) throws Exception + { + if(iomId == null) { + return; + } + doDriver_.close(iomId); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/drivers/TricolorLightDriver.java b/src/main/java/com/iflytop/sgs/hardware/drivers/TricolorLightDriver.java new file mode 100644 index 0000000..6bd5e61 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/drivers/TricolorLightDriver.java @@ -0,0 +1,28 @@ +package com.iflytop.sgs.hardware.drivers; + + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.MId; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TricolorLightDriver { + private final A8kCanBusService canBus; + public enum Color { + RED, GREEN, BLUE + } + + public void open(MId mid, Color color) throws HardwareException { + canBus.callcmd(mid, CmdId.tricolor_light_on, color.ordinal()); + } + + public void close(MId mid) throws HardwareException{ + canBus.callcmd(mid, CmdId.tricolor_light_off); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/exception/HardwareException.java b/src/main/java/com/iflytop/sgs/hardware/exception/HardwareException.java new file mode 100644 index 0000000..5d4182c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/exception/HardwareException.java @@ -0,0 +1,62 @@ +package com.iflytop.sgs.hardware.exception; + + +import com.iflytop.sgs.hardware.type.*; +import com.iflytop.sgs.hardware.type.error.A8kEcode; +import com.iflytop.sgs.hardware.type.error.AECodeError; +import com.iflytop.sgs.hardware.type.error.AEHardwareError; +import com.iflytop.sgs.hardware.type.error.AppError; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +public class HardwareException extends Exception { + // 构造函数 + + public AppError error; + + + public HardwareException(A8kEcode ecodeType) { + super(String.format("%s", ecodeType)); + this.error = new AppError(ecodeType); + } + + public HardwareException(AppError error) { + super(String.format("%s", error.toString())); + this.error = error; + } + + public static HardwareException of(A8kEcode errorCode, MId mid, CmdId cmdId) { + return new HardwareException(new AEHardwareError(errorCode, mid, cmdId)); + } + + public static HardwareException of(A8kEcode errorCode) { + return new HardwareException(new AppError(errorCode)); + } + + public static HardwareException of(A8kEcode errorCode, MId mid) { + return new HardwareException(new AEHardwareError(errorCode, mid, null)); + } + + public static HardwareException of(A8kEcode errorCode, String exmsg, Object... args) { + return new HardwareException(new AppError(errorCode, exmsg, args)); + } + + public static HardwareException of(AEHardwareError AEHardwareError) { + return new HardwareException(AEHardwareError); + } + + public static HardwareException of(AECodeError AECodeError) { + return new HardwareException(AECodeError); + } + + + public static HardwareException ofAECodeError(String fmt, Object... args) { + return new HardwareException(new AECodeError(fmt, args)); + } + +// public static HardwareException ofSimplePrompt(String fmt, Object... args) { +// return new HardwareException(new AESimpleErrorPrompt(fmt, args)); +// } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/factory/A8kPacketFactory.java b/src/main/java/com/iflytop/sgs/hardware/factory/A8kPacketFactory.java new file mode 100644 index 0000000..a240471 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/factory/A8kPacketFactory.java @@ -0,0 +1,53 @@ +package com.iflytop.sgs.hardware.factory; + + + +import com.iflytop.sgs.hardware.type.A8kPacket; +import com.iflytop.sgs.hardware.type.CmdId; + +import java.util.List; + +public class A8kPacketFactory { + + //typedef struct { + // uint8_t packetType; + // uint16_t cmdid; + // uint8_t moduleId; + // uint16_t index; + // uint8_t datalen; + // uint8_t data[]; + // /* int8_t checksum;*/ + //} zcr_cmd_header_t; + + static public A8kPacket buildPacket(Integer moduleId, int packetType, Integer cmdId, Integer[] params) { + return A8kPacket.createPacket(moduleId, packetType, cmdId, params); + } + + static public A8kPacket buildCMDPacket(Integer moduleId, Integer cmdId, Integer[] params) { + return buildPacket(moduleId, (byte) 0, cmdId, params); + } + + static public A8kPacket buildCMDPacket(Integer moduleId, Integer cmdId, List params) { + Integer[] array = new Integer[params.size()]; + params.toArray(array); + return buildPacket(moduleId, A8kPacket.PACKET_TYPE_CMD, cmdId, array); + } + + static public A8kPacket buildReportPacket(Integer moduleId, Integer cmdId, Integer[] params) { + return buildPacket(moduleId, (byte) 3, cmdId, params); + } + + // static public A8kPacket build_event_a8000_idcard_online() { + // return buildReportPacket(MId.A8kIdCardReader.toInt(), CmdId.event_a8000_idcard_online.index, new Integer[0]); + // } + // + // static public A8kPacket build_event_a8000_idcard_offline() { + // return buildReportPacket(MId.A8kIdCardReader.toInt(), CmdId.event_a8000_idcard_offline.index, new Integer[0]); + // } + + public static void main(String[] args) { + var packet = buildCMDPacket(1, CmdId.module_get_reg.toInt(), List.of(1, 2, 3)); + System.out.println(packet.toByteString()); + } +} + diff --git a/src/main/java/com/iflytop/sgs/hardware/service/AppEventBusService.java b/src/main/java/com/iflytop/sgs/hardware/service/AppEventBusService.java new file mode 100644 index 0000000..5d5e96a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/service/AppEventBusService.java @@ -0,0 +1,93 @@ +package com.iflytop.sgs.hardware.service; + +import com.iflytop.sgs.hardware.type.appevent.AppEvent; +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * 事件总线 + * + */ +@Component +public class AppEventBusService implements ApplicationListener { + public static final Logger logger = LoggerFactory.getLogger(AppEventBusService.class); + + @FunctionalInterface + public interface AppEventListener { + void onAppEvent(AppEvent event); + } + + + Thread eventProcessorThread; + BlockingQueue eventQueue = new LinkedBlockingQueue<>(); + List listeners = new ArrayList<>(); + + @PostConstruct + public void init() { + + } + + public void regListener(AppEventListener listener) { + listeners.add(listener); + } + + + public void pushEvent(AppEvent event) { + logger.info("pushEvent: {} {}", event.getClass().getSimpleName(), event); + eventQueue.add(event); + } + +// public void pushAppPromptEvent(String msg) { +// pushEvent(new AppPromptEvent(ZAppPromoptFactory.buildNotifyPromopt(msg))); +// } + +// public void pushAppExceptionEvent(Exception e) { +// pushEvent(new AppExceptionEvent(e)); +// } + +// public void pushAppTubeholderSettingUpdateEvent() { +// pushEvent(new AppTubeholderSettingUpdateEvent()); +// } + + private void eventBusSchedule() { + while (!Thread.currentThread().isInterrupted()) { + AppEvent event = null; + try { + event = eventQueue.take(); + } catch (InterruptedException ignored) { + } + callOnEvent(event); + } + } + + private void callOnEvent(AppEvent event) { + // logger.info("Processing event: {}", event.getClass().getSimpleName()); + for (AppEventListener listener : listeners) { + listener.onAppEvent(event); + } + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + logger.info("Spring context refreshed"); + if (eventProcessorThread == null) { + eventProcessorThread = new Thread(new Runnable() { + public void run() { + logger.info("Starting event bus schedule"); + eventBusSchedule(); + } + }); + eventProcessorThread.start(); + } + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/service/GDDeviceStatusService.java b/src/main/java/com/iflytop/sgs/hardware/service/GDDeviceStatusService.java new file mode 100644 index 0000000..84d151f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/service/GDDeviceStatusService.java @@ -0,0 +1,91 @@ +package com.iflytop.sgs.hardware.service; + +import com.iflytop.sgs.hardware.drivers.DIDriver.InputDetectDriver; +import com.iflytop.sgs.hardware.drivers.HeaterRodDriver; +import com.iflytop.sgs.hardware.drivers.LeisaiServoDriver; +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.MiniServoDriver; +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.StepMotorCtrlDriver; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.IO.InputIOMId; +import com.iflytop.sgs.hardware.type.Servo.LeisaiServoMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.driver.HeaterRodSlavedId; +import com.iflytop.sgs.hardware.utils.Math.StepMotorConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class GDDeviceStatusService { + private final InputDetectDriver inputDetectDriver; + private final HeaterRodDriver heaterRodDriver; + private final StepMotorCtrlDriver stepMotorCtrlDriver; + private final MiniServoDriver miniServoDriver; + private final LeisaiServoDriver leisaiServoDriver; + + /** + * 获取步进电机的原点状态 + * @param stepMotorMId + * @return true 在原点 false 不在原点 + */ + public Boolean stepMotorIsOrigin(StepMotorMId stepMotorMId) throws HardwareException { + return stepMotorCtrlDriver.stepMotorReadIoState(stepMotorMId, 0); + } + + /** + * 获取XY 伺服的原点状态 + * @param mid + * @return + * @throws HardwareException + */ + public Boolean getXYServoIsOrigin(LeisaiServoMId mid) throws HardwareException + { + return leisaiServoDriver.readIoState(mid, 0); + } + + /** + * 获取 拍子 试管架拍子 急停 到位信号 + * @param inputIOMId + * @return + * @throws HardwareException + */ + public Boolean getInputState(InputIOMId inputIOMId) throws HardwareException { + return inputDetectDriver.getIOState(inputIOMId); + } + + /** + * 获取加热棒温度 + * @param heaterRodSlavedId + * @return + * @throws Exception + */ + public Double getHeaterRodTemperature(HeaterRodSlavedId heaterRodSlavedId) throws Exception { + return heaterRodDriver.getTemperature(heaterRodSlavedId); + } + + /** + * 获取电机位置 + * @param stepMotorMId + * @return + * @throws HardwareException + */ + public Double getStepMotorPostion(StepMotorMId stepMotorMId) throws HardwareException { + Integer motorPosition = stepMotorCtrlDriver.stepMotorReadPos(stepMotorMId); + Double realPosition = StepMotorConverter.toUserPosition(motorPosition); + return realPosition; + } + + /** + * 获取伺服类电机位置 + * @param mid + * @return + * @throws HardwareException + */ + public Double getXYServoPosition(LeisaiServoMId mid) throws HardwareException { + int servoPosition = leisaiServoDriver.readPosition(mid); + return StepMotorConverter.toUserPosition(servoPosition); + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/service/IOService.java b/src/main/java/com/iflytop/sgs/hardware/service/IOService.java new file mode 100644 index 0000000..05ee897 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/service/IOService.java @@ -0,0 +1,4 @@ +package com.iflytop.sgs.hardware.service; + +public class IOService { +} diff --git a/src/main/java/com/iflytop/sgs/hardware/service/ServoService.java b/src/main/java/com/iflytop/sgs/hardware/service/ServoService.java new file mode 100644 index 0000000..83680a3 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/service/ServoService.java @@ -0,0 +1,129 @@ +package com.iflytop.sgs.hardware.service; + +import com.iflytop.sgs.hardware.drivers.MiniServoDriver.MiniServoDriver; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.Servo.DeviceServoId; +import com.iflytop.sgs.hardware.type.Servo.MiniServoMId; +import com.iflytop.sgs.hardware.type.Servo.MiniServoRegIndex; +import com.iflytop.sgs.hardware.type.error.A8kEcode; +import com.iflytop.sgs.hardware.type.error.AppError; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class ServoService { + private final MiniServoDriver miniServoDriver; + + private void throwDeviceNull(DeviceServoId id) throws HardwareException + { + throw new HardwareException(new AppError(A8kEcode.PE_PARAM_OUT_OF_RANGE, "StepMotorMId is null for id: " + id)); + } + + public void enable(DeviceServoId id, Boolean enable) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.miniServoEnable(servoMId, enable ? 1 : 0); + } + + public void stop(DeviceServoId id) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.moduleStop(servoMId); + } + + public void moveTo(DeviceServoId id, Integer pos) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.miniServoMoveToBlock(servoMId, pos); + } + + public void rotate(DeviceServoId id, Integer direction) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.miniServoRotate(servoMId, direction); + } + + public void rotateWithTorque(DeviceServoId id, Integer pos) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.miniServoRotateWithTorque(servoMId, pos); + } + + public void setReg(DeviceServoId id, MiniServoRegIndex reg, Integer val) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.setReg(servoMId, reg, val); + } + + public Map getAllReg(DeviceServoId id) { + Map map = new HashMap<>(); + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + return map; + } + + for (MiniServoRegIndex reg : MiniServoRegIndex.values()) { + try { + map.put(reg.name(), miniServoDriver.getReg(servoMId, reg)); + } catch (HardwareException e) { + } + } + return map; + } + + public void setLimitVelocity(DeviceServoId id, Integer val) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.setReg(servoMId, MiniServoRegIndex.kreg_mini_servo_limit_velocity, val); + } + + public void setLimitTorque(DeviceServoId id, Integer val) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.setReg(servoMId, MiniServoRegIndex.kreg_mini_servo_limit_torque, val); + } + + public void setProtectiveTorque(DeviceServoId id, Integer val) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.setReg(servoMId, MiniServoRegIndex.kreg_mini_servo_protective_torque, val); + } + + public Integer readPos(DeviceServoId id) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + return miniServoDriver.miniServoReadPos(servoMId); + } + + public void moveToZero(DeviceServoId id) throws HardwareException { + MiniServoMId servoMId = id.getServoMId(); + if (servoMId == null) { + throwDeviceNull(id); + } + miniServoDriver.miniServoMoveToZeroBlock(servoMId); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/service/StepMotorService.java b/src/main/java/com/iflytop/sgs/hardware/service/StepMotorService.java new file mode 100644 index 0000000..73f6e0a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/service/StepMotorService.java @@ -0,0 +1,262 @@ +package com.iflytop.sgs.hardware.service; + +import com.iflytop.sgs.hardware.drivers.StepMotorDriver.*; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.StepMotor.DeviceStepMotorId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorMId; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorRegIndex; +import com.iflytop.sgs.hardware.type.StepMotor.StepMotorSpeedLevel; +import com.iflytop.sgs.hardware.type.error.A8kEcode; +import com.iflytop.sgs.hardware.type.error.AppError; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class StepMotorService { + private final StepMotorCtrlDriver stepMotorCtrlDriver_; + + StepMotorSpeedLevel speedLevel_ = StepMotorSpeedLevel.DEFAULT; + + private void throwDeviceNull(DeviceStepMotorId id) throws HardwareException + { + throw new HardwareException(new AppError(A8kEcode.PE_PARAM_OUT_OF_RANGE, "StepMotorMId is null for id: " + id)); + } + + public void setStepMotorSpeedLevel(StepMotorSpeedLevel speedLevel) { + this.speedLevel_ = speedLevel; + } + + public void enableMotor(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.stepMotorEnable(stepId, 1); + } + + public void disableMotor(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.stepMotorEnable(stepId, 0); + } + + public void stepMotorEasyMoveToZero(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.stepMotorEasyMoveToZero(stepId); + } + + public void stepMotorEasyMoveToZeroPointQuickBlock(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.stepMotorEasyMoveToZeroPointQuickBlock(stepId); + } + + public void stepMotorStop(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.stepMotorStop(stepId); + } + + public void stepMotorEasyMoveByForward(DeviceStepMotorId id, Integer dpos) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + log.info("stepMotorEasyMoveForward: {} {}", id, dpos); + stepMotorCtrlDriver_.moveByBlock(stepId, dpos, speedLevel_); + } + + public void stepMotorEasyMoveByBackward(DeviceStepMotorId id, Integer dpos) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + log.info("stepMotorEasyMoveBackward: {} {}", id, -dpos); + stepMotorCtrlDriver_.moveByBlock(stepId, -dpos, speedLevel_); + } + + public void stepMotorEasyMoveTo(DeviceStepMotorId id, Integer pos) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + log.info("stepMotorEasyMoveTo: {} {}", id, pos); + stepMotorCtrlDriver_.moveToBlock(stepId, pos, speedLevel_); + } + + public void rotateForward(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.rotate(stepId, 1, speedLevel_); + } + + public void rotateBackward(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.rotate(stepId, -1, speedLevel_); + } + + public Integer readEncPos(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + return stepMotorCtrlDriver_.stepMotorReadEncPos(stepId); + } + + public Integer readPos(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + return stepMotorCtrlDriver_.stepMotorReadPos(stepId); + } + + public void setReg(DeviceStepMotorId id, StepMotorRegIndex reg, Integer val) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, reg, val); + } + + public void setStartAndStopVel(DeviceStepMotorId id, Integer v) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_vstart, v); + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_vstop, v); + } + + public void setV1(DeviceStepMotorId id, Integer v) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_v1, v); + } + + public void setA1AndD1(DeviceStepMotorId id, Integer acc) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_a1, acc); + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_d1, acc); + } + + public void setAmaxAndDmax(DeviceStepMotorId id, Integer acc) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_amax, acc); + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_dmax, acc); + } + + public void setDefaultVel(DeviceStepMotorId id, Integer v) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_default_velocity, v); + } + + public void setSpeed(DeviceStepMotorId id, Integer low_speed, Integer mid_speed, Integer high_speed) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_low_velocity, low_speed); + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_mid_velocity, mid_speed); + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_high_velocity, high_speed); + } + + public void setMres(DeviceStepMotorId id, Integer mres) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_mres, mres); + } + + public void setIRUN(DeviceStepMotorId id, Integer irun) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_irun, irun); + } + + public void setIHOLD(DeviceStepMotorId id, Integer ihold) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_ihold, ihold); + } + + public void setOneCirclePulse(DeviceStepMotorId id, Integer pulse, Integer denominator) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_one_circle_pulse, pulse); + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_one_circle_pulse_denominator, denominator); + } + + public void setDZero(DeviceStepMotorId id, Integer dzero) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_dzero_pos, dzero); + } + + public Integer readReg(DeviceStepMotorId id, StepMotorRegIndex reg) { + try { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + return stepMotorCtrlDriver_.getReg(stepId, reg); + } catch (HardwareException e) { + return 0; + } + } + + public Map readAllRegs(DeviceStepMotorId id) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + return stepMotorCtrlDriver_.getAllReg(stepId); + } + + public Boolean readIoState(DeviceStepMotorId id, Integer ioindex) throws HardwareException { + StepMotorMId stepId = id.getStepMotorMId(); + if (stepId == null) { + throwDeviceNull(id); + } + return stepMotorCtrlDriver_.stepMotorReadIoState(stepId, ioindex); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/service/setup/A8kSubModuleRegInitService.java b/src/main/java/com/iflytop/sgs/hardware/service/setup/A8kSubModuleRegInitService.java new file mode 100644 index 0000000..5aa669c --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/service/setup/A8kSubModuleRegInitService.java @@ -0,0 +1,136 @@ +package com.iflytop.sgs.hardware.service.setup; + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.config.A8kSubModuleInitRegConfig; +import com.iflytop.sgs.hardware.drivers.ModuleEnableCtrlDriver; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.service.AppEventBusService; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.ModuleType; +import com.iflytop.sgs.hardware.type.RegIndex; +import com.iflytop.sgs.hardware.type.appevent.A8kCanBusOnConnectEvent; +import com.iflytop.sgs.hardware.type.appevent.AppEvent; +import com.iflytop.sgs.hardware.type.db.SubModuleRegInitialValue; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 子设备寄存器初始化服务 + */ + +@Component +@Slf4j +@RequiredArgsConstructor +public class A8kSubModuleRegInitService { + private final A8kCanBusService canBus; + private final ModuleEnableCtrlDriver moduleEnableCtrlDriver; + private final AppEventBusService eventBus; + private final A8kSubModuleInitRegConfig a8kSubModuleInitRegConfig; + private final SubModuleRegInitialValueMgrService subModuleRegInitialValueMgrService; + private Boolean isInited = false; + + @PostConstruct + void init() { + eventBus.regListener(this::onAppEvent); + } + + public synchronized boolean getIsInited() { + return isInited; + } + + + private void onAppEvent(AppEvent event) { + if (event instanceof A8kCanBusOnConnectEvent) { + var initThread = new Thread(this::initModuleRegVal); + initThread.setName("initModuleRegVal"); + initThread.start(); + } + } + + private void initModuleRegVal() { + do { +// try { + log.info("forceInitA8kModParams"); + dumpAllSubBoardVersion(); +// initA8kModParams(); + isInited = true; + + log.info("======================================================"); + log.info("= init hardware param success...... ="); + log.info("======================================================"); + break; +// } catch (HardwareException e) { +// log.error("init hardware param fail......, try init it after 5s", e); +// } + } while (false); + } + + private void dumpAllSubBoardVersion() { + log.info("======================================================="); + log.info("= dump all sub board version...... ="); + log.info("="); + + boolean hasDiffVersion = false; + Integer maxVersion = null; + for (MId mid : MId.values()) { + if (mid == MId.NotSet) { + continue; + } + try { + if (!canBus.ping(mid)) { + log.error(String.format("+ %-40s(%d): offline", mid, mid.index)); + continue; + } + + Integer ver = canBus.moduleReadVersion(mid); + String moduleType = canBus.moduleReadType(mid).toString(); + if (maxVersion != null && !maxVersion.equals(ver)) { + hasDiffVersion = true; + } + if (maxVersion == null || ver > maxVersion) { + maxVersion = ver; + } + log.info(String.format("+ %-40s(%d): type:%-20s version:%s", mid, mid.index, moduleType, ver)); + } catch (HardwareException e) { + log.error(String.format("+ %20s(%d): offline", mid, mid.index)); + + } + } + + if (!hasDiffVersion) { + // gStateMgrService.setMcuVersion(String.format("V%04d", maxVersion)); + } else { + //版本不一致,标记为(*),表示需要升级 + // gStateMgrService.setMcuVersion(String.format("V%04d(*)", maxVersion)); + } + } + + + private void initA8kModParams() throws HardwareException { + for (MId mid : MId.values()) { + if (mid == MId.NotSet) + continue; + + log.info("============== {}({}) ===============", mid, mid.index); + + ModuleType moduleType = canBus.moduleReadType(mid); + List regIndexes = a8kSubModuleInitRegConfig.findRegIndexByModuleType(moduleType); + + for (RegIndex regIndex : regIndexes) { + SubModuleRegInitialValue val = subModuleRegInitialValueMgrService.findByIDAndRegIndex(mid, regIndex); + if (val == null) + continue; + + log.info(String.format("= init %s(%d) %-45s: %d", mid, mid.index, regIndex, val.regInitVal)); + canBus.moduleSetReg(mid, regIndex, val.regInitVal); + } + log.info("="); + } + } + + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/service/setup/SubModuleRegInitialValueMgrService.java b/src/main/java/com/iflytop/sgs/hardware/service/setup/SubModuleRegInitialValueMgrService.java new file mode 100644 index 0000000..338ef04 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/service/setup/SubModuleRegInitialValueMgrService.java @@ -0,0 +1,85 @@ +package com.iflytop.sgs.hardware.service.setup; + + +import com.iflytop.sgs.hardware.comm.can.A8kCanBusService; +import com.iflytop.sgs.hardware.config.A8kSubModuleInitRegConfig; +import com.iflytop.sgs.hardware.dao.SubModuleRegInitialValueDao; +import com.iflytop.sgs.hardware.exception.HardwareException; +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.ModuleType; +import com.iflytop.sgs.hardware.type.RegIndex; +import com.iflytop.sgs.hardware.type.db.SubModuleRegInitialValue; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + + +@Component +@Slf4j +@RequiredArgsConstructor +public class SubModuleRegInitialValueMgrService { + + private final SubModuleRegInitialValueDao subModuleRegInitialValueDao; + private final A8kCanBusService canBus; + private final A8kSubModuleInitRegConfig a8kSubModuleInitRegConfig; + + + /** + * 从微控制器同步寄存器值到数据库 + * @throws HardwareException e + */ + public void syncFromMirco() throws HardwareException { + List regcache = List.of(); + for (MId mid : MId.values()) { + if (mid == MId.NotSet) + continue; + + ModuleType moduleType = canBus.moduleReadType(mid); + List regIndexes = a8kSubModuleInitRegConfig.findRegIndexByModuleType(moduleType); + + for (RegIndex regIndex : regIndexes) { + log.info("read {}({})-{}", mid, mid.index, regIndex); + Integer val = canBus.moduleGetReg(mid, regIndex); + regcache.add(new SubModuleRegInitialValue(mid, regIndex, val)); + } + } + for (SubModuleRegInitialValue reg : regcache) { + log.info(String.format("storage %s(%d) %-40s: %d", reg.mid, reg.mid.index, reg.regIndex, reg.regInitVal)); + subModuleRegInitialValueDao.update(reg.mid, reg.regIndex, reg.regInitVal); + } + + // + // 删除掉不需要的寄存器 + // + var allConfigs = subModuleRegInitialValueDao.getAll(); + for (SubModuleRegInitialValue reg : allConfigs) { + ModuleType moduleType = canBus.moduleReadType(reg.mid); + if (!a8kSubModuleInitRegConfig.isNeeded(moduleType, reg.regIndex)) { + subModuleRegInitialValueDao.delete(reg.id); + } + } + } + + public void export() { + subModuleRegInitialValueDao.export(); + } + + public String getExportPath() { + return subModuleRegInitialValueDao.getExportPATH(); + } + + public void importFromCSV(String filecontent) { + subModuleRegInitialValueDao.importFromCSV(filecontent, true); + } + + public List getAll() { + return subModuleRegInitialValueDao.getAll(); + } + + public SubModuleRegInitialValue findByIDAndRegIndex(MId mid, RegIndex regIndex) { + return subModuleRegInitialValueDao.findByIDAndRegIndex(mid, regIndex); + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/A8kPacket.java b/src/main/java/com/iflytop/sgs/hardware/type/A8kPacket.java new file mode 100644 index 0000000..9813ce1 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/A8kPacket.java @@ -0,0 +1,227 @@ +package com.iflytop.sgs.hardware.type; + +import com.iflytop.sgs.common.utils.ByteArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class A8kPacket { + + //#pragma pack(push, 1) + //typedef struct { + // uint8_t packetType; + // uint16_t cmdid; + // uint8_t moduleId; + // uint16_t index; + // uint8_t datalen; + // uint8_t data[]; + // /* int8_t checksum;*/ + //} zcr_cmd_header_t; + // + //#pragma pack(pop) + //typedef enum { + // kptv2_cmd = 0xA0, + // kptv2_ack = 0xA1, + // kptv2_error_ack = 0xA2, + // kptv2_event = 0xA3, + //} zcan_cmd_packet_type_t; + // + + /** + * + * @WARNING + * 1. 修改这里时,需要注意连同createPacket一起修改 + * 2. PACKET_MIN_LEN 比Header多一个字节,是因为还有一个字节的校验位 + */ + + public static final int PACKET_TYPE_OFFSET = 0; + public static final int CMDID_OFFSET = 1; + public static final int MODULE_ID_OFFSET = 3; + public static final int INDEX_OFFSET = 4; + public static final int DATA_LEN_OFFSET = 6; + public static final int DATA_OFFSET = 7; + + public static final int PACKET_MIN_LEN = 8;// + + public static final int CMD_OVERTIME = 1500; + + + public static final int PACKET_TYPE_CMD = 0xA0; + public static final int PACKET_TYPE_ACK = 0xA1; + public static final int PACKET_TYPE_ERROR_ACK = 0xA2; + public static final int PACKET_TYPE_EVENT = 0xA3; + + public static final Logger logger = LoggerFactory.getLogger(A8kPacket.class); + + byte[] raw; + + public A8kPacket(byte[] cmd) { + raw = new byte[cmd.length]; + System.arraycopy(cmd, 0, raw, 0, cmd.length); + } + + public void setPacketIndex(int packetIndex) { + ByteArray.setU16bit(raw, INDEX_OFFSET, packetIndex); + int checkcode = computeCheckcode(); + ByteArray.setU8(raw, raw.length - 1, checkcode); + } + + + public int getPacketIndex() { + return ByteArray.readU16bit(raw, INDEX_OFFSET); + } + + public int getCmdId() { + return ByteArray.readU16bit(raw, CMDID_OFFSET); + } + + public int getPacketType() { + return ByteArray.readU8bit(raw, PACKET_TYPE_OFFSET); + } + + public int getModuleId() { + return ByteArray.readU8bit(raw, MODULE_ID_OFFSET); + } + + public int getDataLen() { + return ByteArray.readU8bit(raw, DATA_LEN_OFFSET); + } + + public byte[] getCmdContent() { + if (raw.length < PACKET_MIN_LEN) { + return new byte[0]; + } + byte[] cmdcontent = new byte[getDataLen()]; + System.arraycopy(raw, DATA_OFFSET, cmdcontent, 0, getDataLen()); + return cmdcontent; + } + + public int getContentI32(int index) { + return ByteArray.read32bit(raw, DATA_OFFSET + index * 4); + } + + public int getCheckcode() { + return ByteArray.readU8bit(raw, raw.length - 1); + } + + public int computeCheckcode() { + int checkcode = 0; + for (int i = 0; i < raw.length - 1; i++) { + checkcode += raw[i]; + } + return checkcode & 0xFF; + } + + public String toByteString() { + return ByteArray.toByteString(raw); + } + + public String toString() { + int packetType = getPacketType(); + String ret = ""; + + CmdId cmdId = CmdId.valueOf(getCmdId()); + Assert.isTrue(cmdId != null, "cmdId is null"); + if (packetType == PACKET_TYPE_CMD) { + + if (cmdId.equals(CmdId.module_get_reg) || cmdId.equals(CmdId.module_set_reg)) { + RegIndex regIndex = RegIndex.valueOf(getContentI32(0)); + if (regIndex != null) { + ret = String.format("[CMD ] index:[%d] %s %s %s(%d) %d", getPacketIndex(), cmdId, + MId.valueOf(getModuleId()), regIndex, regIndex.index, getContentI32(1)); + } else { + ret = String.format("[CMD ] index:[%d] %s unkown_index(%d) %d", getPacketIndex(), cmdId, + getContentI32(0), getContentI32(1)); + } + } else { + if (cmdId.getCmdAttachType() == CmdId.ATTACH_IS_INT32) { + ret = String.format("[CMD ] index:[%d] (%s %s(%d) :param:[%s])", getPacketIndex(), cmdId, + MId.valueOf(getModuleId()), getModuleId(), formatInt32ATTACH(getCmdContent())); + } else { + ret = String.format("[CMD ] index:[%d] (%s %s(%d) :param:[%s])", getPacketIndex(), cmdId, + MId.valueOf(getModuleId()), getModuleId(), ByteArray.toByteString(getCmdContent())); + } + } + + } else if (packetType == PACKET_TYPE_ACK) { + if (cmdId.getReceiptAttachType() == CmdId.ATTACH_IS_INT32) { + ret = String.format("[ACK ] index:[%d] (%s :[%s])", getPacketIndex(), cmdId, + formatInt32ATTACH(getCmdContent())); + } else { + ret = String.format("[ACK ] index:[%d] (%s :[%s])", getPacketIndex(), cmdId, + ByteArray.toByteString(getCmdContent())); + } + } else if (packetType == PACKET_TYPE_ERROR_ACK) { + ret = String.format("[EACK ] index:[%d] (%s :[%s])", getPacketIndex(), cmdId, (getContentI32(0))); + } else if (packetType == PACKET_TYPE_EVENT) { + if (cmdId.getCmdAttachType() == CmdId.ATTACH_IS_INT32) { + ret = String.format("[EVENT] index:[%d] (%s %s(%d) :[%s])", getPacketIndex(), cmdId, + MId.valueOf(getModuleId()), getModuleId(), formatInt32ATTACH(getCmdContent())); + } else { + ret = String.format("[EVENT] index:[%d] (%s %s(%d) :[%s])", getPacketIndex(), cmdId, + MId.valueOf(getModuleId()), getModuleId(), ByteArray.toByteString(getCmdContent())); + } + } else { + ret = String.format("Unknown packet type: %d", packetType); + } + return ret; + } + + private String formatInt32ATTACH(byte[] attach) { + StringBuilder ret = new StringBuilder(); + for (int i = 0; i < attach.length; i += 4) { + if (i + 3 >= attach.length) + break; + if (i != 0) + ret.append(","); + ret.append(String.format("%d", ByteArray.read32bit(attach, i))); + } + return ret.toString(); + } + + public Boolean isSupportPacket() { + CmdId cmdid = CmdId.valueOf(getCmdId()); + if (cmdid == null) { + return false; + } + if (CmdId.module_get_reg.equals(cmdid) || CmdId.module_set_reg.equals(cmdid)) { + if (getPacketType() == PACKET_TYPE_CMD) { + RegIndex regIndex = RegIndex.valueOf(getContentI32(0)); + return regIndex != null; + } + } + return true; + } + + + static public A8kPacket createPacket(Integer moduleId, int packetType, Integer cmdId, Integer[] params) { + int bufferSize = A8kPacket.PACKET_MIN_LEN + 4 * params.length; + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.put((byte) (packetType & 0xff)); // packetType + buffer.putShort((short) (cmdId & 0xffff)); // cmdid + buffer.put((byte) (moduleId & 0xFF)); // moduleId + buffer.put((byte) 0x4C); // index + buffer.put((byte) 0x00); // index + buffer.put((byte) (params.length * 4)); // datalen + for (int value : params) { + buffer.putInt(value); + } + // int8_t checksum; + int checksum = 0; + for (int i = 0; i < bufferSize - 1; i++) { + checksum += buffer.get(i); + } + buffer.put((byte) checksum); + return new A8kPacket(buffer.array()); + } + + public static void main(String[] args) { + var packet = createPacket(41, A8kPacket.PACKET_TYPE_CMD, CmdId.module_stop.index, new Integer[]{}); + logger.info("{}", packet.toByteString()); + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/CmdId.java b/src/main/java/com/iflytop/sgs/hardware/type/CmdId.java new file mode 100644 index 0000000..260eb54 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/CmdId.java @@ -0,0 +1,177 @@ +package com.iflytop.sgs.hardware.type; + + +public enum CmdId { + NotSet(0xFFFF, "NOACTION"),// + + board_reset(0x0000, "复位板子"),// + event_bus_reg_change_report(0x0064, "寄存器修改事件"),// + // + + module_ping(0x0101, "MODULE_PING"), + module_stop(0x0102, "MODULE_STOP"), + + module_get_error(0x0110, "MODULE_GET_ERROR"), + module_get_detail_error(0x0111, "MODULE_GET_DETAIL_ERROR"), + module_clear_error(0x0112, "MODULE_CLEAR_ERROR"), + + module_set_reg(0x0120, "MODULE_SET_REG"), + module_get_reg(0x0121, "MODULE_GET_REG"), + module_reset_reg(0x0123, "MODULE_RESET_REG"), + + module_get_version(0x0130, "MODULE_GET_VERSION"), + module_get_type(0x0131, "MODULE_GET_TYPE"), + module_get_status(0x0132, "MODULE_GET_STATUS"), + + // IO 板卡 1 + + // IO 板卡 2 + + // 电机通用控制 + step_motor_enable(0x0201, "STEP_MOTOR_ENABLE"),// + step_motor_read_pos(0x020b, "STEP_MOTOR_READ_POS"),// + step_motor_read_enc_pos(0x020c, "STEP_MOTOR_READ_ENC_POS"),// + step_motor_easy_rotate(0x0211, "STEP_MOTOR_EASY_ROTATE"),// + step_motor_easy_move_by(0x0212, "STEP_MOTOR_EASY_MOVE_BY"),// + step_motor_easy_move_to(0x0213, "STEP_MOTOR_EASY_MOVE_TO"),// + step_motor_easy_move_to_zero(0x0214, "STEP_MOTOR_EASY_MOVE_TO_ZERO"),// + step_motor_easy_set_current_pos(0x0215, "STEP_MOTOR_EASY_SET_CURRENT_POS"),// + step_motor_easy_move_to_io(0x0216, "STEP_MOTOR_EASY_MOVE_TO_IO"),// + step_motor_stop(0x0228, "STEP_MOTOR_STOP"),// + step_motor_move_by(0x021d, "STEP_MOTOR_MOVE_BY"), // (dpos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high) + step_motor_move_to(0x021e, "STEP_MOTOR_MOVE_TO"), // (pos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high) + step_motor_move_to_zero_point_quick(0x021f, "STEP_MOTOR_MOVE_TO_ZERO_POINT_QUICK"), // (speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high) + step_motor_rotate(0x0220, "STEP_MOTOR_ROTATE"), // + + step_motor_active_cfg(0x0229, "STEP_MOTOR_ACTIVE_CFG"),// + step_motor_read_io_state(0x022a, "STEP_MOTOR_READ_IO_STATE"),// + step_motor_easy_move_to_end_point(0x022c, "STEP_MOTOR_EASY_MOVE_TO_END_POINT"),// + step_motor_read_tmc5130_status(0x0232, "STEP_MOTOR_READ_TMC5130_STATUS"),// + step_motor_read_tmc5130_state(0x0233, "STEP_MOTOR_READ_TMC5130_STATE"),// + step_motor_read_io_index_in_stm32(0x0238, "STEP_MOTOR_READ_IO_INDEX_IN_STM32"),// + step_motor_set_subdevice_reg(0x0239, "STEP_MOTOR_SET_SUBDEVICE_REG"),// + step_motor_get_subdevice_reg(0x023a, "STEP_MOTOR_GET_SUBDEVICE_REG"),// + step_motor_easy_reciprocating_motion(0x022d, "STEP_MOTOR_EASY_RECIPROCATING_MOTION"),// + step_motor_easy_move_to_zero_point_quick(0x022e, "STEP_MOTOR_EASY_MOVE_TO_ZERO_POINT_QUICK"), + + + mini_servo_enable(0x3601, "MINI_SERVO_ENABLE"), + mini_servo_read_pos(0x3602, "MINI_SERVO_READ_POS"), + mini_servo_active_cfg(0x3603, "MINI_SERVO_ACTIVE_CFG"), + mini_servo_stop(0x3604, "MINI_SERVO_STOP"), + mini_servo_set_mid_point(0x3607, "MINI_SERVO_SET_MID_POINT"), + mini_servo_read_io_state(0x3608, "MINI_SERVO_READ_IO_STATE"), + mini_servo_move_to(0x3609, "MINI_SERVO_MOVE_TO"), + mini_servo_rotate(0x360a, "MINI_SERVO_ROTATE"), + mini_servo_rotate_with_torque(0x360b, "MINI_SERVO_ROTATE_WITH_TORQUE"), + mini_servo_set_cur_pos(0x360c, "MINI_SERVO_SET_CUR_POS"), + + leisai_servo_enable(0x3701, "LEISAI_SERVO_ENABLE"),// + leisai_servo_read_pos(0x3702, "LEISAI_SERVO_READ_POS"),// + leisai_servo_read_io_state(0x3703, "LEISAI_SERVO_READ_IO_STATE"),// + leisai_servo_move_by(0x3704, "LEISAI_SERVO_MOVE_BY"), // (dpos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high) + leisai_servo_move_to(0x3705, "LEISAI_SERVO_MOVE_TO"), // (pos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high) + leisai_servo_rotate(0x3706, "LEISAI_SERVO_ROTATE"), // (direction,speedlevel)-> null + leisai_servo_move_to_zero(0x3707, "LEISAI_SERVO_MOVE_TO_ZERO"), // (null)-> null + + liquid_distribution_arm_enable(0x3801, "LIQUID_DISTRIBUTION_ARM_ENABLE"),// + liquid_distribution_arm_move_to(0x3802, "LIQUID_DISTRIBUTION_ARM_MOVE_TO"), // (pos)->null pos=(-1,0,1...16) -1代表未知未知,0代表待机位置,1...16分别对应16加酸孔位 + liquid_distribution_arm_read_pos(0x3803, "LIQUID_DISTRIBUTION_ARM_READ_POS"), // (null)->pos pos=(-1,0,1...16) -1代表未知未知,0代表待机位置,1...16分别对应16加酸孔位 + liquid_distribution_arm_set_cur_pos_as_mid_pos(0x3804, "LIQUID_DISTRIBUTION_ARM_SET_CUR_POS_AS_MID_POS"), // (null)->null 校准舵机 + liquid_distribution_arm_set_cur_pos_as_preset_pos(0x3805, "LIQUID_DISTRIBUTION_ARM_SET_CUR_POS_AS_PRESET_POS"), // 设置当前位置作为预设位置(posindex)->null pos=(0,1...16) 0代表未知未知,1...16分别对应16加酸孔位 + + read_in_io(0x4001, "READ_IN_IO"), + write_out_io(0x4002, "WRITE_OUT_IO"), + read_muti_in_io(0x4003, "READ_MUTI_IN_IO"), + read_in_io_index_in_stm32(0x4004, "READ_IN_IO_INDEX_IN_STM32"), + read_out_io_index_in_stm32(0x4005, "READ_OUT_IO_INDEX_IN_STM32"), + read_out_io(0x4006, "READ_OUT_IO"), + + tricolor_light_on(0x5101, "TRICOLOR_LIGHT_ON"), + tricolor_light_off(0x5102, "TRICOLOR_LIGHT_OFF"), + + pwm_light_on(0x5201, "PWM_LIGHT_ON"), + pwm_light_off(0x5202, "PWM_LIGHT_OFF"), + + adc_enable_log(0x05301, "ADC_ENABLE_LOG"), + adc_read_adc(0x05302, "ADC_READ_ADC"), + adc_read_multi_adc(0x05303, "ADC_READ_MULTI_ADC"), + ; + + public final static int ATTACH_IS_BYTES = 1; + public final static int ATTACH_IS_INT32 = 2; + public final int index; + public final String chName; + + CmdId(int index, String chname) { + this.index = index; + this.chName = chname; + } + + public int toInt() { + return index; + } + + public static CmdId valueOf(int val) { + CmdId[] values = CmdId.values(); + for (CmdId e : values) { + if (e.toInt() == val) + return e; + } + return null; + } + + public static String toString(int val) { + CmdId[] values = CmdId.values(); + for (CmdId e : values) { + if (e.toInt() == val) { + return e.toString(); + } + } + return "unkown(" + val + ")"; + } + + public String getChname() { + return chName; + } + + public Boolean eq(Integer index) { + return this.index == index; + } + + + public int getCmdAttachType() { + return switch (this) { + default -> ATTACH_IS_INT32; + }; + } + + public int getReceiptAttachType() { + return switch (this) { + default -> ATTACH_IS_INT32; + }; + } + + public boolean isTrace() { + return true; + } + + public boolean isActionCmd() { + + return switch (this) { + case module_get_status, + module_get_error, + module_get_reg, + module_set_reg, + step_motor_read_io_state, + read_in_io, + read_muti_in_io, + read_in_io_index_in_stm32, + read_out_io_index_in_stm32, + adc_read_adc, + adc_read_multi_adc -> false; + default -> true; + }; + } +} + diff --git a/src/main/java/com/iflytop/sgs/hardware/type/IO/InputIOMId.java b/src/main/java/com/iflytop/sgs/hardware/type/IO/InputIOMId.java new file mode 100644 index 0000000..71cfc5b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/IO/InputIOMId.java @@ -0,0 +1,34 @@ +package com.iflytop.sgs.hardware.type.IO; + +import com.iflytop.sgs.hardware.type.MId; + +public enum InputIOMId { + HEAT_MODULE01_EXIST("HEAT_MODULE01_EXIST [ 加热模1传感器存在 ]", MId.IO1_IO, 0,false), + HEAT_MODULE02_EXIST("HEAT_MODULE02_EXIST [ 加热模2传感器存在 ]", MId.IO1_IO, 1,false), + HEAT_MODULE03_EXIST("HEAT_MODULE03_EXIST [ 加热模3传感器存在 ]", MId.IO1_IO, 2,false), + HEAT_MODULE04_EXIST("HEAT_MODULE04_EXIST [ 加热模4传感器存在 ]", MId.IO1_IO, 3,false), + HEAT_MODULE05_EXIST("HEAT_MODULE05_EXIST [ 加热模5传感器存在 ]", MId.IO1_IO, 4,false), + HEAT_MODULE06_EXIST("HEAT_MODULE06_EXIST [ 加热模6传感器存在 ]", MId.IO1_IO, 5,false), + CAP01_EXIST("CAP01_EXIST [ 拍子存放位拍子1到位 ]", MId.IO1_IO, 6,false), + CAP02_EXIST("CAP02_EXIST [ 拍子存放位拍子2到位 ]", MId.IO1_IO, 7,false), + CAP03_EXIST("CAP03_EXIST [ 拍子存放位拍子3到位 ]", MId.IO1_IO, 8,false), + CAP04_EXIST("CAP04_EXIST [ 拍子存放位拍子4到位 ]", MId.IO1_IO, 9,false), + CAP05_EXIST("CAP05_EXIST [ 拍子存放位拍子5到位 ]", MId.IO1_IO, 10,false), + CAP06_EXIST("CAP06_EXIST [ 拍子存放位拍子6到位 ]", MId.IO1_IO, 11,false), + + E_STOP("E_STOP [ 急停 ]", MId.IO1_IO, 18,false), + ; + + final public String description; + final public MId mid; + final public int ioIndex; + final public boolean mirror; + + InputIOMId(String description, MId mid, int ioIndex, boolean mirror) { + this.description = description; + this.mid = mid; + this.ioIndex = ioIndex; + this.mirror = mirror; + + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/IO/OutputIOMId.java b/src/main/java/com/iflytop/sgs/hardware/type/IO/OutputIOMId.java new file mode 100644 index 0000000..2f7c9e2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/IO/OutputIOMId.java @@ -0,0 +1,34 @@ +package com.iflytop.sgs.hardware.type.IO; + +import com.iflytop.sgs.hardware.type.MId; + +public enum OutputIOMId { + DO_FAN1("FAN1", MId.IO1_IO, 0,false), + DO_FAN2("FAN2", MId.IO1_IO, 1,false), + DO_FAN3("FAN3", MId.IO1_IO, 2,false), + DO_FAN4("FAN4", MId.IO1_IO, 3,false), + DO_FAN5("FAN5", MId.IO1_IO, 4,false), + DO_FAN6("FAN6", MId.IO1_IO, 5,false), + DO_TRAY_MOTOR_CLAMP("TRAY_MOTOR_CLAMP [ 托盘电机 ] ", MId.IO1_IO, 6,false), + DO_HBOTZ_MOTOR_CLAMP("HBOTZ_MOTOR_CLAMP [ 龙门架Z轴 ] ", MId.IO1_IO, 7,false), + DO_WATER_PUMP("WATER_PUMP [ 水泵 ] ", MId.IO1_IO, 12,false), + DO_VENTILATOR("VENTILATOR [ 风机电源 ]", MId.IO1_IO, 13,false), + DO_COLD_TRAP_POWER("COLD_TRAP_POWER[ 冷阱 电源 ]", MId.IO1_IO, 14,false), + DO_BEEP("DO_BEEP [ 蜂鸣器 ]", MId.IO1_IO, 16,false), + DO_OUT17("DO_OUT17", MId.IO1_IO, 17,false), + ; + + final public String description; + final public MId mid; + final public int ioIndex; + final public boolean mirror; + + OutputIOMId(String description, MId mid, int ioIndex, boolean mirror) { + this.description = description; + this.mid = mid; + this.ioIndex = ioIndex; + this.mirror = mirror; + + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/MId.java b/src/main/java/com/iflytop/sgs/hardware/type/MId.java new file mode 100644 index 0000000..b39bf06 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/MId.java @@ -0,0 +1,109 @@ +package com.iflytop.sgs.hardware.type; + +/** + * MID 现版本 取值范围 0-255 + */ +public enum MId { + NotSet(0, "未设置"), + IO1Board(5, "台面 IO 板模块"), + PWMLight(6, "PWM 灯"), + TriColorLight(7, "三色灯"), + HBotClawSV(8, "夹爪舵机"),// + DualRobotAxis1SV(9, "双轴机械臂1舵机"), + DualRobotAxis2SV(10, "双轴机械臂2舵机"), + IO1_IO(11, "台面 IO 板模块"), + IO1_ADC(12, "台面 ADC 板模块"), + LiquidDistributionArm(13, "加液臂"), + MainXSV(14, "X轴Leisai舵机"),// + MainYSV(15, "Y轴Leisai舵机"),// + +// IO2Board(25, "台下 IO 板模块"), + + DoorBoard(40, "门电机板模块"),// + DoorM(41, "门电机"), + + ShakeModBoard(45, "摇匀模组板"), // + ShakeM(46, "加液位摇匀电机"), // + + CapStorageBoard(50, "拍子存放板模块"), // + CapStorageM(51, "拍子存放电机"), // + + HBotXBoard(65, "X轴板模块"),// + HBotXM(66, "X轴电机"), + HBotYBoard(70, "Y轴板模块"),// + HBotYM(71, "Y轴电机"), + HBotZBoard(75, "Z轴板模块"),// + HBotZM(76, "Z轴电机"), + + Heater1Board(80, "加热1板模块"),// + Heater1M(81, "加热1电机"), + Heater2Board(85, "加热2板模块"),// + Heater2M(86, "加热2电机"), + Heater3Board(90, "加热3板模块"),// + Heater3M(91, "加热3电机"), + Heater4Board(95, "加热4板模块"),// + Heater4M(96, "加热4电机"), + Heater5Board(100, "加热5板模块"),// + Heater5M(101, "加热5电机"), + Heater6Board(105, "加热6板模块"),// + Heater6M(106, "加热6电机"), + + AcidPump1Board(110, "加酸泵1板模块"),// + AcidPump1M(111, "加酸泵1电机"), + AcidPump2Board(115, "加酸泵2板模块"),// + AcidPump2M(116, "加酸泵2电机"), + AcidPump3Board(120, "加酸泵3板模块"),// + AcidPump3M(121, "加酸泵3电机"), + AcidPump4Board(125, "加酸泵4板模块"),// + AcidPump4M(126, "加酸泵4电机"), + AcidPump5Board(130, "加酸泵5板模块"),// + AcidPump5M(131, "加酸泵5电机"), + AcidPump6Board(135, "加酸泵6板模块"),// + AcidPump6M(136, "加酸泵6电机"), + AcidPump7Board(140, "加酸泵7板模块"),// + AcidPump7M(141, "加酸泵7电机"), + AcidPump8Board(145, "加酸泵8板模块"),// + AcidPump8M(146, "加酸泵8电机"), + ; + + final public String description; + final public int index; + + MId(int index, String description) { + this.description = description; + this.index = index; + } + + public int toInt() { + return index; + } + + public String getDescription() + { + return description; + } + + + public static MId valueOf(Integer val) { + return valueOf(val.intValue()); + } + + public static MId valueOf(int val) { + MId[] values = MId.values(); + for (MId e : values) { + if (e.toInt() == val) + return e; + } + return null; + } + + public static String toString(int val) { + MId[] values = MId.values(); + for (MId e : values) { + if (e.toInt() == val) { + return e.toString(); + } + } + return "unknown(" + val + ")"; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/ModuleStatus.java b/src/main/java/com/iflytop/sgs/hardware/type/ModuleStatus.java new file mode 100644 index 0000000..57ba644 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/ModuleStatus.java @@ -0,0 +1,25 @@ +package com.iflytop.sgs.hardware.type; + +import org.springframework.util.Assert; + +public enum ModuleStatus { + IDLE(0), // + BUSY(1), // + ERROR(2),// + ; + + final public int index; + + ModuleStatus(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + + public static ModuleStatus valueOf(int value) { // 手写的从int到enum的转换函数 + Assert.isTrue((value >= 0 && value <= 2), String.format("(value=%s value >= 0 && value <= 2)", value)); + return values()[value]; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/ModuleType.java b/src/main/java/com/iflytop/sgs/hardware/type/ModuleType.java new file mode 100644 index 0000000..4b7a77e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/ModuleType.java @@ -0,0 +1,33 @@ +package com.iflytop.sgs.hardware.type; + +public enum ModuleType { + Board(1), // 板子 + + TMCStepMotor(2), // 步进电机 + MiniServo(3), // 舵机 + IO(4), // IO扩展 + + TriColorLightCtrl(101), // + PwmLightCtrl(102), // + ADC(103), // ADC扩展 + LeiSaiServo(104), // 雷赛伺服 + LiquidDistributionArm(105), // 液体分配机械臂控制 + + UNKNOWN(999); // 未知类型 + public final int code; + + ModuleType(int val) { + this.code = val; + } + + static public ModuleType of(int val) { + for (ModuleType type : ModuleType.values()) { + if (type.code == val) { + return type; + } + } + return UNKNOWN; + } + + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/RegIndex.java b/src/main/java/com/iflytop/sgs/hardware/type/RegIndex.java new file mode 100644 index 0000000..4f8f8ae --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/RegIndex.java @@ -0,0 +1,192 @@ +package com.iflytop.sgs.hardware.type; + +/** + * @brief 寄存器索引 + */ +public enum RegIndex { + + /*********************************************************************************************************************** + * 步进电机控制器 * + ***********************************************************************************************************************/ + /*********************************************************************************************************************** + * step_motor start * + ***********************************************************************************************************************/ + + kreg_step_motor_pos(10101), // 机器人x坐标 + kreg_step_motor_is_enable(10102), // 是否使能 + kreg_step_motor_dpos(10103), // 执行完上一条指令后的相对位移 + kreg_step_motor_has_move_zero(10104), // 是否回零 + // kreg_step_motor_shift(10150), // x偏移 + kreg_step_motor_shaft(10151), // x轴是否反转 + kreg_step_motor_one_circle_pulse(10152), // x轴一圈脉冲数 + kreg_step_motor_one_circle_pulse_denominator(10153), // 设置一圈脉冲数的分母 + kreg_step_motor_default_velocity(10154), // 默认速度 + kreg_step_motor_low_velocity(10155), // 低速 + kreg_step_motor_mid_velocity(10156), // 中速 + kreg_step_motor_high_velocity(10157), // 高速 + kreg_step_motor_ihold(10158), // 步进电机电流配置 + kreg_step_motor_irun(10159), // 步进电机电流配置 + kreg_step_motor_iholddelay(10160), // 步进电机电流配置 + kreg_step_motor_iglobalscaler(10161), // 步进电机电流配置 + kreg_step_motor_mres(10162), // 电机细分 + kreg_step_motor_run_to_zero_speed(10173), // 回零速度 + kreg_step_motor_look_zero_edge_speed(10175), // 找零边缘速度 + kreg_step_motor_max_d(10178), // 最大限制距离 + kreg_step_motor_min_d(10179), // 最小限制距离 + kreg_step_motor_in_debug_mode(10180), // 驱动器处于调试模式 + kreg_step_motor_vstart(10181), // a1起作用的速度 + kreg_step_motor_a1(10182), // + kreg_step_motor_amax(10183), // + kreg_step_motor_v1(10184), // + kreg_step_motor_dmax(10185), // + kreg_step_motor_d1(10186), // + kreg_step_motor_vstop(10187), // + kreg_step_motor_tzerowait(10188), // + kreg_step_motor_enc_resolution(10189), // 编码器分辨率 1000,1024,4000,4096,16384 + kreg_step_motor_enable_enc(10190), // + kreg_step_motor_dzero_pos(10191), // 驱动器处于调试模式 + kret_step_motor_pos_devi_tolerance(10192), // 位置偏差容忍度 + kret_step_motor_io_trigger_append_distance(10193), // 移动到io时,附加的距离 + + /*********************************************************************************************************************** + * step_motor end * + ***********************************************************************************************************************/ + + + /*********************************************************************************************************************** + * servo * + ***********************************************************************************************************************/ + + kreg_mini_servo_pos(10201), // 位置 + kreg_mini_servo_limit_velocity(10251), // 限制速度 + kreg_mini_servo_limit_torque(10252), // 限制扭矩 + kreg_mini_servo_protective_torque(10253), // 保护扭矩 + kreg_mini_servo_is_move(10254), // 是否在运动 + kreg_mini_servo_status(10255), // 舵机状态 + kreg_mini_servo_voltage(10256), // 电压 反馈当前舵机工作电压,反馈精度为0.1V,即120*0.1=12V + kreg_mini_servo_current(10257), // 电流 反馈当前工作电流值,单位为ma + kreg_mini_servo_temperature(10258), // 温度 反馈当前舵机内部工作温度,反馈精度为1摄氏度 + kreg_mini_servo_loadvalue(10259), // 负载值 输出驱动电机的当前占空比电压,单位为0.1%,取值0-1000 + kreg_mini_servo_target_pos_tolerance(10260), // 目标位置容忍度 + + + kreg_mini_servo_firmware_main_version(10500), // 固件主版本号 + kreg_mini_servo_firmware_sub_version(10501), // 固件次版本号 + kreg_mini_servo_servo_main_version(10503), // 舵机主版本号 + kreg_mini_servo_servo_sub_version(10504), // 舵机次版本号 + kreg_mini_servo_servo_min_angle(10509), // 最小角度限制 + kreg_mini_servo_servo_max_angle(10511), // 最大角度限制 + kreg_mini_servo_servo_max_temp(10513), // 最高温度上限 + kreg_mini_servo_servo_max_voltage(10514), // 最高输入电压 + kreg_mini_servo_servo_min_voltage(10515), // 最低输入电压 + kreg_mini_servo_servo_max_torque(10516), // 最大扭矩 + kreg_mini_servo_servo_unload_condition(10519), // 卸载条件 + kreg_mini_servo_servo_p(10521), // P 比例系 + kreg_mini_servo_servo_d(10522), // D 微分系 + kreg_mini_servo_servo_i(10523), // I + kreg_mini_servo_servo_min_start(10524), // 最小启动 + kreg_mini_servo_servo_cw_dead_zone(10526), // 顺时针不灵敏区 + kreg_mini_servo_servo_ccw_dead_zone(10527), // 逆时针不灵敏 + kreg_mini_servo_servo_protect_current(10528), // 保护电流 + kreg_mini_servo_servo_protect_torque(10534), // 保护扭矩 0->100 ,触发后,需要写入与组转方向相反的位置指令,进行解除 + kreg_mini_servo_servo_protect_time(10535), // 保护时间 + kreg_mini_servo_servo_overload_torque(10536), // 过载扭矩 + kreg_mini_servo_servo_speed_p(10537), // 速度闭环P比例参数 + kreg_mini_servo_servo_overload_time(10538), // 过流保护时间 + kreg_mini_servo_servo_speed_i(10539), // 速度闭环I积分参数 + kreg_mini_servo_servo_torque_switch(10540), // 扭矩开关 + kreg_mini_servo_servo_acc(10541), // 加速度 + kreg_mini_servo_servo_target_pos(10542), // 目标位置 + kreg_mini_servo_servo_run_time(10544), // 运行时间 + kreg_mini_servo_servo_run_speed(10546), // 运行速度 + kreg_mini_servo_servo_torque_limit(10548), // 转矩限制 + kreg_mini_servo_servo_lock_flag(10555), // 锁标志 + kreg_mini_servo_servo_current_pos(10556), // 当前位置 + kreg_mini_servo_servo_current_speed(10558), // 当前速度 + kreg_mini_servo_servo_current_load(10560), // 当前负载 bit10为方向位 输出驱动电机的当前占空比电压,单位为0.1%,取值0-1000 + kreg_mini_servo_servo_current_voltage(10562), // 当前电压 反馈当前舵机工作电压,反馈精度为0.1V,即120*0.1=12V + kreg_mini_servo_servo_current_temp(10563), // 当前温度 反馈当前舵机内部工作温度,反馈精度为1摄氏度 + kreg_mini_servo_servo_status(10565), // 舵机状态 + kreg_mini_servo_servo_move_flag(10566), // 移动标志 + kreg_mini_servo_servo_current_current(10569), // 当前电流 反馈当前工作电流值,单位为6.26.5mA,最大可反馈电流为500*6.5mA=3250mA + + + kreg_liquid_distribution_arm_enable(10600), // 电机使能 + kreg_liquid_distribution_arm_pos0_d0(10610), // 位置0 舵机0位置 + kreg_liquid_distribution_arm_pos0_d1(10611), // 位置0 舵机1位置 + kreg_liquid_distribution_arm_pos1_d0(10612), // 位置1 舵机0位置 + kreg_liquid_distribution_arm_pos1_d1(10613), // 位置1 舵机1位置 + kreg_liquid_distribution_arm_pos2_d0(10614), // 位置2 舵机0位置 + kreg_liquid_distribution_arm_pos2_d1(10615), // 位置2 舵机1位置 + kreg_liquid_distribution_arm_pos3_d0(10616), // 位置3 舵机0位置 + kreg_liquid_distribution_arm_pos3_d1(10617), // 位置3 舵机1位置 + kreg_liquid_distribution_arm_pos4_d0(10618), // 位置4 舵机0位置 + kreg_liquid_distribution_arm_pos4_d1(10619), // 位置4 舵机1位置 + kreg_liquid_distribution_arm_pos5_d0(10620), // 位置5 舵机0位置 + kreg_liquid_distribution_arm_pos5_d1(10621), // 位置5 舵机1位置 + kreg_liquid_distribution_arm_pos6_d0(10622), // 位置6 舵机0位置 + kreg_liquid_distribution_arm_pos6_d1(10623), // 位置6 舵机1位置 + kreg_liquid_distribution_arm_pos7_d0(10624), // 位置7 舵机0位置 + kreg_liquid_distribution_arm_pos7_d1(10625), // 位置7 舵机1位置 + kreg_liquid_distribution_arm_pos8_d0(10626), // 位置8 舵机0位置 + kreg_liquid_distribution_arm_pos8_d1(10627), // 位置8 舵机1位置 + kreg_liquid_distribution_arm_pos9_d0(10628), // 位置9 舵机0位置 + kreg_liquid_distribution_arm_pos9_d1(10629), // 位置9 舵机1位置 + kreg_liquid_distribution_arm_pos10_d0(10630), // 位置10 舵机0位置 + kreg_liquid_distribution_arm_pos10_d1(10631), // 位置10 舵机1位置 + kreg_liquid_distribution_arm_pos11_d0(10632), // 位置11 舵机0位置 + kreg_liquid_distribution_arm_pos11_d1(10633), // 位置11 舵机1位置 + kreg_liquid_distribution_arm_pos12_d0(10634), // 位置12 舵机0位置 + kreg_liquid_distribution_arm_pos12_d1(10635), // 位置12 舵机1位置 + kreg_liquid_distribution_arm_pos13_d0(10636), // 位置13 舵机0位置 + kreg_liquid_distribution_arm_pos13_d1(10637), // 位置13 舵机1位置 + kreg_liquid_distribution_arm_pos14_d0(10638), // 位置14 舵机0位置 + kreg_liquid_distribution_arm_pos14_d1(10639), // 位置14 舵机1位置 + kreg_liquid_distribution_arm_pos15_d0(10640), // 位置15 舵机0位置 + kreg_liquid_distribution_arm_pos15_d1(10641), // 位置15 舵机1位置 + kreg_liquid_distribution_arm_pos16_d0(10642), // 位置16 舵机0位置 + kreg_liquid_distribution_arm_pos16_d1(10643), // 位置16 舵机1位置 + + kreg_leisai_servo_is_enable(10702), // 是否使能 + kreg_leisai_servo_has_move_zero(10703), // 是否回零 + kreg_leisai_servo_default_velocity(10704), // 默认速度 + kreg_leisai_servo_low_velocity(10705), // 低速 + kreg_leisai_servo_mid_velocity(10706), // 中速 + kreg_leisai_servo_high_velocity(10707); // 高速 + ; + + public final int index; + public final Boolean trace; + + RegIndex(int regIndex) { + this.index = regIndex; + this.trace = true; + } + + RegIndex(int regIndex, Boolean trace) { + this.index = regIndex; + this.trace = trace; + } + + public static RegIndex valueOf(int val) { + RegIndex[] values = RegIndex.values(); + for (RegIndex regindex : values) { + if (regindex.index == val) { + return regindex; + } + } + return null; + } + + public static RegIndex fromString(String regStr) + { + RegIndex[] values = RegIndex.values(); + for (RegIndex regindex : values) { + if (regindex.toString().equalsIgnoreCase(regStr)) { + return regindex; + } + } + return null; + } +} + diff --git a/src/main/java/com/iflytop/sgs/hardware/type/Servo/DeviceServoId.java b/src/main/java/com/iflytop/sgs/hardware/type/Servo/DeviceServoId.java new file mode 100644 index 0000000..b7d0577 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/Servo/DeviceServoId.java @@ -0,0 +1,33 @@ +package com.iflytop.sgs.hardware.type.Servo; + +public enum DeviceServoId { + dual_robot_axis1(MiniServoMId.DUAL_ROBOT_AXIS1_MID, "双轴械臂一轴"), + dual_robot_axis2(MiniServoMId.DUAL_ROBOT_AXIS2_MID, "双轴械臂二轴"), + claw(MiniServoMId.CLAW_MID, "夹爪"), + ; + + private MiniServoMId servoMId; + private String description; + + public MiniServoMId getServoMId() { + return servoMId; + } + + public String getDescription() { + return description; + } + + DeviceServoId(MiniServoMId servoMId, String description) { + this.servoMId = servoMId; + this.description = description; + } + + public static DeviceServoId getById(MiniServoMId servoMId) { + for (DeviceServoId deviceServoId : DeviceServoId.values()) { + if (deviceServoId.getServoMId() == servoMId) { + return deviceServoId; + } + } + return null; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiRegIndex.java b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiRegIndex.java new file mode 100644 index 0000000..974a4cc --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiRegIndex.java @@ -0,0 +1,19 @@ +package com.iflytop.sgs.hardware.type.Servo; + +import com.iflytop.sgs.hardware.type.RegIndex; + +public enum LeisaiRegIndex { + kreg_leisai_servo_is_enable(RegIndex.kreg_leisai_servo_is_enable), + kreg_leisai_servo_has_move_zero(RegIndex.kreg_leisai_servo_has_move_zero), + kreg_leisai_servo_default_velocity(RegIndex.kreg_leisai_servo_default_velocity), + kreg_leisai_servo_low_velocity(RegIndex.kreg_leisai_servo_low_velocity), + kreg_leisai_servo_mid_velocity(RegIndex.kreg_leisai_servo_mid_velocity), + kreg_leisai_servo_high_velocity(RegIndex.kreg_leisai_servo_high_velocity), + ; + + public final RegIndex regIndex; + + LeisaiRegIndex(RegIndex regIndex) { + this.regIndex = regIndex; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiServoMId.java b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiServoMId.java new file mode 100644 index 0000000..217c163 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiServoMId.java @@ -0,0 +1,17 @@ +package com.iflytop.sgs.hardware.type.Servo; + +import com.iflytop.sgs.hardware.type.MId; +import org.springframework.util.Assert; + +public enum LeisaiServoMId { + MainXSV(MId.MainXSV), + MainYSV(MId.MainYSV), + ; + + final public MId mid; + + LeisaiServoMId(MId mid) { + Assert.isTrue(this.name().equals(mid.name()), "LeisaiServoMId Init fail"); + this.mid = mid; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiServoSpeedLevel.java b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiServoSpeedLevel.java new file mode 100644 index 0000000..b3897b2 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LeisaiServoSpeedLevel.java @@ -0,0 +1,9 @@ +package com.iflytop.sgs.hardware.type.Servo; + +public enum LeisaiServoSpeedLevel { + DEFAULT, + LOW, + MID, + HIGH, + ; +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/Servo/LiquidArmMId.java b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LiquidArmMId.java new file mode 100644 index 0000000..2c8853d --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LiquidArmMId.java @@ -0,0 +1,16 @@ +package com.iflytop.sgs.hardware.type.Servo; + +import com.iflytop.sgs.hardware.type.MId; +import org.springframework.util.Assert; + +public enum LiquidArmMId { + LiquidDistributionArm(MId.LiquidDistributionArm),// + ; + + final public MId mid; + + LiquidArmMId(MId mid) { + Assert.isTrue(this.name().equals(mid.name()), "LiquidDistributionArm Init fail"); + this.mid = mid; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/Servo/LiquidArmRegIndex.java b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LiquidArmRegIndex.java new file mode 100644 index 0000000..149c595 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/Servo/LiquidArmRegIndex.java @@ -0,0 +1,47 @@ +package com.iflytop.sgs.hardware.type.Servo; + +import com.iflytop.sgs.hardware.type.RegIndex; + +public enum LiquidArmRegIndex { + kreg_liquid_distribution_arm_enable(RegIndex.kreg_liquid_distribution_arm_enable), // 电机使能 + kreg_liquid_distribution_arm_pos0_d0(RegIndex.kreg_liquid_distribution_arm_pos0_d0), // 位置0 舵机0位置 + kreg_liquid_distribution_arm_pos0_d1(RegIndex.kreg_liquid_distribution_arm_pos0_d1), // 位置0 舵机1位置 + kreg_liquid_distribution_arm_pos1_d0(RegIndex.kreg_liquid_distribution_arm_pos1_d0), // 位置1 舵机0位置 + kreg_liquid_distribution_arm_pos1_d1(RegIndex.kreg_liquid_distribution_arm_pos1_d1), // 位置1 舵机1位置 + kreg_liquid_distribution_arm_pos2_d0(RegIndex.kreg_liquid_distribution_arm_pos2_d0), // 位置2 舵机0位置 + kreg_liquid_distribution_arm_pos2_d1(RegIndex.kreg_liquid_distribution_arm_pos2_d1), // 位置2 舵机1位置 + kreg_liquid_distribution_arm_pos3_d0(RegIndex.kreg_liquid_distribution_arm_pos3_d0), // 位置3 舵机0位置 + kreg_liquid_distribution_arm_pos3_d1(RegIndex.kreg_liquid_distribution_arm_pos3_d1), // 位置3 舵机1位置 + kreg_liquid_distribution_arm_pos4_d0(RegIndex.kreg_liquid_distribution_arm_pos4_d0), // 位置4 舵机0位置 + kreg_liquid_distribution_arm_pos4_d1(RegIndex.kreg_liquid_distribution_arm_pos4_d1), // 位置4 舵机1位置 + kreg_liquid_distribution_arm_pos5_d0(RegIndex.kreg_liquid_distribution_arm_pos5_d0), // 位置5 舵机0位置 + kreg_liquid_distribution_arm_pos5_d1(RegIndex.kreg_liquid_distribution_arm_pos5_d1), // 位置5 舵机1位置 + kreg_liquid_distribution_arm_pos6_d0(RegIndex.kreg_liquid_distribution_arm_pos6_d0), // 位置6 舵机0位置 + kreg_liquid_distribution_arm_pos6_d1(RegIndex.kreg_liquid_distribution_arm_pos6_d1), // 位置6 舵机1位置 + kreg_liquid_distribution_arm_pos7_d0(RegIndex.kreg_liquid_distribution_arm_pos7_d0), // 位置7 舵机0位置 + kreg_liquid_distribution_arm_pos7_d1(RegIndex.kreg_liquid_distribution_arm_pos7_d1), // 位置7 舵机1位置 + kreg_liquid_distribution_arm_pos8_d0(RegIndex.kreg_liquid_distribution_arm_pos8_d0), // 位置8 舵机0位置 + kreg_liquid_distribution_arm_pos8_d1(RegIndex.kreg_liquid_distribution_arm_pos8_d1), // 位置8 舵机1位置 + kreg_liquid_distribution_arm_pos9_d0(RegIndex.kreg_liquid_distribution_arm_pos9_d0), // 位置9 舵机0位置 + kreg_liquid_distribution_arm_pos9_d1(RegIndex.kreg_liquid_distribution_arm_pos9_d1), // 位置9 舵机1位置 + kreg_liquid_distribution_arm_pos10_d0(RegIndex.kreg_liquid_distribution_arm_pos10_d0), // 位置10 舵机0位置 + kreg_liquid_distribution_arm_pos10_d1(RegIndex.kreg_liquid_distribution_arm_pos10_d1), // 位置10 舵机1位置 + kreg_liquid_distribution_arm_pos11_d0(RegIndex.kreg_liquid_distribution_arm_pos11_d0), // 位置11 舵机0位置 + kreg_liquid_distribution_arm_pos11_d1(RegIndex.kreg_liquid_distribution_arm_pos11_d1), // 位置11 舵机1位置 + kreg_liquid_distribution_arm_pos12_d0(RegIndex.kreg_liquid_distribution_arm_pos12_d0), // 位置12 舵机0位置 + kreg_liquid_distribution_arm_pos12_d1(RegIndex.kreg_liquid_distribution_arm_pos12_d1), // 位置12 舵机1位置 + kreg_liquid_distribution_arm_pos13_d0(RegIndex.kreg_liquid_distribution_arm_pos13_d0), // 位置13 舵机0位置 + kreg_liquid_distribution_arm_pos13_d1(RegIndex.kreg_liquid_distribution_arm_pos13_d1), // 位置13 舵机1位置 + kreg_liquid_distribution_arm_pos14_d0(RegIndex.kreg_liquid_distribution_arm_pos14_d0), // 位置14 舵机0位置 + kreg_liquid_distribution_arm_pos14_d1(RegIndex.kreg_liquid_distribution_arm_pos14_d1), // 位置14 舵机1位置 + kreg_liquid_distribution_arm_pos15_d0(RegIndex.kreg_liquid_distribution_arm_pos15_d0), // 位置15 舵机0位置 + kreg_liquid_distribution_arm_pos15_d1(RegIndex.kreg_liquid_distribution_arm_pos15_d1), // 位置15 舵机1位置 + kreg_liquid_distribution_arm_pos16_d0(RegIndex.kreg_liquid_distribution_arm_pos16_d0), // 位置16 舵机0位置 + kreg_liquid_distribution_arm_pos16_d1(RegIndex.kreg_liquid_distribution_arm_pos16_d1); // 位置16 舵机1位置 + + public final RegIndex regIndex; + + LiquidArmRegIndex(RegIndex regIndex) { + this.regIndex = regIndex; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/Servo/MiniServoMId.java b/src/main/java/com/iflytop/sgs/hardware/type/Servo/MiniServoMId.java new file mode 100644 index 0000000..ab772d4 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/Servo/MiniServoMId.java @@ -0,0 +1,19 @@ +package com.iflytop.sgs.hardware.type.Servo; + +import com.iflytop.sgs.hardware.type.MId; + +public enum MiniServoMId { + NotSet(MId.NotSet),// + DUAL_ROBOT_AXIS1_MID(MId.DualRobotAxis1SV),// + DUAL_ROBOT_AXIS2_MID(MId.DualRobotAxis2SV), + CLAW_MID(MId.HBotClawSV) + ; + + final public MId mid; + + MiniServoMId(MId mid) { +// Assert.isTrue(this.name().equals(mid.name()),"MiniServoMid Init fail"); + this.mid = mid; + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/Servo/MiniServoRegIndex.java b/src/main/java/com/iflytop/sgs/hardware/type/Servo/MiniServoRegIndex.java new file mode 100644 index 0000000..db15da9 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/Servo/MiniServoRegIndex.java @@ -0,0 +1,71 @@ +package com.iflytop.sgs.hardware.type.Servo; + +import com.iflytop.sgs.hardware.type.RegIndex; + +public enum MiniServoRegIndex { + // kreg_module_version(RegIndex.kreg_module_version), // 模块版本 + // kreg_module_type(RegIndex.kreg_module_type), // 模块类型 + // kreg_module_status(RegIndex.kreg_module_status), // 0idle,1busy,2error + // kreg_module_errorcode(RegIndex.kreg_module_errorcode), // inited_flag + + kreg_mini_servo_pos(RegIndex.kreg_mini_servo_pos), // 位置 + kreg_mini_servo_limit_velocity(RegIndex.kreg_mini_servo_limit_velocity), // 限制速度 + kreg_mini_servo_limit_torque(RegIndex.kreg_mini_servo_limit_torque), // 限制扭矩 + kreg_mini_servo_protective_torque(RegIndex.kreg_mini_servo_protective_torque), // 保护扭矩 + kreg_mini_servo_is_move(RegIndex.kreg_mini_servo_is_move), // 是否在运动 + + + kreg_mini_servo_status(RegIndex.kreg_mini_servo_status), // 舵机状态 + kreg_mini_servo_voltage(RegIndex.kreg_mini_servo_voltage), // 电压 反馈当前舵机工作电压,反馈精度为0.1V,即120*0.1=12V + kreg_mini_servo_current(RegIndex.kreg_mini_servo_current), // 电流 反馈当前工作电流值,单位为ma + kreg_mini_servo_temperature(RegIndex.kreg_mini_servo_temperature), // 温度 反馈当前舵机内部工作温度,反馈精度为1摄氏度 + kreg_mini_servo_loadvalue(RegIndex.kreg_mini_servo_loadvalue), // 负载值 输出驱动电机的当前占空比电压,单位为0.1%,取值0-1000 + kreg_mini_servo_target_pos_tolerance(RegIndex.kreg_mini_servo_target_pos_tolerance), // 目标位置容忍度 + + + kreg_mini_servo_firmware_main_version(RegIndex.kreg_mini_servo_firmware_main_version), // 固件主版本号 + kreg_mini_servo_firmware_sub_version(RegIndex.kreg_mini_servo_firmware_sub_version), // 固件次版本号 + kreg_mini_servo_servo_main_version(RegIndex.kreg_mini_servo_servo_main_version), // 舵机主版本号 + kreg_mini_servo_servo_sub_version(RegIndex.kreg_mini_servo_servo_sub_version), // 舵机次版本号 + kreg_mini_servo_servo_min_angle(RegIndex.kreg_mini_servo_servo_min_angle), // 最小角度限制 + kreg_mini_servo_servo_max_angle(RegIndex.kreg_mini_servo_servo_max_angle), // 最大角度限制 + kreg_mini_servo_servo_max_temp(RegIndex.kreg_mini_servo_servo_max_temp), // 最高温度上限 + kreg_mini_servo_servo_max_voltage(RegIndex.kreg_mini_servo_servo_max_voltage), // 最高输入电压 + kreg_mini_servo_servo_min_voltage(RegIndex.kreg_mini_servo_servo_min_voltage), // 最低输入电压 + kreg_mini_servo_servo_max_torque(RegIndex.kreg_mini_servo_servo_max_torque), // 最大扭矩 + kreg_mini_servo_servo_unload_condition(RegIndex.kreg_mini_servo_servo_unload_condition), // 卸载条件 + kreg_mini_servo_servo_p(RegIndex.kreg_mini_servo_servo_p), // P 比例系 + kreg_mini_servo_servo_d(RegIndex.kreg_mini_servo_servo_d), // D 微分系 + kreg_mini_servo_servo_i(RegIndex.kreg_mini_servo_servo_i), // I + kreg_mini_servo_servo_min_start(RegIndex.kreg_mini_servo_servo_min_start), // 最小启动 + kreg_mini_servo_servo_cw_dead_zone(RegIndex.kreg_mini_servo_servo_cw_dead_zone), // 顺时针不灵敏区 + kreg_mini_servo_servo_ccw_dead_zone(RegIndex.kreg_mini_servo_servo_ccw_dead_zone), // 逆时针不灵敏 + kreg_mini_servo_servo_protect_current(RegIndex.kreg_mini_servo_servo_protect_current), // 保护电流 + kreg_mini_servo_servo_protect_torque(RegIndex.kreg_mini_servo_servo_protect_torque), // 保护扭矩 0->100 ,触发后,需要写入与组转方向相反的位置指令,进行解除 + kreg_mini_servo_servo_protect_time(RegIndex.kreg_mini_servo_servo_protect_time), // 保护时间 + kreg_mini_servo_servo_overload_torque(RegIndex.kreg_mini_servo_servo_overload_torque), // 过载扭矩 + kreg_mini_servo_servo_speed_p(RegIndex.kreg_mini_servo_servo_speed_p), // 速度闭环P比例参数 + kreg_mini_servo_servo_overload_time(RegIndex.kreg_mini_servo_servo_overload_time), // 过流保护时间 + kreg_mini_servo_servo_speed_i(RegIndex.kreg_mini_servo_servo_speed_i), // 速度闭环I积分参数 + kreg_mini_servo_servo_torque_switch(RegIndex.kreg_mini_servo_servo_torque_switch), // 扭矩开关 + kreg_mini_servo_servo_acc(RegIndex.kreg_mini_servo_servo_acc), // 加速度 + kreg_mini_servo_servo_target_pos(RegIndex.kreg_mini_servo_servo_target_pos), // 目标位置 + kreg_mini_servo_servo_run_time(RegIndex.kreg_mini_servo_servo_run_time), // 运行时间 + kreg_mini_servo_servo_run_speed(RegIndex.kreg_mini_servo_servo_run_speed), // 运行速度 + kreg_mini_servo_servo_torque_limit(RegIndex.kreg_mini_servo_servo_torque_limit), // 转矩限制 + kreg_mini_servo_servo_lock_flag(RegIndex.kreg_mini_servo_servo_lock_flag), // 锁标志 + kreg_mini_servo_servo_current_pos(RegIndex.kreg_mini_servo_servo_current_pos), // 当前位置 + kreg_mini_servo_servo_current_speed(RegIndex.kreg_mini_servo_servo_current_speed), // 当前速度 + kreg_mini_servo_servo_current_load(RegIndex.kreg_mini_servo_servo_current_load), // 当前负载 bit10为方向位 输出驱动电机的当前占空比电压,单位为0.1%,取值0-1000 + kreg_mini_servo_servo_current_voltage(RegIndex.kreg_mini_servo_servo_current_voltage), // 当前电压 反馈当前舵机工作电压,反馈精度为0.1V,即120*0.1=12V + kreg_mini_servo_servo_current_temp(RegIndex.kreg_mini_servo_servo_current_temp), // 当前温度 反馈当前舵机内部工作温度,反馈精度为1摄氏度 + kreg_mini_servo_servo_status(RegIndex.kreg_mini_servo_servo_status), // 舵机状态 + kreg_mini_servo_servo_move_flag(RegIndex.kreg_mini_servo_servo_move_flag), // 移动标志 + kreg_mini_servo_servo_current_current(RegIndex.kreg_mini_servo_servo_current_current), // 当前电流 反馈当前工作电流值,单位为6.26.5mA,最大可反馈电流为500*6.5mA=3250mA + ; + public final RegIndex regIndex; + + MiniServoRegIndex(RegIndex regIndex) { + this.regIndex = regIndex; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/DeviceStepMotorId.java b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/DeviceStepMotorId.java new file mode 100644 index 0000000..9a46e2a --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/DeviceStepMotorId.java @@ -0,0 +1,51 @@ +package com.iflytop.sgs.hardware.type.StepMotor; + + +public enum DeviceStepMotorId { + door(StepMotorMId.DOOR_MOTOR_MID, "门"), + shake_motor(StepMotorMId.SHAKE_MOTOR_MID, "摇匀电机"), + tray_motor(StepMotorMId.TRAY_MOTOR_MID, "托盘电机"), + gantry_x(StepMotorMId.HBOT_X_MOTOR_MID, "龙门架X轴"), + gantry_y(StepMotorMId.HBOT_Y_MOTOR_MID, "龙门架Y轴"), + gantry_z(StepMotorMId.HBOT_Z_MOTOR_MID, "龙门架Z轴"), + heater_motor_1(StepMotorMId.HEATER_1_MOTOR_MID, "加热位顶升电机1"), + heater_motor_2(StepMotorMId.HEATER_2_MOTOR_MID, "加热位顶升电机2"), + heater_motor_3(StepMotorMId.HEATER_3_MOTOR_MID, "加热位顶升电机3"), + heater_motor_4(StepMotorMId.HEATER_4_MOTOR_MID, "加热位顶升电机4"), + heater_motor_5(StepMotorMId.HEATER_5_MOTOR_MID, "加热位顶升电机5"), + heater_motor_6(StepMotorMId.HEATER_6_MOTOR_MID, "加热位顶升电机6"), + acid_pump_1(StepMotorMId.ACID_PUMP_1_MOTOR_MID, "加酸泵电机1"), + acid_pump_2(StepMotorMId.ACID_PUMP_2_MOTOR_MID, "加酸泵电机2"), + acid_pump_3(StepMotorMId.ACID_PUMP_3_MOTOR_MID, "加酸泵电机3"), + acid_pump_4(StepMotorMId.ACID_PUMP_4_MOTOR_MID, "加酸泵电机4"), + acid_pump_5(StepMotorMId.ACID_PUMP_5_MOTOR_MID, "加酸泵电机5"), + acid_pump_6(StepMotorMId.ACID_PUMP_6_MOTOR_MID, "加酸泵电机6"), + acid_pump_7(StepMotorMId.ACID_PUMP_7_MOTOR_MID, "加酸泵电机7"), + acid_pump_8(StepMotorMId.ACID_PUMP_8_MOTOR_MID, "加酸泵电机8"), + ; + + private StepMotorMId stepMotorMId; + private String description; + + public StepMotorMId getStepMotorMId() { + return stepMotorMId; + } + + public String getDescription() { + return description; + } + + DeviceStepMotorId(StepMotorMId stepMotorMId, String description) { + this.stepMotorMId = stepMotorMId; + this.description = description; + } + + public static DeviceStepMotorId getById(StepMotorMId stepMotorMId) { + for (DeviceStepMotorId deviceStepMotorId : DeviceStepMotorId.values()) { + if (deviceStepMotorId.getStepMotorMId() == stepMotorMId) { + return deviceStepMotorId; + } + } + return null; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorDirect.java b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorDirect.java new file mode 100644 index 0000000..17e189b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorDirect.java @@ -0,0 +1,17 @@ +package com.iflytop.sgs.hardware.type.StepMotor; + +public enum StepMotorDirect { + FORWARD(1), // 正转 + BACKWARD(0), // 反转 + ; + + private final int value; + + StepMotorDirect(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorMId.java b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorMId.java new file mode 100644 index 0000000..1de3de1 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorMId.java @@ -0,0 +1,32 @@ +package com.iflytop.sgs.hardware.type.StepMotor; + +import com.iflytop.sgs.hardware.type.MId; + +public enum StepMotorMId { + DOOR_MOTOR_MID(MId.DoorM), + SHAKE_MOTOR_MID(MId.ShakeM), + TRAY_MOTOR_MID(MId.CapStorageM), + HBOT_X_MOTOR_MID(MId.HBotXM), + HBOT_Y_MOTOR_MID(MId.HBotYM), + HBOT_Z_MOTOR_MID(MId.HBotZM), + HEATER_1_MOTOR_MID(MId.Heater1M), + HEATER_2_MOTOR_MID(MId.Heater2M), + HEATER_3_MOTOR_MID(MId.Heater3M), + HEATER_4_MOTOR_MID(MId.Heater4M), + HEATER_5_MOTOR_MID(MId.Heater5M), + HEATER_6_MOTOR_MID(MId.Heater6M), + ACID_PUMP_1_MOTOR_MID(MId.AcidPump1M), + ACID_PUMP_2_MOTOR_MID(MId.AcidPump2M), + ACID_PUMP_3_MOTOR_MID(MId.AcidPump3M), + ACID_PUMP_4_MOTOR_MID(MId.AcidPump4M), + ACID_PUMP_5_MOTOR_MID(MId.AcidPump5M), + ACID_PUMP_6_MOTOR_MID(MId.AcidPump6M), + ACID_PUMP_7_MOTOR_MID(MId.AcidPump7M), + ACID_PUMP_8_MOTOR_MID(MId.AcidPump8M), + ; + final public MId mid; + + StepMotorMId(MId mid) { + this.mid = mid; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorRegIndex.java b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorRegIndex.java new file mode 100644 index 0000000..2c5cbef --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorRegIndex.java @@ -0,0 +1,56 @@ +package com.iflytop.sgs.hardware.type.StepMotor; + +import com.iflytop.sgs.hardware.type.RegIndex; + +public enum StepMotorRegIndex { + // kreg_module_version(RegIndex.kreg_module_version), // 模块版本 + // kreg_module_type(RegIndex.kreg_module_type), // 模块类型 + // kreg_module_status(RegIndex.kreg_module_status), // 0idle,1busy,2error + // kreg_module_errorcode(RegIndex.kreg_module_errorcode), // inited_flag + /*********************************************************************************************************************** + * step_motor * + ***********************************************************************************************************************/ + kreg_step_motor_pos(RegIndex.kreg_step_motor_pos), // 机器人x坐标 + kreg_step_motor_is_enable(RegIndex.kreg_step_motor_is_enable), // 是否使能 + kreg_step_motor_dpos(RegIndex.kreg_step_motor_dpos), // 执行完上一条指令后的相对位移 + kreg_step_motor_has_move_zero(RegIndex.kreg_step_motor_has_move_zero), // 是否已经移动到零点 + kreg_step_motor_shaft(RegIndex.kreg_step_motor_shaft), // x轴是否反转 + kreg_step_motor_one_circle_pulse(RegIndex.kreg_step_motor_one_circle_pulse), // x轴一圈脉冲数 + kreg_step_motor_one_circle_pulse_denominator(RegIndex.kreg_step_motor_one_circle_pulse_denominator), // 设置一圈脉冲数的分母 + kreg_step_motor_default_velocity(RegIndex.kreg_step_motor_default_velocity), // 默认速度 + kreg_step_motor_low_velocity(RegIndex.kreg_step_motor_low_velocity), + kreg_step_motor_mid_velocity(RegIndex.kreg_step_motor_mid_velocity), + kreg_step_motor_high_velocity(RegIndex.kreg_step_motor_high_velocity), + kreg_step_motor_ihold(RegIndex.kreg_step_motor_ihold), // 步进电机电流配置 + kreg_step_motor_irun(RegIndex.kreg_step_motor_irun), // 步进电机电流配置 + kreg_step_motor_iholddelay(RegIndex.kreg_step_motor_iholddelay), // 步进电机电流配置 + kreg_step_motor_iglobalscaler(RegIndex.kreg_step_motor_iglobalscaler), // 步进电机电流配置 + kreg_step_motor_mres(RegIndex.kreg_step_motor_mres), // 步进电机电流配置 + + kreg_step_motor_run_to_zero_speed(RegIndex.kreg_step_motor_run_to_zero_speed), // 回零速度 + kreg_step_motor_look_zero_edge_speed(RegIndex.kreg_step_motor_look_zero_edge_speed), // 找零边缘速度 + kreg_step_motor_max_d(RegIndex.kreg_step_motor_max_d), // 最大限制距离 + kreg_step_motor_min_d(RegIndex.kreg_step_motor_min_d), // 最小限制距离 + kreg_step_motor_in_debug_mode(RegIndex.kreg_step_motor_in_debug_mode), // 驱动器处于调试模式 + kreg_step_motor_vstart(RegIndex.kreg_step_motor_vstart), // a1起作用的速度 + kreg_step_motor_a1(RegIndex.kreg_step_motor_a1), // + kreg_step_motor_amax(RegIndex.kreg_step_motor_amax), // + kreg_step_motor_v1(RegIndex.kreg_step_motor_v1), // + kreg_step_motor_dmax(RegIndex.kreg_step_motor_dmax), // + kreg_step_motor_d1(RegIndex.kreg_step_motor_d1), // + kreg_step_motor_vstop(RegIndex.kreg_step_motor_vstop), // + kreg_step_motor_tzerowait(RegIndex.kreg_step_motor_tzerowait), // + kreg_step_motor_enc_resolution(RegIndex.kreg_step_motor_enc_resolution), // 编码器分辨率 1000,1024,4000,4096,16384 + kreg_step_motor_enable_enc(RegIndex.kreg_step_motor_enable_enc), // + kreg_step_motor_dzero_pos(RegIndex.kreg_step_motor_dzero_pos), // 驱动器处于调试模式 + kret_step_motor_pos_devi_tolerance(RegIndex.kret_step_motor_pos_devi_tolerance), // 位置偏差容忍度 + kret_step_motor_io_trigger_append_distance(RegIndex.kret_step_motor_io_trigger_append_distance), // 移动到io时,附加的距离 + + + ; + public final RegIndex regIndex; + + StepMotorRegIndex(RegIndex regIndex) { + this.regIndex = regIndex; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorSpeedLevel.java b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorSpeedLevel.java new file mode 100644 index 0000000..28cce3b --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/StepMotor/StepMotorSpeedLevel.java @@ -0,0 +1,9 @@ +package com.iflytop.sgs.hardware.type.StepMotor; + +public enum StepMotorSpeedLevel { + DEFAULT, + LOW, + MID, + HIGH, + ; +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/appevent/A8kCanBusOnConnectEvent.java b/src/main/java/com/iflytop/sgs/hardware/type/appevent/A8kCanBusOnConnectEvent.java new file mode 100644 index 0000000..4524528 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/appevent/A8kCanBusOnConnectEvent.java @@ -0,0 +1,7 @@ +package com.iflytop.sgs.hardware.type.appevent; + +public class A8kCanBusOnConnectEvent extends AppEvent { + public A8kCanBusOnConnectEvent() { + super(A8kCanBusOnConnectEvent.class.getSimpleName()); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/appevent/AppEvent.java b/src/main/java/com/iflytop/sgs/hardware/type/appevent/AppEvent.java new file mode 100644 index 0000000..916a27f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/appevent/AppEvent.java @@ -0,0 +1,13 @@ +package com.iflytop.sgs.hardware.type.appevent; + +import java.util.Date; +import java.util.UUID; + +public class AppEvent { + public String typeName; + public Integer timestamp = (int) (new Date().getTime() / 1000); + public String eventId = UUID.randomUUID().toString(); + public AppEvent(String typeName) { + this.typeName = typeName; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/db/SubModuleRegInitialValue.java b/src/main/java/com/iflytop/sgs/hardware/type/db/SubModuleRegInitialValue.java new file mode 100644 index 0000000..23f2737 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/db/SubModuleRegInitialValue.java @@ -0,0 +1,23 @@ +package com.iflytop.sgs.hardware.type.db; + + +import com.iflytop.sgs.hardware.type.MId; +import com.iflytop.sgs.hardware.type.RegIndex; + +import java.io.Serializable; + +public class SubModuleRegInitialValue implements Serializable { + public int id = 0; + public MId mid; + public RegIndex regIndex; + public Integer regInitVal; + + public SubModuleRegInitialValue() { + } + + public SubModuleRegInitialValue(MId mid, RegIndex regIndex, Integer regInitVal) { + this.mid = mid; + this.regIndex = regIndex; + this.regInitVal = regInitVal; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/driver/HeaterRodSlavedId.java b/src/main/java/com/iflytop/sgs/hardware/type/driver/HeaterRodSlavedId.java new file mode 100644 index 0000000..0c6b563 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/driver/HeaterRodSlavedId.java @@ -0,0 +1,51 @@ +package com.iflytop.sgs.hardware.type.driver; + +import com.iflytop.sgs.common.enums.cmd.CmdDevice; +import com.iflytop.sgs.hardware.type.MId; + +public enum HeaterRodSlavedId { + HEATER_ROD1_ID(CmdDevice.heat_rod_1, 4, MId.IO1_ADC, 3), + HEATER_ROD2_ID(CmdDevice.heat_rod_2, 5, MId.IO1_ADC, 4), + HEATER_ROD3_ID(CmdDevice.heat_rod_3, 6, MId.IO1_ADC, 5), + HEATER_ROD4_ID(CmdDevice.heat_rod_4, 1, MId.IO1_ADC, 0), + HEATER_ROD5_ID(CmdDevice.heat_rod_5, 2, MId.IO1_ADC, 1), + HEATER_ROD6_ID(CmdDevice.heat_rod_6, 3, MId.IO1_ADC, 2), + ; + + private final CmdDevice cmdDevice; + private final Integer slaveId; + private final MId mid; + private final Integer adcCurrentIndex; // ADC电流索引 + + HeaterRodSlavedId(CmdDevice cmdDevice, Integer slaveId, MId mid, Integer adcCurrentIndex) { + this.cmdDevice = cmdDevice; + this.slaveId = slaveId; + this.mid = mid; + this.adcCurrentIndex = adcCurrentIndex; + } + + public CmdDevice getCmdDevice() { + return cmdDevice; + } + + public Integer getSlaveId() { + return slaveId; + } + + public MId getMid() { + return mid; + } + + public Integer getAdcCurrentIndex() { + return adcCurrentIndex; + } + + static public HeaterRodSlavedId getByCmdDevice(CmdDevice cmdDevice) { + for (HeaterRodSlavedId value : HeaterRodSlavedId.values()) { + if (value.cmdDevice == cmdDevice) { + return value; + } + } + return null; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/error/A8kEcode.java b/src/main/java/com/iflytop/sgs/hardware/type/error/A8kEcode.java new file mode 100644 index 0000000..b690487 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/error/A8kEcode.java @@ -0,0 +1,255 @@ +package com.iflytop.sgs.hardware.type.error; + +import lombok.extern.slf4j.Slf4j; + +/** + * 错误分类 + * 10, + * 为代码错误,代码没有BUG的情况下,这个错误时不会出现的,前端打印出错误,和错误携带的栈信息,方便产品维护时,非程序员能够提供详细的错误的信息。 + * + * + * + * + * + */ +@Slf4j +public enum A8kEcode { + + SUC(0), + // + // 代码错误,代码没有BUG的情况下,前端直接打印错误信息和栈信息即可 + // + CODEERROR(10), //代码错误 + ERROR_WORK_MODE(11), //工作模式错误 + ZAPP_INTERRUPT_EXCEPTION(12), + ERROR_OPERATION(13), //操作错误 + IP_FORMAT_ERROR(14), //IP地址格式错误 + SHUTDOWN_ERROR_DEVICE_IS_WORKING(15), //设备正在工作中,不允许关机 + LOGIN_OUT_ERROR_DEVICE_IS_WORKING(16), //设备正在工作中,不允许退出登录 + DEVICE_IS_BUSY(17), //设备正在工作中,不允许操作 + SYS_EXCEPTION(18), //系统异常 + DEVICE_NOT_INIT(19), //设备未初始化 + APPE_PAUSE_OPERATION_NOT_SUPPORT_IN_ENGINEER_TASK(20), // + APPE_CONTINUE_OPERATION_NOT_SUPPORT_IN_ENGINEER_TASK(21), // + + // + // 参数错误 + // + PE_PARAM_OUT_OF_RANGE(100),//参数超出范围 + // + // 用户错误 + // + USR_ALREADY_EXIST(110),//用户已存在 + USR_NOT_EXIT(111), //用户不存在 + USR_PASSWORD_ERROR(112),//用户密码错误 + USR_PASSWORD_IS_EMPTY(113),//用户密码为空 + USR_OLD_PASSWORD_ERROR(114),//用户旧密码错误 + + // + // ID卡错误 + // + APPE_A8K_ID_CARD_NOT_MOUNTED(120), //ID卡未挂载 + // + // 试管配置管理服务相关错误码 + // + APPE_TUBE_HOLDER_SETTING_IS_LOCKED(130),//试管架设置被锁定,已锁定的,说明当前后台正在使用这个配置,是不允许修改 + APPE_TUBEHOLDER_SETTING_ERROR(131), //试管架配置错误 + + // + // 急诊错误 + // + APPE_EMERGENCY_SAMPLE_IS_PROCESSING(141), //添加急诊样本失败,急诊样本还没有处理完成 (这个错误发生在前端,没有做好状态检测) + APPE_ADD_EMERGENCY_ACTION_IS_NOT_ALLOWED_WHEN_WORKING(142),//添加急诊样本失败,设备正在运行中 (这个错误发生在前端,没有做好状态检测) + // + // 设备操作操作 + // + APPE_DEVICE_IS_IN_FATAL_ERROR(150),//设备处于严重错误,请关机后重启设备进行恢复 + APPE_DEVICE_INIT_CHECK_FAIL(151),//设备初始化检查失败 + + APPE_DO_ACTION_FAIL_DEVICE_IS_WORKING(152),//设备正在工作中,不允许执行操作 + APPE_CONSUMABLES_IS_IN_USE_NOT_ALLOW_UNSTALL(153),//耗材正在使用中,不允许卸载 + + DEVICE_STARTUP_IN_PROGRESS(154), // 设备启动中,请稍后 + // + // 业务流程中的错误 + // 1. 点击开始运行后,设备在运行过程中出现的错误 + // 2. 下面错误,中文情况下,直接显示注释信息即可 + // + //扫描耗材错误 + APPE_NO_UNINSTALLED_CONSUMABLE(160),//没有未安装的耗材 + + + APPE_TIP_G1_SETTING_NUM_MISMATCH_ERROR(161),//Tip设置数量错误 + APPE_TIP_G2_SETTING_NUM_MISMATCH_ERROR(162),//Tip设置数量错误 + APPE_TIP_G3_SETTING_NUM_MISMATCH_ERROR(163),//Tip设置数量错误 + + APPE_INACTIVE_TUBEHOLDER_SETTING_EXISTS(164),//有试管架配置未激活 + + + //入料阶段错误 + APPE_SCAN_TUBEHOLDER_TYPE_TIMEOUT(200),//扫描试管架类型超时 + APPE_TUBE_HOLDER_TYPE_IS_NOT_SUPPORT(201),//试管架类型不支持 + APPE_INFEED_OVERTIME_FAIL(202),//入料超时失败 + + //样本处理过程中的错误 + APPE_PUT_TIP_FAIL(210),//丢弃Tip失败 + APPE_TAKE_TIP_FAIL(211),//取Tip失败 + APPE_DETECT_SAMPLE_FAIL(212),// 检测样本失败 + APPE_TAKE_LARGE_BUFFER_LIQUID_FAIL(213),//取大瓶缓冲液失败 + APPE_TAKE_SAMPLE_FAIL(214),//取大瓶缓冲液失败 + + + //出料阶段错误 + APPE_EJECT_TUBEHOLDER_TIMEOUT(220),//弹出试管架超时 + APPE_NO_TUBE_IN_HOLDER(221),//试管架中没有试管 + APPE_CONSUME_NOT_ENOUGH(222),//耗材不足 + APPE_TIP_NOT_ENOUGH(223),//tip不足 + + //运行中的公用错误 + APPE_A8K_PROJ_CARD_PARSE_ERROR(300), //ID卡解析错误 + APPE_A8K_PROJ_CARD_EXPIRYED(301), //ID卡过期 + APPE_A8K_PROJ_ID_IS_EMPTY(302), //项目ID为空 + PROJ_CARD_ERROR_WRONG_UNSUPPORTED(303), //项目不支持 + APPE_PULLERM_INIT_POS_ERROR(305), //初始化阶段,拉板电机没有处于零点位置 + APPE_PUSHERM_INIT_POS_ERROR(306), //初始化阶段,推板电机没有处于零点位置 + APPE_PLATE_STUCK_DETECTOR_SENSOR_TRIGGER(307), //卡板检测传感器触发 + APPE_TUBE_X_CHANNEL_IS_NOT_EMPTY(308), //试管架通道有异物 + APPE_PLATE_BOX_NOT_COVER(309), //板夹仓未盖 + APPE_DEVICE_NOT_INITED(310), //设备未初始化 + APPE_SHAKE_MODULE_CLAMPM_NOT_IN_ZERO_POS(311), //摇匀模组夹紧模块没有在零点位置 + APPE_INCUBATION_PLATE_OUTLET_STUCK_DETECTOR_SENSOR_IS_TRIGGER(312), //孵育板出料口卡板检测传感器触发 + APPE_INCUBATION_PLATE_INLET_STUCK_DETECTOR_SENSOR_IS_TRIGGER(313), //孵育板入料口卡板检测传感器触发 + + + // + // ID卡检验错误 + // + PROJ_CARD_ERROR_EXPIRYED(500),//ID卡验证错误,过期 + PROJ_CARD_ERROR_WRONG_FUNCTION_NUM(500),//ID卡验证错误,函数数量错误 + PROJ_CARD_ERROR_WRONG_OPT_NUM(500),//ID卡验证错误,光学数量错误 + PROJ_CARD_ERROR_WRONG_FUNCTION_X_TYPE(500),//ID卡验证错误,公式X类型错误 + PROJ_CARD_ERROR_WRONG_FUNCTION_JUDGE_X_TYPE(500),//ID卡验证错误,公式X类型错误 + PROJ_CARD_ERROR_WRONG_FUNCTION_L_X_TYPE(500),//ID卡验证错误,公式X类型错误 + PROJ_CARD_ERROR_WRONG_FUNCTION_H_X_TYPE(500),//ID卡验证错误,公式X类型错误 + PROJ_CARD_ERROR_WRONG_FUNCTION_X_SCOPE(500),//ID卡验证错误,公式X的范围错误 + PROJ_CARD_ERROR_WRONG_FUNCTION_LIMIT_SCOPE(500),//ID卡验证错误,光学数量错误 + PROJ_CARD_ERROR_WRONG_FUNCTION_TYPE(500),//ID卡验证错误,公式类型错误 + + PROJ_CARD_ERROR_WRONG_RESULT_UNIT_TYPE(500),//ID卡验证错误,Unit类型错误 + PROJ_CARD_ERROR_WRONG_RESULT_UNIT2_COEFFICIENT(500),//ID卡验证错误,Unit2转换系数错误 + PROJ_CARD_ERROR_WRONG_RESULT_UNIT3_COEFFICIENT(500),//ID卡验证错误,Unit3转换系数错误 + + // PROJ_CARD_ERROR_WRONG_UNSUPPORTED(520),//ID卡验证错误,不支持的ID卡 + PROJ_CARD_ERROR_BUILD_IN_PROJ_INFO_ERROR(521),//ID卡验证错误,内置项目信息错误 + PROJ_CARD_ERROR_ID_CARD_VERSION_IS_LOWER_THAN_STORED(522),//ID卡验证错误,ID卡版本低于存储的版本 + + + // + // LowBoard 底层错误,直接打印错误英文 + // + LOW_ERROR_HARDWARE_ERROR_START(1000), + LOW_ERROR_BOARD_COMMON_ERROR(1001), + LOW_ERROR_PARAM_OUT_OF_RANGE(1102), + LOW_ERROR_CMD_NOT_SUPPORT(1103), + LOW_ERROR_DEVICE_IS_BUSY(1104), + LOW_ERROR_DEVICE_IS_OFFLINE(1105), + LOW_ERROR_OVERTIME(1106), + LOW_ERROR_NOACK(1107), + LOW_ERROR_ERRORACK(1108), + LOW_ERROR_DEVICE_OFFLINE(1109), + LOW_ERROR_SUBDEVICE_OVERTIME(1111), + LOW_ERROR_BUFFER_NOT_ENOUGH(1112), + LOW_ERROR_CMD_PARAM_NUM_ERROR(1114), + LOW_ERROR_CHECKCODE_IS_ERROR(1115), + LOW_ERROR_ILLEGAL_OPERATION(1116), + LOW_ERROR_ACTION_OVERTIME(1117), + LOW_ERROR_MODULE_OPEATION_BREAK_BY_USER(1202), + LOW_ERROR_MODULE_NOT_FIND_REG(1207), + LOW_ERROR_XYMOTOR_X_FIND_ZERO_EDGE_FAIL(1306), + LOW_ERROR_XYMOTOR_Y_FIND_ZERO_EDGE_FAIL(1307), + LOW_ERROR_XYMOTOR_NOT_ENABLE(1308), + LOW_ERROR_XYMOTOR_TARGET_POS_OUTOF_RANGE(1309), + LOW_ERROR_XYMOTOR_NOT_MOVE_TO_ZERO(1310), + LOW_ERROR_PIPETTE_ERROR_NO_ERROR(1400), + LOW_ERROR_PIPETTE_ERROR_INIT_FAIL(1401), + LOW_ERROR_PIPETTE_ERROR_INVALID_CMD(1402), + LOW_ERROR_PIPETTE_ERROR_INVALID_ARG(1403), + LOW_ERROR_PIPETTE_ERROR_PRESSURE_SENSOR_ERROR(1404), + LOW_ERROR_PIPETTE_ERROR_OVER_PRESSURE(1405), + LOW_ERROR_PIPETTE_ERROR_LLD_ERROR(1406), //一般为lld时移液枪吸液过快,导致很快到达最大行程 + LOW_ERROR_PIPETTE_ERROR_DEVICE_NOT_INIT(1407), + LOW_ERROR_PIPETTE_ERROR_TIP_POP_ERROR(1408), + LOW_ERROR_PIPETTE_ERROR_PUMP_OVERLOAD(1409), + LOW_ERROR_PIPETTE_ERROR_TIP_DROP(1410), + LOW_ERROR_PIPETTE_ERROR_CAN_BUS_ERROR(1411), + LOW_ERROR_PIPETTE_ERROR_INVALID_CHECKSUM(1412), + LOW_ERROR_PIPETTE_ERROR_EEPROM_ERROR(1413), + LOW_ERROR_PIPETTE_ERROR_CMD_BUFFER_EMPTY(1414), + LOW_ERROR_PIPETTE_ERROR_CMD_BUFFER_OVERFLOW(1415), + LOW_ERROR_PIPETTE_ERROR_TIP_BLOCK(1416), + LOW_ERROR_PIPETTE_ERROR_AIR_SUCTION(1417), + LOW_ERROR_PIPETTE_ERROR_BUBBLE(1418), + LOW_ERROR_PIPETTE_ERROR_VOLUME_ERROR(1419), + LOW_ERROR_PIPETTE_ERROR_TIP_ALREADY_LOAD(1420), + LOW_ERROR_PIPETTE_ERROR_TIP_LOAD_FAIL(1421), + LOW_ERROR_PIPETTE_ERROR_NO_TIP_WHEN_LLD(1422), + LOW_ERROR_PIPETTE_ERROR_UNINITED(1501), + LOW_ERROR_PIPETTE_ERROR_NOT_LLD_PREPARE(1502), + LOW_ERROR_PIPETTE_ERROR_TIPISLOAD_WHEN_LLD_PREPARE(1500), + LOW_ERROR_PIPETTE_ERROR_PUMP_LOAD_VAL_IS_NOT_EMPTY(1503), + + LOW_ERROR_STEP_MOTOR_NOT_FOUND_ZERO_POINT(1600), + LOW_ERROR_STEP_MOTOR_NOT_GO_ZERO(1601), + LOW_ERROR_STEP_MOTOR_OVER_TEMPERATURE(1602), + LOW_ERROR_STEP_MOTOR_OVER_VOLTAGE(1603), + LOW_ERROR_STEP_MOTOR_RUN_OVERTIME(1604), + LOW_ERROR_STEP_MOTOR_NOT_ENABLE(1605), + LOW_ERROR_STEP_MOTOR_IOINDEX_OUT_OF_RANGE(1606), + LOW_ERROR_STEP_MOTOR_SUBIC_RESET(1607), + LOW_ERROR_STEP_MOTOR_DRV_ERR(1608), + LOW_ERROR_STEP_MOTOR_UV_CP(1609), + LOW_ERROR_STEP_MOTOR_NOT_FOUND_POINT_EDGE(1610), + LOW_ERROR_STEP_MOTOR_LOST_STEP(1611), + LOW_ERROR_STEP_MOTOR_NOT_MOVE_TO_ZERO(1612), + LOW_ERROR_STEP_MOTOR_OT(1613), + LOW_ERROR_STEP_MOTOR_OTPW(1614), + LOW_ERROR_STEP_MOTOR_S2GA(1615), + LOW_ERROR_STEP_MOTOR_S2GB(1616), + LOW_ERROR_STEP_MOTOR_OLA(1617), + LOW_ERROR_STEP_MOTOR_OLB(1618), + LOW_ERROR_MINI_SERVO_NOT_ENABLE(1700), + LOW_ERROR_MINI_SERVO_MODE_NOT_SUPPORT(1701), + LOW_ERROR_FAN_HARDWARE_FAULT(1800), + LOW_ERROR_WATER_COOLING_FAN_ERROR(1900), + LOW_ERROR_WATER_COOLING_TEMPERATURE_SENSOR_ERROR(1902), + LOW_ERROR_WATER_COOLING_PUMP_IS_ERROR(1903), + LOW_ERROR_WATER_COOLING_PELTER_IS_ERROR(1904), + // + // 底层扩展错误码,由java定义 + // + LOW_EXT_ERROR_UNKOWN_INDEX_ERROR(5000), //代码错误,未知错误 + LOW_EXT_ERROR_CMD_NOT_SUPPORT(5001), //代码错误,未知错误 + LOW_EXT_ERROR_MOTOR_AT_WRONG_POS(5002),//电机在错误的位置 + ; + + public final int index; + public int rawindex = 0; + + A8kEcode(int index) { + this.index = index; + } + + + static public A8kEcode fromInt(int index) { + for (var e : A8kEcode.values()) { + if (e.index == index) { + return e; + } + } + log.error("未知错误码:{}", index); + A8kEcode.LOW_EXT_ERROR_UNKOWN_INDEX_ERROR.rawindex = index; + return A8kEcode.LOW_EXT_ERROR_UNKOWN_INDEX_ERROR; + } + + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/error/AECodeError.java b/src/main/java/com/iflytop/sgs/hardware/type/error/AECodeError.java new file mode 100644 index 0000000..9937096 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/error/AECodeError.java @@ -0,0 +1,37 @@ +package com.iflytop.sgs.hardware.type.error; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class AECodeError extends AppError { + @JsonIgnore + public StackTraceElement[] stackTraceElements; + + public AECodeError(String exmsg) { + super(A8kEcode.CODEERROR); + this.exmsg = exmsg; + stackTraceElements = Thread.currentThread().getStackTrace(); + } + + public AECodeError(String fmt, Object... args) { + super(A8kEcode.CODEERROR); + this.exmsg = String.format(fmt, args); + stackTraceElements = Thread.currentThread().getStackTrace(); + } + + public AECodeError(Exception e) { + super(A8kEcode.CODEERROR); + this.exmsg = e.getMessage(); + stackTraceElements = e.getStackTrace(); + } + + + public static void main(String[] args) { + try { + throw new Exception("test"); + } catch (Exception e) { + AECodeError aeCodeError = new AECodeError(e); +// System.out.println(ZJsonHelper.objToPrettyJson(aeCodeError)); + } + + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/error/AEHardwareError.java b/src/main/java/com/iflytop/sgs/hardware/type/error/AEHardwareError.java new file mode 100644 index 0000000..7359b75 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/error/AEHardwareError.java @@ -0,0 +1,29 @@ +package com.iflytop.sgs.hardware.type.error; + +import com.iflytop.sgs.hardware.type.CmdId; +import com.iflytop.sgs.hardware.type.MId; +import io.swagger.v3.oas.annotations.media.Schema; + +public class AEHardwareError extends AppError { + @Schema(description = "模块id(辅助调试,直接原始显示就行)") + public MId mid; + @Schema(description = "命令id(辅助调试,直接原始显示就行)") + public CmdId cmdId; + + public String rawtxcmd; + public String rawrxcmd; + + public AEHardwareError(A8kEcode errorCode, MId mid, CmdId cmdId, String rawtxcmd, String rawrxcmd) { + super(errorCode); + this.mid = mid; + this.cmdId = cmdId; + this.rawtxcmd = rawtxcmd; + this.rawrxcmd = rawrxcmd; + } + + public AEHardwareError(A8kEcode errorCode, MId mid, CmdId cmdId) { + super(errorCode); + this.mid = mid; + this.cmdId = cmdId; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/type/error/AppError.java b/src/main/java/com/iflytop/sgs/hardware/type/error/AppError.java new file mode 100644 index 0000000..b9ff737 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/error/AppError.java @@ -0,0 +1,38 @@ +package com.iflytop.sgs.hardware.type.error; + +import cn.hutool.json.JSONUtil; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; + +public class AppError implements Serializable { + @Schema(description = "错误码") + public A8kEcode code; + public String type; + @Schema(description = "额外信息") + public String exmsg; + + public AppError(A8kEcode errorCode) { + this.code = errorCode; + this.type = this.getClass().getSimpleName(); + } + + public AppError(A8kEcode errorCode, String exmsg, Object... args) { + this.code = errorCode; + this.type = this.getClass().getSimpleName(); + this.exmsg = String.format(exmsg, args); + } + + public String toString() { + return JSONUtil.toJsonStr(this); + } + + public Boolean eq(A8kEcode... ecode) { + for (A8kEcode e : ecode) { + if (e == code) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/hardware/type/error/AppErrorCode.java b/src/main/java/com/iflytop/sgs/hardware/type/error/AppErrorCode.java new file mode 100644 index 0000000..efdd4d6 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/type/error/AppErrorCode.java @@ -0,0 +1,21 @@ +package com.iflytop.sgs.hardware.type.error; + +public enum AppErrorCode { + APP_OK(200, "操作成功"), + FAIL(500, "操作失败"), + PARAM_ERROR(400, "参数错误"); + + private int code; + private String msg; + private AppErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public int getCode() { + return code; + } + public String getMsg() { + return msg; + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/hardware/utils/CommonPage.java b/src/main/java/com/iflytop/sgs/hardware/utils/CommonPage.java new file mode 100644 index 0000000..04d12c4 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/utils/CommonPage.java @@ -0,0 +1,25 @@ +package com.iflytop.sgs.hardware.utils; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class CommonPage { + + private Integer pageNum; //第几页 + private Integer pageSize; //一页展示几条数据 + private Integer totalPage; //总页数 + private long total; //总条数 + private List list; //数据 + + public CommonPage(Integer pageNum, Integer pageSize, Integer totalPage, long total, List list) { + this.pageNum = pageNum; + this.pageSize = pageSize; + this.totalPage = totalPage; + this.total = total; + this.list = list; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/utils/Math/ServoPositionConverter.java b/src/main/java/com/iflytop/sgs/hardware/utils/Math/ServoPositionConverter.java new file mode 100644 index 0000000..0f3410f --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/utils/Math/ServoPositionConverter.java @@ -0,0 +1,84 @@ +package com.iflytop.sgs.hardware.utils.Math; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ServoPositionConverter { + // 舵机位置相关常量 + private static final double MIN_SERVO_POSITION = 1920; + private static final double MAX_SERVO_POSITION = 3000; + + // 4096量程相关常量 + private static final double MIN_SERVO_4096_POSITION = 0.0; + private static final double MAX_SERVO_4096_POSITION = 4096.0; + + // 3600量程相关常量 + private static final double MIN_SERVO_3600_POSITION = 0.0; + private static final double MAX_SERVO_3600_POSITION = 3600.0; + + /** + * 将0-4096范围内的值映射到1920-3000的舵机位置,再映射到0-3600的目标量程 + * @param value 输入值,范围0-4096 + * @return 对应的3600量程值 + */ + public static double convert4096To3600(double value) { + // 限制输入值在有效范围内 + value = Math.max(MIN_SERVO_4096_POSITION, Math.min(MAX_SERVO_4096_POSITION, value)); + + // 第一步:将0-4096的输入值映射到1920-3000的舵机位置 + double servoPosition = MIN_SERVO_POSITION + + (value - MIN_SERVO_4096_POSITION) * + (MAX_SERVO_POSITION - MIN_SERVO_POSITION) / + (MAX_SERVO_4096_POSITION - MIN_SERVO_4096_POSITION); + + // 第二步:将1920-3000的舵机位置映射到0-3600的目标量程 + double result = MIN_SERVO_3600_POSITION + + (servoPosition - MIN_SERVO_POSITION) * + (MAX_SERVO_3600_POSITION - MIN_SERVO_3600_POSITION) / + (MAX_SERVO_POSITION - MIN_SERVO_POSITION); + + return result; + } + + /** + * 将0-3600范围内的值反向映射回0-4096的原始量程 + * @param value 输入值,范围0-3600 + * @return 对应的4096量程值 + */ + public static double convert3600To4096(double value) { + // 限制输入值在有效范围内 + value = Math.max(MIN_SERVO_3600_POSITION, Math.min(MAX_SERVO_3600_POSITION, value)); + + // 第一步:将0-3600的值反向映射到1920-3000的舵机位置 + double servoPosition = MIN_SERVO_POSITION + + (value - MIN_SERVO_3600_POSITION) * + (MAX_SERVO_POSITION - MIN_SERVO_POSITION) / + (MAX_SERVO_3600_POSITION - MIN_SERVO_3600_POSITION); + + // 第二步:将1920-3000的舵机位置反向映射到0-4096的原始量程 + double result = MIN_SERVO_4096_POSITION + + (servoPosition - MIN_SERVO_POSITION) * + (MAX_SERVO_4096_POSITION - MIN_SERVO_4096_POSITION) / + (MAX_SERVO_POSITION - MIN_SERVO_POSITION); + + return result; + } + + /** + * 测试转换功能的示例方法 + */ + public static void main(String[] args) { + // 测试从4096到3600的转换 + double value4096 = 2048; // 中间值 + double value3600 = convert4096To3600(value4096); + System.out.printf("4096量程的 %.2f 对应3600量程的 %.2f%n", value4096, value3600); + + // 测试反向转换 + double reversedValue = convert3600To4096(value3600); + System.out.printf("3600量程的 %.2f 反向映射回4096量程的 %.2f%n", value3600, reversedValue); + + // 测试边界值 + System.out.printf("4096量程的 0 对应3600量程的 %.2f%n", convert4096To3600(0)); + System.out.printf("4096量程的 4096 对应3600量程的 %.2f%n", convert4096To3600(4096)); + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/hardware/utils/Math/StepMotorConverter.java b/src/main/java/com/iflytop/sgs/hardware/utils/Math/StepMotorConverter.java new file mode 100644 index 0000000..e7ef363 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/utils/Math/StepMotorConverter.java @@ -0,0 +1,81 @@ +package com.iflytop.sgs.hardware.utils.Math; + +/** + * @author iflytop + * @date 2023/10/12 + * @description 步进电机助手类 + * lead[S](导程): mm + * rps(转速): r/s + * rpm(转速): r/min + * r(转数): r + * + */ +public class StepMotorConverter { + static private final double kScale = 100.0; + + /* **** **** **** **** **** **** **** **** **** **** **** + * 位置转换 + * 1. 用户位置 X + * 2. 用户单位距离 unit_X + * 3. R = X * kScale / motor_scale * motor_lead + * 4. unit_X(1mm/1ml) = R / S = X * kScale / motor_scale * motor_lead / S + * 5. kScale == 10000.0 && motor_lead == 1 + * 6. unit_X(1mm/1ml) = X * kScale / motor_scale / S (X=1, 即 X = unit_X, 保证 kScale / motor_scale / S == 1) + * 7. K = S / motor_scale + * + **** **** **** **** **** **** **** **** **** **** **** */ + + /** + * 用户设置位置转换为控制器需要的位置 + * @param position + * @return + */ + public static int toMotorPosition(double position) + { + return (int)(position * kScale); + } + + /** + * 控制器位置转换为用户设置位置 + * @param motorPosition + * @return + */ + public static double toUserPosition(int motorPosition) + { + return (double)motorPosition / kScale; + } + + /* **** **** **** **** **** **** **** **** **** **** **** + * 速度转换 + * 1. 用户速度 X/s + * 2. 电机速度 R/min + * 3. X/s = rps * S + * 4. rps = X/s / S + * 5. rps = rpm / 60.0 + * 6. rpm = X/s * 60.0 + * 7. rpm = X/s / S * 60.0 + * + **** **** **** **** **** **** **** **** **** **** **** */ + + /** + * 用户设置速度转换为控制器需要的速度 + * 用户速度 U_Speed xx/s + * 控制器速度 rpm + * S 导程 + */ + public static int toMotorSpeed(double speed, double S) + { + return (int)(speed * 60.0 / S); + } + + /** + * 控制器速度转换为用户设置速度 + * 控制器速度 rpm + * 用户速度 U_Speed xx/s + * S 导程 + */ + public static double toUserSpeed(int motorSpeed, double S) + { + return (double)motorSpeed * S / 60.0; + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/utils/OS.java b/src/main/java/com/iflytop/sgs/hardware/utils/OS.java new file mode 100644 index 0000000..ca24e36 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/utils/OS.java @@ -0,0 +1,24 @@ +package com.iflytop.sgs.hardware.utils; + +public class OS { + + public static void hsleep(int ms) { + forceSleep(ms); + } + + public static void forceSleep(Integer mills) { + Long start = System.currentTimeMillis(); + long end = start + mills; + while (System.currentTimeMillis() < end) { + int left = (int) (end - System.currentTimeMillis()); + threadSleep(left); + } + } + + public static void threadSleep(Integer mills) { + try { + Thread.sleep(mills); + } catch (InterruptedException ignored) { + } + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/utils/ZCSVUtils.java b/src/main/java/com/iflytop/sgs/hardware/utils/ZCSVUtils.java new file mode 100644 index 0000000..67d37fa --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/utils/ZCSVUtils.java @@ -0,0 +1,78 @@ +package com.iflytop.sgs.hardware.utils; + + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.text.csv.CsvReader; +import cn.hutool.core.text.csv.CsvUtil; +import cn.hutool.core.text.csv.CsvWriter; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public class ZCSVUtils { + + + public static void writeCSV(String filePath, Class type, List objects) { + File file = new File(filePath); + try ( + CsvWriter csvWriter = CsvUtil.getWriter(file.getAbsoluteFile(), StandardCharsets.UTF_8); + ) { + + List headers = new ArrayList<>(); + var fields = type.getDeclaredFields(); + for (var field : fields) { + headers.add(field.getName()); + } + + String[] headersArray = new String[headers.size()]; + headers.toArray(headersArray); + + + csvWriter.write(headersArray); + + for (var object : objects) { + String[] row = new String[fields.length]; + for (int i = 0; i < fields.length; i++) { + fields[i].setAccessible(true); + row[i] = String.format("%s", fields[i].get(object)); + } + + + // log.info("row: {}", row); + csvWriter.write(row); + } + csvWriter.flush(); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static List readCSV(String filePath, Class clazz) { + File file = new File(filePath); + final CsvReader reader = CsvUtil.getReader(); + String csvContent = ResourceUtil.readUtf8Str(file.getAbsolutePath()); + return reader.read(csvContent, clazz); + } + + public static List readCSVContent(String csvContent, Class clazz) { + final CsvReader reader = CsvUtil.getReader(); + return reader.read(csvContent, clazz); + } + + public static List readCSVFromResource(String filePath, Class clazz) { + String csvContent = ResourceUtil.readUtf8Str(filePath); + final CsvReader reader = CsvUtil.getReader(); + return reader.read(csvContent, clazz); + } + + public static Boolean isResourceExist(String filePath) { + var resource = ResourceUtil.getResource(filePath); + return resource != null; + } + + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/utils/ZList.java b/src/main/java/com/iflytop/sgs/hardware/utils/ZList.java new file mode 100644 index 0000000..ce73bb9 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/utils/ZList.java @@ -0,0 +1,11 @@ +package com.iflytop.sgs.hardware.utils; + +import java.util.ArrayList; +import java.util.Arrays; + +public class ZList { + //List.of 方法不支持 removeIf, 代码中尽量全部使用 ZList.of + public static ArrayList of(T... args) { + return new ArrayList<>(Arrays.asList(args)); + } +} diff --git a/src/main/java/com/iflytop/sgs/hardware/utils/ZSqlite.java b/src/main/java/com/iflytop/sgs/hardware/utils/ZSqlite.java new file mode 100644 index 0000000..57b9fee --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/utils/ZSqlite.java @@ -0,0 +1,209 @@ +package com.iflytop.sgs.hardware.utils; + +import com.iflytop.sgs.hardware.constants.FilePathConstant; +import jakarta.annotation.Nullable; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.sql.ResultSet; +import java.util.List; + +public class ZSqlite { + private static final Logger log = LoggerFactory.getLogger(ZSqlite.class); + JdbcTemplate jdbcTemplate; + Class tClass; + public String tableName; + + public ZSqlite() { + } + + public void init(JdbcTemplate jdbcTemplate, String tableName, Class tClass, boolean foreceCreate) { + this.tableName = tableName; + this.tClass = tClass; + this.jdbcTemplate = jdbcTemplate; + if (foreceCreate) { + this.forceDeleteTable(); + } + + if (!this.isTableExist()) { + this.createTable(); + tryInitDBData(); + } + } + + public void init(JdbcTemplate jdbcTemplate, String tableName, Class tClass) { + init(jdbcTemplate, tableName, tClass, false); + } + + + public List getAll() { + return jdbcTemplate.query("select * from " + tableName, this::rowMapperList); + } + + public List getAllDesc() { + return jdbcTemplate.query("select * from " + tableName + " order by id desc", this::rowMapperList); + } + + public CommonPage getPageDesc(Integer pageNum, Integer pageSize) { + //倒叙 + String sql = "select * from " + tableName + " order by id desc"; + return queryPage(pageNum, pageSize, sql); + } + + + public CommonPage queryPage(Integer pageNum, Integer pageSize, String sql, @Nullable Object... args) { + sql = sql + " limit " + (pageNum - 1) * pageSize + "," + pageSize; + List list = jdbcTemplate.query(sql, this::rowMapperList, args); + Integer total = jdbcTemplate.queryForObject("select count(*) from " + tableName, Integer.class); + if (total == null) { + total = 0; + } + return new CommonPage<>(pageNum, pageSize, total / pageSize + 1, total, list); + } + + public List getPage(Integer pageNum, Integer pageSize) { + //倒叙 + String sql = "select * from " + tableName + " limit " + (pageNum - 1) * pageSize + "," + pageSize; + List list = jdbcTemplate.query(sql, this::rowMapperList); + //总数 + Integer total = jdbcTemplate.queryForObject("select count(*) from " + tableName, Integer.class); + if (total == null) { + total = 0; + } + return list; + } + + public T findById(int id) { + List list = jdbcTemplate.query("select * from " + tableName + " where id = ?", this::rowMapperList, id); + if (list.isEmpty()) { + return null; + } + return list.get(0); + } + + + @SneakyThrows public T rowMapperList(ResultSet rs, int rowNum) { + var ret = ZSqliteJdbcHelper.rowMapper(rs, tClass); + return tClass.cast(ret); + } + + // @SneakyThrows public T rowMapperOne(ResultSet rs) { + // // return (T) ZSqliteJdbcHelper.rowMapper(rs, tClass); + // return tClass.cast(ZSqliteJdbcHelper.rowMapper(rs, tClass)); + // } + + public Boolean isTableExist() { + return ZSqliteJdbcHelper.isTableExist(jdbcTemplate, tableName); + } + + public void deleteTable() { + ZSqliteJdbcHelper.deleteTable(jdbcTemplate, tableName); + } + + public void forceDeleteTable() { + ZSqliteJdbcHelper.forceDeleteTable(jdbcTemplate, tableName); + } + + public void createTable() { + ZSqliteJdbcHelper.createTable(jdbcTemplate, tableName, tClass); + } + + public void delete(int id) { + ZSqliteJdbcHelper.delete(jdbcTemplate, tableName, id); + } + + public void add(T obj) { + if (obj == null) { + return; + } + ZSqliteJdbcHelper.addObj(jdbcTemplate, tableName, tClass, obj); + } + + public void addAll(List list) { + for (var val : list) { + add(val); + } + } + + public void update(T obj) { + ZSqliteJdbcHelper.updateObj(jdbcTemplate, tableName, tClass, obj); + } + + public void deleteAll() { + ZSqliteJdbcHelper.deleteTable(jdbcTemplate, tableName); + ZSqliteJdbcHelper.createTable(jdbcTemplate, tableName, tClass); + } + + public T queryOne(String sql, @Nullable Object... args) { + List list = jdbcTemplate.query(sql, this::rowMapperList, args); + if (list.isEmpty()) { + return null; + } + return list.get(0); + } + + public List queryList(String sql, @Nullable Object... args) { + return jdbcTemplate.query(sql, this::rowMapperList, args); + } + + public void update(String sql, @Nullable Object... args) { + jdbcTemplate.update(sql, args); + } + + + public void export() { + ZCSVUtils.writeCSV(getExportPATH(), tClass, getAll()); + } + + /** + * 从CSV文件导入数据到数据库 !WARNING: 会删除原有数据 + * @param csvcontent CSV文件内容 + */ + public void importFromCSV(String csvcontent, boolean forceDelete) { + if (forceDelete) { + deleteAll(); + List list = ZCSVUtils.readCSV(csvcontent, tClass); + list.forEach(this::add); + } else { + List list = ZCSVUtils.readCSV(csvcontent, tClass); + for (var val : list) { + var obj = tClass.cast(val); + try { + var id = tClass.getField("id"); + id.setAccessible(true); + int idVal = (int) id.get(obj); + + if (idVal == 0) { + add(obj); + } else { + update(obj); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + } + + + public String getExportPATH() { + return String.format("%s/%s.csv", FilePathConstant.FILE_DB_EXPORT_PATH, tableName); + } + + + public void resetDBData() { + deleteAll(); + tryInitDBData(); + } + + private void tryInitDBData() { + String csvPath = String.format("db/%s.csv", tableName); + if (ZCSVUtils.isResourceExist(csvPath)) { + var list = ZCSVUtils.readCSVFromResource(String.format("db/%s.csv", tableName), tClass); + addAll(list); + } + } + +} diff --git a/src/main/java/com/iflytop/sgs/hardware/utils/ZSqliteJdbcHelper.java b/src/main/java/com/iflytop/sgs/hardware/utils/ZSqliteJdbcHelper.java new file mode 100644 index 0000000..32322e1 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/hardware/utils/ZSqliteJdbcHelper.java @@ -0,0 +1,284 @@ +package com.iflytop.sgs.hardware.utils; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.slf4j.Logger; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class ZSqliteJdbcHelper { + static Logger logger = org.slf4j.LoggerFactory.getLogger(ZSqliteJdbcHelper.class); + + static public boolean isTableExist(JdbcTemplate jdbcTemplate, String tableName) { + String sql = "select * from sqlite_master where type = 'table' and name = '" + tableName + "';"; + List result = jdbcTemplate.query(sql, (ResultSet rs, int rowNum) -> { + return rs.toString(); + }); + return !result.isEmpty(); + } + + static public void deleteTable(JdbcTemplate jdbcTemplate, String tableName) { + String sql = "drop table " + tableName + ";"; + jdbcTemplate.execute(sql); + } + + static public void forceDeleteTable(JdbcTemplate jdbcTemplate, String tableName) { + if (isTableExist(jdbcTemplate, tableName)) { + deleteTable(jdbcTemplate, tableName); + } + } + + static public void createTable(JdbcTemplate jdbcTemplate, String tableName, Class tClass) { + StringBuilder sql = new StringBuilder("create table " + tableName + " ("); + Boolean hasId = false; + for (java.lang.reflect.Field field : tClass.getDeclaredFields()) { + if (field.getName().equals("id")) { + hasId = true; + } + sql.append(" '").append(field.getName()).append("' "); + if (field.getType().equals(Integer.class) || field.getType().equals(int.class)) { + sql.append("integer,"); + } else if (field.getType().equals(Double.class) || field.getType().equals(Float.class)) { + sql.append("real,"); + } else if (field.getType().equals(Boolean.class)) { + sql.append("integer,"); + } else if (field.getType().equals(String.class)) { + sql.append("text,"); + } else if (field.getType().equals(Date.class)) { + sql.append("text,"); + } else if (field.getType().isEnum()) { + sql.append("text,"); + } else { + sql.append("text,"); + } + } + if (!hasId) { + throw new RuntimeException("id field not found in class " + tClass.getName()); + } + + sql.append(" PRIMARY KEY ('id' DESC));"); + jdbcTemplate.execute(sql.toString()); + } + + //从数据库中读取数据 + static public Object rowMapper(ResultSet rs, Class tClass) throws SQLException, InstantiationException, IllegalAccessException { + Object obj = tClass.newInstance(); + for (java.lang.reflect.Field field : tClass.getDeclaredFields()) { + + if (!field.getName().equals("id") && rs.getObject(field.getName()) == null) { + field.set(obj, null); + continue; + } + + if (field.getType().equals(Integer.class)) { + + field.set(obj, rs.getInt(field.getName())); + } else if (field.getType().equals(int.class)) { + field.set(obj, rs.getInt(field.getName())); + } else if (field.getType().equals(Float.class)) { + field.set(obj, rs.getFloat(field.getName())); + } else if (field.getType().equals(Double.class)) { + field.set(obj, rs.getDouble(field.getName())); + } else if (field.getType().equals(Boolean.class)) { + field.set(obj, rs.getBoolean(field.getName())); + } else if (field.getType().equals(String.class)) { + field.set(obj, rs.getString(field.getName())); + } else if (field.getType().equals(Date.class)) { + if (rs.getString(field.getName()) == null || rs.getString(field.getName()).isEmpty()) + field.set(obj, null); + else + field.set(obj, new Date(rs.getLong(field.getName()))); + } else if (field.getType().equals(ObjectNode.class)) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + String val = rs.getString(field.getName()); + if (val != null && !val.isEmpty()) { + // logger.info("val: {}", val); + field.set(obj, objectMapper.readTree(val)); + } else { + field.set(obj, null); + } + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } else if (field.getType().isEnum()) { + Method methodValueOf = null; + try { + methodValueOf = field.getType().getMethod("valueOf", String.class); + if (rs.getString(field.getName()) == null || rs.getString(field.getName()).isEmpty()) + field.set(obj, null); + else { + field.set(obj, methodValueOf.invoke(null, rs.getString(field.getName()))); + } + } catch (NoSuchMethodException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + try { + ObjectMapper objectMapper = new ObjectMapper(); + String json = rs.getString(field.getName()); + if (json == null || json.isEmpty()) { + field.set(obj, null); + } else { + + if (field.getType().equals(List.class)) { + Type type = field.getGenericType(); + if (type instanceof ParameterizedType parameterizedType) { + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length == 1) { + Class clazz = (Class) actualTypeArguments[0]; + field.set(obj, objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz))); + } + } + } else { + field.set(obj, objectMapper.readValue(json, field.getType())); + } + + + } + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + } + return obj; + } + + //存储数据到数据库 + static public List buildArgs(Class tClass, Object obj) { + // Object[] args = new Object[filedCount]; + List args = new ArrayList<>(); + for (java.lang.reflect.Field field : tClass.getDeclaredFields()) { + try { + if (field.getName().equals("id")) + continue; + + if (field.get(obj) == null) { + args.add(null); + continue; + } + + field.setAccessible(true); + if (field.getType().equals(Integer.class) || field.getType().equals(int.class)) { + // args[i] = field.getInt(obj); + args.add(field.get(obj)); + } else if (field.getType().equals(Double.class) || field.getType().equals(Float.class)) { + // args.add( field.getDouble(obj)); + args.add(field.get(obj)); + } else if (field.getType().equals(Boolean.class)) { + Boolean value = (Boolean) field.get(obj); + args.add(value ? 1 : 0); + } else if (field.getType().equals(String.class)) { + args.add(field.get(obj)); + } else if (field.getType().equals(Date.class)) { + Date date = (Date) field.get(obj); + // 按照 yyyy-MM-dd HH:mm:ss 存储 + // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + if (date == null) + args.add(""); + else + args.add(date.getTime()); + } else if (field.getType().isEnum()) { + args.add(field.get(obj).toString()); + } else if (field.getType().equals(ObjectNode.class)) { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE); + objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE); + args.add(objectMapper.writeValueAsString(field.get(obj))); + } else { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE); + objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE); + args.add(objectMapper.writeValueAsString(field.get(obj))); + } + } catch (IllegalAccessException | JsonProcessingException e) { + logger.error("", e); + args.add(null); + } + } + return args; + } + + public static void delete(JdbcTemplate jdbcTemplate, String tableName, int id) { + jdbcTemplate.update("delete from " + tableName + " where id = ?", id); + } + + public static void addObj(JdbcTemplate jdbcTemplate, String tableName, Class tClass, Object obj) { + StringBuilder sql = new StringBuilder("insert into " + tableName + "("); + StringBuilder values = new StringBuilder(" values("); + + int idval = 0; + + for (java.lang.reflect.Field field : tClass.getDeclaredFields()) { + if (field.getName().equals("id")) { + if (field.getType().equals(Integer.class) || field.getType().equals(int.class)) { + field.setAccessible(true); + try { + idval = field.getInt(obj); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + continue; + } + sql.append(field.getName()).append(","); + values.append("?,"); + } + if (idval != 0) { + sql.append("id,"); + values.append("?,"); + } + + sql.deleteCharAt(sql.length() - 1).append(")"); + values.deleteCharAt(values.length() - 1).append(")"); + sql.append(values); + + var args = buildArgs(tClass, obj); + if (idval != 0) + args.add(idval); + + jdbcTemplate.update(sql.toString(), args.toArray()); + } + + public static void updateObj(JdbcTemplate jdbcTemplate, String tableName, Class tClass, Object obj) { + StringBuilder sql = new StringBuilder("update " + tableName + " set "); + int id = 0; + + for (java.lang.reflect.Field field : tClass.getDeclaredFields()) { + if (field.getName().equals("id")) { + field.setAccessible(true); + try { + id = field.getInt(obj); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + continue; + } + sql.append(field.getName()).append(" = ?,"); + } + sql.deleteCharAt(sql.length() - 1).append(" where id = ?"); + List args = buildArgs(tClass, obj); + args.add(id); + + // System.out.println(sql); + // System.out.println(args); + + jdbcTemplate.update(sql.toString(), args.toArray()); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/monitor/collect/d.txt b/src/main/java/com/iflytop/sgs/monitor/collect/d.txt new file mode 100644 index 0000000..758a991 --- /dev/null +++ b/src/main/java/com/iflytop/sgs/monitor/collect/d.txt @@ -0,0 +1 @@ +采集器 \ No newline at end of file diff --git a/src/main/java/com/iflytop/sgs/monitor/controller/CraftsMonitorController.java b/src/main/java/com/iflytop/sgs/monitor/controller/CraftsMonitorController.java new file mode 100644 index 0000000..841338e --- /dev/null +++ b/src/main/java/com/iflytop/sgs/monitor/controller/CraftsMonitorController.java @@ -0,0 +1,46 @@ +package com.iflytop.sgs.monitor.controller; + +import com.iflytop.sgs.app.model.vo.CraftStatusVO; +import com.iflytop.sgs.app.service.CraftsService; +import com.iflytop.sgs.common.result.Result; +import com.iflytop.sgs.common.result.ResultCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "工艺监控") +@RestController +@RequestMapping("/api/monitor/crafts") +@RequiredArgsConstructor +@Slf4j +@Validated +public class CraftsMonitorController { + private final CraftsService craftsService; + + @Operation(summary = "获取某个加热区工艺状态") + @GetMapping("/status/{heatId}") + public Result getStatus( + @NotNull @PathVariable String heatId) { + CraftStatusVO vo = craftsService.getStatus(heatId); + if (vo == null) { + return Result.failed(ResultCode.NOT_FOUND, "未找到执行任务"); + } + return Result.success(vo); + } + + @Operation(summary = "获取所有加热区工艺状态列表") + @GetMapping("/status") + public Result> getAllStatuses() { + List list = craftsService.getAllStatuses(); + return Result.success(list); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..fd90bfd --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,51 @@ +server: + servlet: + context-path: / + port: 8080 + +spring: + application: + name: separate_gold_service + datasource: + url: jdbc:sqlite:db/app.db + driver-class-name: org.sqlite.JDBC + sql: + init: + #always embedded never + mode: always + schema-locations: classpath:/sql/init.sql + +mybatis-plus: + configuration: + # 开启 SQL 日志输出(可选) + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 如果需要加载 XML 文件(自定义 SQL),可配置 mapper-locations: + mapper-locations: classpath*:mapper/*.xml + +#开启 SQL 打印,调试时方便查看 +logging: + level: + root: INFO + org.mybatis: DEBUG + +springdoc: + default-flat-param-object: true + +command_bus: + +device.enableCanBus: true + +iflytophald: + ip: 192.168.8.168 + cmdch.port: 19004 + datach.port: 19005 + +#window config +#modbus: +# port: COM18 +# baudrate: 9600 + +#工控机 config +modbus: + port: ttyS1 + baudrate: 9600 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..3a960f9 --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,41 @@ +server: + servlet: + context-path: / + port: 8080 + +spring: + application: + name: separate_gold_service + datasource: + url: jdbc:sqlite:db/app.db + driver-class-name: org.sqlite.JDBC + sql: + init: + #always embedded never + mode: always + schema-locations: classpath:/sql/init.sql + +mybatis-plus: + configuration: + # 开启 SQL 日志输出(可选) + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 如果需要加载 XML 文件(自定义 SQL),可配置 mapper-locations: + mapper-locations: classpath*:mapper/*.xml + +#开启 SQL 打印,调试时方便查看 +logging: + level: + root: INFO + org.mybatis: DEBUG + +springdoc: + default-flat-param-object: true + +command_bus: + +device.enableCanBus: true + +iflytophald: + ip: 192.168.8.168 + cmdch.port: 19004 + datach.port: 19005 \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 7c883f8..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=separate-gold-service diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..caf4dfc --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/src/main/resources/init/zapp_sub_module_reg_initial_value.csv b/src/main/resources/init/zapp_sub_module_reg_initial_value.csv new file mode 100644 index 0000000..dd53432 --- /dev/null +++ b/src/main/resources/init/zapp_sub_module_reg_initial_value.csv @@ -0,0 +1,691 @@ +id,mid,regIndex,regInitVal +1,HBotClawSV,kreg_mini_servo_limit_velocity,10000 +2,HBotClawSV,kreg_mini_servo_limit_torque,300 +3,HBotClawSV,kreg_mini_servo_protective_torque,800 +4,HBotClawSV,kreg_mini_servo_target_pos_tolerance,15 +5,HBotClawSV,kreg_mini_servo_servo_min_angle,0 +6,HBotClawSV,kreg_mini_servo_servo_max_angle,4095 +7,HBotClawSV,kreg_mini_servo_servo_max_temp,70 +8,HBotClawSV,kreg_mini_servo_servo_max_voltage,254 +9,HBotClawSV,kreg_mini_servo_servo_min_voltage,90 +10,HBotClawSV,kreg_mini_servo_servo_max_torque,1000 +11,HBotClawSV,kreg_mini_servo_servo_unload_condition,44 +12,HBotClawSV,kreg_mini_servo_servo_protect_current,500 +13,HBotClawSV,kreg_mini_servo_servo_protect_torque,40 +14,HBotClawSV,kreg_mini_servo_servo_protect_time,200 +15,HBotClawSV,kreg_mini_servo_servo_overload_torque,40 +16,HBotClawSV,kreg_mini_servo_servo_acc,0 +17,DualRobotAxis1SV,kreg_mini_servo_limit_velocity,10000 +18,DualRobotAxis1SV,kreg_mini_servo_limit_torque,300 +19,DualRobotAxis1SV,kreg_mini_servo_protective_torque,800 +20,DualRobotAxis1SV,kreg_mini_servo_target_pos_tolerance,15 +21,DualRobotAxis1SV,kreg_mini_servo_servo_min_angle,0 +22,DualRobotAxis1SV,kreg_mini_servo_servo_max_angle,4095 +23,DualRobotAxis1SV,kreg_mini_servo_servo_max_temp,70 +24,DualRobotAxis1SV,kreg_mini_servo_servo_max_voltage,254 +25,DualRobotAxis1SV,kreg_mini_servo_servo_min_voltage,90 +26,DualRobotAxis1SV,kreg_mini_servo_servo_max_torque,1000 +27,DualRobotAxis1SV,kreg_mini_servo_servo_unload_condition,44 +28,DualRobotAxis1SV,kreg_mini_servo_servo_protect_current,500 +29,DualRobotAxis1SV,kreg_mini_servo_servo_protect_torque,20 +30,DualRobotAxis1SV,kreg_mini_servo_servo_protect_time,200 +31,DualRobotAxis1SV,kreg_mini_servo_servo_overload_torque,80 +32,DualRobotAxis1SV,kreg_mini_servo_servo_acc,0 +33,DualRobotAxis2SV,kreg_mini_servo_limit_velocity,10000 +34,DualRobotAxis2SV,kreg_mini_servo_limit_torque,300 +35,DualRobotAxis2SV,kreg_mini_servo_protective_torque,800 +36,DualRobotAxis2SV,kreg_mini_servo_target_pos_tolerance,15 +37,DualRobotAxis2SV,kreg_mini_servo_servo_min_angle,0 +38,DualRobotAxis2SV,kreg_mini_servo_servo_max_angle,4095 +39,DualRobotAxis2SV,kreg_mini_servo_servo_max_temp,70 +40,DualRobotAxis2SV,kreg_mini_servo_servo_max_voltage,254 +41,DualRobotAxis2SV,kreg_mini_servo_servo_min_voltage,90 +42,DualRobotAxis2SV,kreg_mini_servo_servo_max_torque,1000 +43,DualRobotAxis2SV,kreg_mini_servo_servo_unload_condition,44 +44,DualRobotAxis2SV,kreg_mini_servo_servo_protect_current,500 +45,DualRobotAxis2SV,kreg_mini_servo_servo_protect_torque,30 +46,DualRobotAxis2SV,kreg_mini_servo_servo_protect_time,200 +47,DualRobotAxis2SV,kreg_mini_servo_servo_overload_torque,80 +48,DualRobotAxis2SV,kreg_mini_servo_servo_acc,0 +50,LiquidDistributionArm,kreg_liquid_distribution_arm_pos0_d0,411 +51,LiquidDistributionArm,kreg_liquid_distribution_arm_pos0_d1,1062 +52,LiquidDistributionArm,kreg_liquid_distribution_arm_pos1_d0,627 +53,LiquidDistributionArm,kreg_liquid_distribution_arm_pos1_d1,621 +54,LiquidDistributionArm,kreg_liquid_distribution_arm_pos2_d0,851 +55,LiquidDistributionArm,kreg_liquid_distribution_arm_pos2_d1,848 +56,LiquidDistributionArm,kreg_liquid_distribution_arm_pos3_d0,1079 +57,LiquidDistributionArm,kreg_liquid_distribution_arm_pos3_d1,1118 +58,LiquidDistributionArm,kreg_liquid_distribution_arm_pos4_d0,1442 +59,LiquidDistributionArm,kreg_liquid_distribution_arm_pos4_d1,1609 +60,LiquidDistributionArm,kreg_liquid_distribution_arm_pos5_d0,881 +61,LiquidDistributionArm,kreg_liquid_distribution_arm_pos5_d1,548 +62,LiquidDistributionArm,kreg_liquid_distribution_arm_pos6_d0,1013 +63,LiquidDistributionArm,kreg_liquid_distribution_arm_pos6_d1,770 +64,LiquidDistributionArm,kreg_liquid_distribution_arm_pos7_d0,1186 +65,LiquidDistributionArm,kreg_liquid_distribution_arm_pos7_d1,1030 +66,LiquidDistributionArm,kreg_liquid_distribution_arm_pos8_d0,1479 +67,LiquidDistributionArm,kreg_liquid_distribution_arm_pos8_d1,1466 +68,LiquidDistributionArm,kreg_liquid_distribution_arm_pos9_d0,1170 +69,LiquidDistributionArm,kreg_liquid_distribution_arm_pos9_d1,543 +70,LiquidDistributionArm,kreg_liquid_distribution_arm_pos10_d0,1236 +71,LiquidDistributionArm,kreg_liquid_distribution_arm_pos10_d1,776 +72,LiquidDistributionArm,kreg_liquid_distribution_arm_pos11_d0,1376 +73,LiquidDistributionArm,kreg_liquid_distribution_arm_pos11_d1,1046 +74,LiquidDistributionArm,kreg_liquid_distribution_arm_pos12_d0,1643 +75,LiquidDistributionArm,kreg_liquid_distribution_arm_pos12_d1,1473 +76,LiquidDistributionArm,kreg_liquid_distribution_arm_pos13_d0,1485 +77,LiquidDistributionArm,kreg_liquid_distribution_arm_pos13_d1,626 +78,LiquidDistributionArm,kreg_liquid_distribution_arm_pos14_d0,1490 +79,LiquidDistributionArm,kreg_liquid_distribution_arm_pos14_d1,845 +80,LiquidDistributionArm,kreg_liquid_distribution_arm_pos15_d0,1600 +81,LiquidDistributionArm,kreg_liquid_distribution_arm_pos15_d1,1127 +82,LiquidDistributionArm,kreg_liquid_distribution_arm_pos16_d0,1956 +83,LiquidDistributionArm,kreg_liquid_distribution_arm_pos16_d1,1731 +84,DoorM,kreg_step_motor_shaft,0 +85,DoorM,kreg_step_motor_one_circle_pulse,7200 +86,DoorM,kreg_step_motor_one_circle_pulse_denominator,1 +87,DoorM,kreg_step_motor_default_velocity,30 +88,DoorM,kreg_step_motor_low_velocity,30 +89,DoorM,kreg_step_motor_mid_velocity,30 +90,DoorM,kreg_step_motor_high_velocity,30 +91,DoorM,kreg_step_motor_ihold,1 +92,DoorM,kreg_step_motor_irun,31 +93,DoorM,kreg_step_motor_iholddelay,10 +94,DoorM,kreg_step_motor_iglobalscaler,72 +95,DoorM,kreg_step_motor_mres,0 +96,DoorM,kreg_step_motor_run_to_zero_speed,10 +97,DoorM,kreg_step_motor_look_zero_edge_speed,30 +98,DoorM,kreg_step_motor_max_d,0 +99,DoorM,kreg_step_motor_min_d,0 +100,DoorM,kreg_step_motor_in_debug_mode,0 +101,DoorM,kreg_step_motor_vstart,10 +102,DoorM,kreg_step_motor_a1,1 +103,DoorM,kreg_step_motor_amax,2 +104,DoorM,kreg_step_motor_v1,5 +105,DoorM,kreg_step_motor_dmax,2 +106,DoorM,kreg_step_motor_d1,1 +107,DoorM,kreg_step_motor_vstop,10 +108,DoorM,kreg_step_motor_tzerowait,0 +109,DoorM,kreg_step_motor_enc_resolution,0 +110,DoorM,kreg_step_motor_enable_enc,0 +111,DoorM,kreg_step_motor_dzero_pos,0 +112,DoorM,kret_step_motor_pos_devi_tolerance,0 +113,DoorM,kret_step_motor_io_trigger_append_distance,0 +114,ShakeM,kreg_step_motor_shaft,1 +115,ShakeM,kreg_step_motor_one_circle_pulse,100 +116,ShakeM,kreg_step_motor_one_circle_pulse_denominator,1 +117,ShakeM,kreg_step_motor_default_velocity,250 +118,ShakeM,kreg_step_motor_low_velocity,0 +119,ShakeM,kreg_step_motor_mid_velocity,0 +120,ShakeM,kreg_step_motor_high_velocity,0 +121,ShakeM,kreg_step_motor_ihold,6 +122,ShakeM,kreg_step_motor_irun,8 +123,ShakeM,kreg_step_motor_iholddelay,10 +124,ShakeM,kreg_step_motor_iglobalscaler,0 +125,ShakeM,kreg_step_motor_mres,0 +126,ShakeM,kreg_step_motor_run_to_zero_speed,10 +127,ShakeM,kreg_step_motor_look_zero_edge_speed,10 +128,ShakeM,kreg_step_motor_max_d,0 +129,ShakeM,kreg_step_motor_min_d,0 +130,ShakeM,kreg_step_motor_in_debug_mode,0 +131,ShakeM,kreg_step_motor_vstart,100 +132,ShakeM,kreg_step_motor_a1,50 +133,ShakeM,kreg_step_motor_amax,100 +134,ShakeM,kreg_step_motor_v1,300 +135,ShakeM,kreg_step_motor_dmax,100 +136,ShakeM,kreg_step_motor_d1,50 +137,ShakeM,kreg_step_motor_vstop,100 +138,ShakeM,kreg_step_motor_tzerowait,0 +139,ShakeM,kreg_step_motor_enc_resolution,0 +140,ShakeM,kreg_step_motor_enable_enc,0 +141,ShakeM,kreg_step_motor_dzero_pos,0 +142,ShakeM,kret_step_motor_pos_devi_tolerance,0 +143,ShakeM,kret_step_motor_io_trigger_append_distance,0 +144,CapStorageM,kreg_step_motor_shaft,1 +145,CapStorageM,kreg_step_motor_one_circle_pulse,225 +146,CapStorageM,kreg_step_motor_one_circle_pulse_denominator,1 +147,CapStorageM,kreg_step_motor_default_velocity,300 +148,CapStorageM,kreg_step_motor_low_velocity,0 +149,CapStorageM,kreg_step_motor_mid_velocity,0 +150,CapStorageM,kreg_step_motor_high_velocity,0 +151,CapStorageM,kreg_step_motor_ihold,5 +152,CapStorageM,kreg_step_motor_irun,12 +153,CapStorageM,kreg_step_motor_iholddelay,10 +154,CapStorageM,kreg_step_motor_iglobalscaler,0 +155,CapStorageM,kreg_step_motor_mres,0 +156,CapStorageM,kreg_step_motor_run_to_zero_speed,200 +157,CapStorageM,kreg_step_motor_look_zero_edge_speed,100 +158,CapStorageM,kreg_step_motor_max_d,0 +159,CapStorageM,kreg_step_motor_min_d,0 +160,CapStorageM,kreg_step_motor_in_debug_mode,0 +161,CapStorageM,kreg_step_motor_vstart,100 +162,CapStorageM,kreg_step_motor_a1,50 +163,CapStorageM,kreg_step_motor_amax,100 +164,CapStorageM,kreg_step_motor_v1,300 +165,CapStorageM,kreg_step_motor_dmax,100 +166,CapStorageM,kreg_step_motor_d1,50 +167,CapStorageM,kreg_step_motor_vstop,100 +168,CapStorageM,kreg_step_motor_tzerowait,0 +169,CapStorageM,kreg_step_motor_enc_resolution,0 +170,CapStorageM,kreg_step_motor_enable_enc,0 +171,CapStorageM,kreg_step_motor_dzero_pos,0 +172,CapStorageM,kret_step_motor_pos_devi_tolerance,0 +173,CapStorageM,kret_step_motor_io_trigger_append_distance,0 +174,HBotXM,kreg_step_motor_shaft,1 +175,HBotXM,kreg_step_motor_one_circle_pulse,500 +176,HBotXM,kreg_step_motor_one_circle_pulse_denominator,1 +177,HBotXM,kreg_step_motor_default_velocity,500 +178,HBotXM,kreg_step_motor_low_velocity,0 +179,HBotXM,kreg_step_motor_mid_velocity,0 +180,HBotXM,kreg_step_motor_high_velocity,0 +181,HBotXM,kreg_step_motor_ihold,5 +182,HBotXM,kreg_step_motor_irun,30 +183,HBotXM,kreg_step_motor_iholddelay,10 +184,HBotXM,kreg_step_motor_iglobalscaler,0 +185,HBotXM,kreg_step_motor_mres,0 +186,HBotXM,kreg_step_motor_run_to_zero_speed,100 +187,HBotXM,kreg_step_motor_look_zero_edge_speed,100 +188,HBotXM,kreg_step_motor_max_d,0 +189,HBotXM,kreg_step_motor_min_d,0 +190,HBotXM,kreg_step_motor_in_debug_mode,0 +191,HBotXM,kreg_step_motor_vstart,100 +192,HBotXM,kreg_step_motor_a1,50 +193,HBotXM,kreg_step_motor_amax,100 +194,HBotXM,kreg_step_motor_v1,300 +195,HBotXM,kreg_step_motor_dmax,100 +196,HBotXM,kreg_step_motor_d1,50 +197,HBotXM,kreg_step_motor_vstop,100 +198,HBotXM,kreg_step_motor_tzerowait,0 +199,HBotXM,kreg_step_motor_enc_resolution,0 +200,HBotXM,kreg_step_motor_enable_enc,0 +201,HBotXM,kreg_step_motor_dzero_pos,0 +202,HBotXM,kret_step_motor_pos_devi_tolerance,0 +203,HBotXM,kret_step_motor_io_trigger_append_distance,0 +204,HBotYM,kreg_step_motor_shaft,1 +205,HBotYM,kreg_step_motor_one_circle_pulse,400 +206,HBotYM,kreg_step_motor_one_circle_pulse_denominator,1 +207,HBotYM,kreg_step_motor_default_velocity,500 +208,HBotYM,kreg_step_motor_low_velocity,0 +209,HBotYM,kreg_step_motor_mid_velocity,0 +210,HBotYM,kreg_step_motor_high_velocity,0 +211,HBotYM,kreg_step_motor_ihold,5 +212,HBotYM,kreg_step_motor_irun,20 +213,HBotYM,kreg_step_motor_iholddelay,10 +214,HBotYM,kreg_step_motor_iglobalscaler,0 +215,HBotYM,kreg_step_motor_mres,0 +216,HBotYM,kreg_step_motor_run_to_zero_speed,100 +217,HBotYM,kreg_step_motor_look_zero_edge_speed,100 +218,HBotYM,kreg_step_motor_max_d,0 +219,HBotYM,kreg_step_motor_min_d,0 +220,HBotYM,kreg_step_motor_in_debug_mode,0 +221,HBotYM,kreg_step_motor_vstart,100 +222,HBotYM,kreg_step_motor_a1,50 +223,HBotYM,kreg_step_motor_amax,100 +224,HBotYM,kreg_step_motor_v1,300 +225,HBotYM,kreg_step_motor_dmax,100 +226,HBotYM,kreg_step_motor_d1,50 +227,HBotYM,kreg_step_motor_vstop,100 +228,HBotYM,kreg_step_motor_tzerowait,0 +229,HBotYM,kreg_step_motor_enc_resolution,0 +230,HBotYM,kreg_step_motor_enable_enc,0 +231,HBotYM,kreg_step_motor_dzero_pos,0 +232,HBotYM,kret_step_motor_pos_devi_tolerance,0 +233,HBotYM,kret_step_motor_io_trigger_append_distance,0 +234,HBotZM,kreg_step_motor_shaft,1 +235,HBotZM,kreg_step_motor_one_circle_pulse,500 +236,HBotZM,kreg_step_motor_one_circle_pulse_denominator,1 +237,HBotZM,kreg_step_motor_default_velocity,800 +238,HBotZM,kreg_step_motor_low_velocity,0 +239,HBotZM,kreg_step_motor_mid_velocity,0 +240,HBotZM,kreg_step_motor_high_velocity,0 +241,HBotZM,kreg_step_motor_ihold,5 +242,HBotZM,kreg_step_motor_irun,15 +243,HBotZM,kreg_step_motor_iholddelay,10 +244,HBotZM,kreg_step_motor_iglobalscaler,0 +245,HBotZM,kreg_step_motor_mres,0 +246,HBotZM,kreg_step_motor_run_to_zero_speed,200 +247,HBotZM,kreg_step_motor_look_zero_edge_speed,100 +248,HBotZM,kreg_step_motor_max_d,0 +249,HBotZM,kreg_step_motor_min_d,0 +250,HBotZM,kreg_step_motor_in_debug_mode,0 +251,HBotZM,kreg_step_motor_vstart,100 +252,HBotZM,kreg_step_motor_a1,50 +253,HBotZM,kreg_step_motor_amax,100 +254,HBotZM,kreg_step_motor_v1,300 +255,HBotZM,kreg_step_motor_dmax,100 +256,HBotZM,kreg_step_motor_d1,50 +257,HBotZM,kreg_step_motor_vstop,100 +258,HBotZM,kreg_step_motor_tzerowait,0 +259,HBotZM,kreg_step_motor_enc_resolution,1000 +260,HBotZM,kreg_step_motor_enable_enc,1 +261,HBotZM,kreg_step_motor_dzero_pos,0 +262,HBotZM,kret_step_motor_pos_devi_tolerance,200 +263,HBotZM,kret_step_motor_io_trigger_append_distance,0 +264,Heater1M,kreg_step_motor_shaft,1 +265,Heater1M,kreg_step_motor_one_circle_pulse,200 +266,Heater1M,kreg_step_motor_one_circle_pulse_denominator,1 +267,Heater1M,kreg_step_motor_default_velocity,500 +268,Heater1M,kreg_step_motor_low_velocity,0 +269,Heater1M,kreg_step_motor_mid_velocity,0 +270,Heater1M,kreg_step_motor_high_velocity,0 +271,Heater1M,kreg_step_motor_ihold,5 +272,Heater1M,kreg_step_motor_irun,12 +273,Heater1M,kreg_step_motor_iholddelay,10 +274,Heater1M,kreg_step_motor_iglobalscaler,0 +275,Heater1M,kreg_step_motor_mres,0 +276,Heater1M,kreg_step_motor_run_to_zero_speed,200 +277,Heater1M,kreg_step_motor_look_zero_edge_speed,100 +278,Heater1M,kreg_step_motor_max_d,0 +279,Heater1M,kreg_step_motor_min_d,0 +280,Heater1M,kreg_step_motor_in_debug_mode,0 +281,Heater1M,kreg_step_motor_vstart,100 +282,Heater1M,kreg_step_motor_a1,50 +283,Heater1M,kreg_step_motor_amax,100 +284,Heater1M,kreg_step_motor_v1,300 +285,Heater1M,kreg_step_motor_dmax,100 +286,Heater1M,kreg_step_motor_d1,50 +287,Heater1M,kreg_step_motor_vstop,100 +288,Heater1M,kreg_step_motor_tzerowait,0 +289,Heater1M,kreg_step_motor_enc_resolution,0 +290,Heater1M,kreg_step_motor_enable_enc,0 +291,Heater1M,kreg_step_motor_dzero_pos,0 +292,Heater1M,kret_step_motor_pos_devi_tolerance,0 +293,Heater1M,kret_step_motor_io_trigger_append_distance,0 +294,Heater2M,kreg_step_motor_shaft,1 +295,Heater2M,kreg_step_motor_one_circle_pulse,200 +296,Heater2M,kreg_step_motor_one_circle_pulse_denominator,1 +297,Heater2M,kreg_step_motor_default_velocity,500 +298,Heater2M,kreg_step_motor_low_velocity,0 +299,Heater2M,kreg_step_motor_mid_velocity,0 +300,Heater2M,kreg_step_motor_high_velocity,0 +301,Heater2M,kreg_step_motor_ihold,5 +302,Heater2M,kreg_step_motor_irun,12 +303,Heater2M,kreg_step_motor_iholddelay,10 +304,Heater2M,kreg_step_motor_iglobalscaler,0 +305,Heater2M,kreg_step_motor_mres,0 +306,Heater2M,kreg_step_motor_run_to_zero_speed,200 +307,Heater2M,kreg_step_motor_look_zero_edge_speed,100 +308,Heater2M,kreg_step_motor_max_d,0 +309,Heater2M,kreg_step_motor_min_d,0 +310,Heater2M,kreg_step_motor_in_debug_mode,0 +311,Heater2M,kreg_step_motor_vstart,100 +312,Heater2M,kreg_step_motor_a1,50 +313,Heater2M,kreg_step_motor_amax,100 +314,Heater2M,kreg_step_motor_v1,300 +315,Heater2M,kreg_step_motor_dmax,100 +316,Heater2M,kreg_step_motor_d1,50 +317,Heater2M,kreg_step_motor_vstop,100 +318,Heater2M,kreg_step_motor_tzerowait,0 +319,Heater2M,kreg_step_motor_enc_resolution,0 +320,Heater2M,kreg_step_motor_enable_enc,0 +321,Heater2M,kreg_step_motor_dzero_pos,0 +322,Heater2M,kret_step_motor_pos_devi_tolerance,0 +323,Heater2M,kret_step_motor_io_trigger_append_distance,0 +324,Heater3M,kreg_step_motor_shaft,1 +325,Heater3M,kreg_step_motor_one_circle_pulse,200 +326,Heater3M,kreg_step_motor_one_circle_pulse_denominator,1 +327,Heater3M,kreg_step_motor_default_velocity,500 +328,Heater3M,kreg_step_motor_low_velocity,0 +329,Heater3M,kreg_step_motor_mid_velocity,0 +330,Heater3M,kreg_step_motor_high_velocity,0 +331,Heater3M,kreg_step_motor_ihold,5 +332,Heater3M,kreg_step_motor_irun,12 +333,Heater3M,kreg_step_motor_iholddelay,10 +334,Heater3M,kreg_step_motor_iglobalscaler,0 +335,Heater3M,kreg_step_motor_mres,0 +336,Heater3M,kreg_step_motor_run_to_zero_speed,200 +337,Heater3M,kreg_step_motor_look_zero_edge_speed,100 +338,Heater3M,kreg_step_motor_max_d,0 +339,Heater3M,kreg_step_motor_min_d,0 +340,Heater3M,kreg_step_motor_in_debug_mode,0 +341,Heater3M,kreg_step_motor_vstart,100 +342,Heater3M,kreg_step_motor_a1,50 +343,Heater3M,kreg_step_motor_amax,100 +344,Heater3M,kreg_step_motor_v1,300 +345,Heater3M,kreg_step_motor_dmax,100 +346,Heater3M,kreg_step_motor_d1,50 +347,Heater3M,kreg_step_motor_vstop,100 +348,Heater3M,kreg_step_motor_tzerowait,0 +349,Heater3M,kreg_step_motor_enc_resolution,0 +350,Heater3M,kreg_step_motor_enable_enc,0 +351,Heater3M,kreg_step_motor_dzero_pos,0 +352,Heater3M,kret_step_motor_pos_devi_tolerance,0 +353,Heater3M,kret_step_motor_io_trigger_append_distance,0 +354,Heater4M,kreg_step_motor_shaft,1 +355,Heater4M,kreg_step_motor_one_circle_pulse,200 +356,Heater4M,kreg_step_motor_one_circle_pulse_denominator,1 +357,Heater4M,kreg_step_motor_default_velocity,500 +358,Heater4M,kreg_step_motor_low_velocity,0 +359,Heater4M,kreg_step_motor_mid_velocity,0 +360,Heater4M,kreg_step_motor_high_velocity,0 +361,Heater4M,kreg_step_motor_ihold,5 +362,Heater4M,kreg_step_motor_irun,12 +363,Heater4M,kreg_step_motor_iholddelay,10 +364,Heater4M,kreg_step_motor_iglobalscaler,0 +365,Heater4M,kreg_step_motor_mres,0 +366,Heater4M,kreg_step_motor_run_to_zero_speed,200 +367,Heater4M,kreg_step_motor_look_zero_edge_speed,100 +368,Heater4M,kreg_step_motor_max_d,0 +369,Heater4M,kreg_step_motor_min_d,0 +370,Heater4M,kreg_step_motor_in_debug_mode,0 +371,Heater4M,kreg_step_motor_vstart,100 +372,Heater4M,kreg_step_motor_a1,50 +373,Heater4M,kreg_step_motor_amax,100 +374,Heater4M,kreg_step_motor_v1,300 +375,Heater4M,kreg_step_motor_dmax,100 +376,Heater4M,kreg_step_motor_d1,50 +377,Heater4M,kreg_step_motor_vstop,100 +378,Heater4M,kreg_step_motor_tzerowait,0 +379,Heater4M,kreg_step_motor_enc_resolution,0 +380,Heater4M,kreg_step_motor_enable_enc,0 +381,Heater4M,kreg_step_motor_dzero_pos,0 +382,Heater4M,kret_step_motor_pos_devi_tolerance,0 +383,Heater4M,kret_step_motor_io_trigger_append_distance,0 +384,Heater5M,kreg_step_motor_shaft,1 +385,Heater5M,kreg_step_motor_one_circle_pulse,200 +386,Heater5M,kreg_step_motor_one_circle_pulse_denominator,1 +387,Heater5M,kreg_step_motor_default_velocity,500 +388,Heater5M,kreg_step_motor_low_velocity,0 +389,Heater5M,kreg_step_motor_mid_velocity,0 +390,Heater5M,kreg_step_motor_high_velocity,0 +391,Heater5M,kreg_step_motor_ihold,5 +392,Heater5M,kreg_step_motor_irun,12 +393,Heater5M,kreg_step_motor_iholddelay,10 +394,Heater5M,kreg_step_motor_iglobalscaler,0 +395,Heater5M,kreg_step_motor_mres,0 +396,Heater5M,kreg_step_motor_run_to_zero_speed,200 +397,Heater5M,kreg_step_motor_look_zero_edge_speed,100 +398,Heater5M,kreg_step_motor_max_d,0 +399,Heater5M,kreg_step_motor_min_d,0 +400,Heater5M,kreg_step_motor_in_debug_mode,0 +401,Heater5M,kreg_step_motor_vstart,100 +402,Heater5M,kreg_step_motor_a1,50 +403,Heater5M,kreg_step_motor_amax,100 +404,Heater5M,kreg_step_motor_v1,300 +405,Heater5M,kreg_step_motor_dmax,100 +406,Heater5M,kreg_step_motor_d1,50 +407,Heater5M,kreg_step_motor_vstop,100 +408,Heater5M,kreg_step_motor_tzerowait,0 +409,Heater5M,kreg_step_motor_enc_resolution,0 +410,Heater5M,kreg_step_motor_enable_enc,0 +411,Heater5M,kreg_step_motor_dzero_pos,0 +412,Heater5M,kret_step_motor_pos_devi_tolerance,0 +413,Heater5M,kret_step_motor_io_trigger_append_distance,0 +414,Heater6M,kreg_step_motor_shaft,1 +415,Heater6M,kreg_step_motor_one_circle_pulse,200 +416,Heater6M,kreg_step_motor_one_circle_pulse_denominator,1 +417,Heater6M,kreg_step_motor_default_velocity,500 +418,Heater6M,kreg_step_motor_low_velocity,0 +419,Heater6M,kreg_step_motor_mid_velocity,0 +420,Heater6M,kreg_step_motor_high_velocity,0 +421,Heater6M,kreg_step_motor_ihold,5 +422,Heater6M,kreg_step_motor_irun,12 +423,Heater6M,kreg_step_motor_iholddelay,10 +424,Heater6M,kreg_step_motor_iglobalscaler,0 +425,Heater6M,kreg_step_motor_mres,0 +426,Heater6M,kreg_step_motor_run_to_zero_speed,200 +427,Heater6M,kreg_step_motor_look_zero_edge_speed,100 +428,Heater6M,kreg_step_motor_max_d,0 +429,Heater6M,kreg_step_motor_min_d,0 +430,Heater6M,kreg_step_motor_in_debug_mode,0 +431,Heater6M,kreg_step_motor_vstart,100 +432,Heater6M,kreg_step_motor_a1,50 +433,Heater6M,kreg_step_motor_amax,100 +434,Heater6M,kreg_step_motor_v1,300 +435,Heater6M,kreg_step_motor_dmax,100 +436,Heater6M,kreg_step_motor_d1,50 +437,Heater6M,kreg_step_motor_vstop,100 +438,Heater6M,kreg_step_motor_tzerowait,0 +439,Heater6M,kreg_step_motor_enc_resolution,0 +440,Heater6M,kreg_step_motor_enable_enc,0 +441,Heater6M,kreg_step_motor_dzero_pos,0 +442,Heater6M,kret_step_motor_pos_devi_tolerance,0 +443,Heater6M,kret_step_motor_io_trigger_append_distance,0 +444,AcidPump1M,kreg_step_motor_shaft,1 +445,AcidPump1M,kreg_step_motor_one_circle_pulse,100 +446,AcidPump1M,kreg_step_motor_one_circle_pulse_denominator,1 +447,AcidPump1M,kreg_step_motor_default_velocity,300 +448,AcidPump1M,kreg_step_motor_low_velocity,0 +449,AcidPump1M,kreg_step_motor_mid_velocity,0 +450,AcidPump1M,kreg_step_motor_high_velocity,0 +451,AcidPump1M,kreg_step_motor_ihold,1 +452,AcidPump1M,kreg_step_motor_irun,20 +453,AcidPump1M,kreg_step_motor_iholddelay,10 +454,AcidPump1M,kreg_step_motor_iglobalscaler,33 +455,AcidPump1M,kreg_step_motor_mres,0 +456,AcidPump1M,kreg_step_motor_run_to_zero_speed,100 +457,AcidPump1M,kreg_step_motor_look_zero_edge_speed,100 +458,AcidPump1M,kreg_step_motor_max_d,0 +459,AcidPump1M,kreg_step_motor_min_d,0 +460,AcidPump1M,kreg_step_motor_in_debug_mode,0 +461,AcidPump1M,kreg_step_motor_vstart,200 +462,AcidPump1M,kreg_step_motor_a1,300 +463,AcidPump1M,kreg_step_motor_amax,300 +464,AcidPump1M,kreg_step_motor_v1,100 +465,AcidPump1M,kreg_step_motor_dmax,300 +466,AcidPump1M,kreg_step_motor_d1,300 +467,AcidPump1M,kreg_step_motor_vstop,200 +468,AcidPump1M,kreg_step_motor_tzerowait,0 +469,AcidPump1M,kreg_step_motor_enc_resolution,0 +470,AcidPump1M,kreg_step_motor_enable_enc,0 +471,AcidPump1M,kreg_step_motor_dzero_pos,0 +472,AcidPump1M,kret_step_motor_pos_devi_tolerance,0 +473,AcidPump1M,kret_step_motor_io_trigger_append_distance,0 +474,AcidPump2M,kreg_step_motor_shaft,1 +475,AcidPump2M,kreg_step_motor_one_circle_pulse,100 +476,AcidPump2M,kreg_step_motor_one_circle_pulse_denominator,1 +477,AcidPump2M,kreg_step_motor_default_velocity,300 +478,AcidPump2M,kreg_step_motor_low_velocity,0 +479,AcidPump2M,kreg_step_motor_mid_velocity,0 +480,AcidPump2M,kreg_step_motor_high_velocity,0 +481,AcidPump2M,kreg_step_motor_ihold,1 +482,AcidPump2M,kreg_step_motor_irun,20 +483,AcidPump2M,kreg_step_motor_iholddelay,10 +484,AcidPump2M,kreg_step_motor_iglobalscaler,33 +485,AcidPump2M,kreg_step_motor_mres,0 +486,AcidPump2M,kreg_step_motor_run_to_zero_speed,100 +487,AcidPump2M,kreg_step_motor_look_zero_edge_speed,100 +488,AcidPump2M,kreg_step_motor_max_d,0 +489,AcidPump2M,kreg_step_motor_min_d,0 +490,AcidPump2M,kreg_step_motor_in_debug_mode,0 +491,AcidPump2M,kreg_step_motor_vstart,200 +492,AcidPump2M,kreg_step_motor_a1,300 +493,AcidPump2M,kreg_step_motor_amax,300 +494,AcidPump2M,kreg_step_motor_v1,100 +495,AcidPump2M,kreg_step_motor_dmax,300 +496,AcidPump2M,kreg_step_motor_d1,300 +497,AcidPump2M,kreg_step_motor_vstop,200 +498,AcidPump2M,kreg_step_motor_tzerowait,0 +499,AcidPump2M,kreg_step_motor_enc_resolution,0 +500,AcidPump2M,kreg_step_motor_enable_enc,0 +501,AcidPump2M,kreg_step_motor_dzero_pos,0 +502,AcidPump2M,kret_step_motor_pos_devi_tolerance,0 +503,AcidPump2M,kret_step_motor_io_trigger_append_distance,0 +504,AcidPump3M,kreg_step_motor_shaft,1 +505,AcidPump3M,kreg_step_motor_one_circle_pulse,100 +506,AcidPump3M,kreg_step_motor_one_circle_pulse_denominator,1 +507,AcidPump3M,kreg_step_motor_default_velocity,300 +508,AcidPump3M,kreg_step_motor_low_velocity,0 +509,AcidPump3M,kreg_step_motor_mid_velocity,0 +510,AcidPump3M,kreg_step_motor_high_velocity,0 +511,AcidPump3M,kreg_step_motor_ihold,1 +512,AcidPump3M,kreg_step_motor_irun,20 +513,AcidPump3M,kreg_step_motor_iholddelay,10 +514,AcidPump3M,kreg_step_motor_iglobalscaler,33 +515,AcidPump3M,kreg_step_motor_mres,0 +516,AcidPump3M,kreg_step_motor_run_to_zero_speed,100 +517,AcidPump3M,kreg_step_motor_look_zero_edge_speed,100 +518,AcidPump3M,kreg_step_motor_max_d,0 +519,AcidPump3M,kreg_step_motor_min_d,0 +520,AcidPump3M,kreg_step_motor_in_debug_mode,0 +521,AcidPump3M,kreg_step_motor_vstart,200 +522,AcidPump3M,kreg_step_motor_a1,300 +523,AcidPump3M,kreg_step_motor_amax,300 +524,AcidPump3M,kreg_step_motor_v1,100 +525,AcidPump3M,kreg_step_motor_dmax,300 +526,AcidPump3M,kreg_step_motor_d1,300 +527,AcidPump3M,kreg_step_motor_vstop,200 +528,AcidPump3M,kreg_step_motor_tzerowait,0 +529,AcidPump3M,kreg_step_motor_enc_resolution,0 +530,AcidPump3M,kreg_step_motor_enable_enc,0 +531,AcidPump3M,kreg_step_motor_dzero_pos,0 +532,AcidPump3M,kret_step_motor_pos_devi_tolerance,0 +533,AcidPump3M,kret_step_motor_io_trigger_append_distance,0 +534,AcidPump4M,kreg_step_motor_shaft,1 +535,AcidPump4M,kreg_step_motor_one_circle_pulse,100 +536,AcidPump4M,kreg_step_motor_one_circle_pulse_denominator,1 +537,AcidPump4M,kreg_step_motor_default_velocity,300 +538,AcidPump4M,kreg_step_motor_low_velocity,0 +539,AcidPump4M,kreg_step_motor_mid_velocity,0 +540,AcidPump4M,kreg_step_motor_high_velocity,0 +541,AcidPump4M,kreg_step_motor_ihold,1 +542,AcidPump4M,kreg_step_motor_irun,20 +543,AcidPump4M,kreg_step_motor_iholddelay,10 +544,AcidPump4M,kreg_step_motor_iglobalscaler,33 +545,AcidPump4M,kreg_step_motor_mres,0 +546,AcidPump4M,kreg_step_motor_run_to_zero_speed,100 +547,AcidPump4M,kreg_step_motor_look_zero_edge_speed,100 +548,AcidPump4M,kreg_step_motor_max_d,0 +549,AcidPump4M,kreg_step_motor_min_d,0 +550,AcidPump4M,kreg_step_motor_in_debug_mode,0 +551,AcidPump4M,kreg_step_motor_vstart,200 +552,AcidPump4M,kreg_step_motor_a1,300 +553,AcidPump4M,kreg_step_motor_amax,300 +554,AcidPump4M,kreg_step_motor_v1,100 +555,AcidPump4M,kreg_step_motor_dmax,300 +556,AcidPump4M,kreg_step_motor_d1,300 +557,AcidPump4M,kreg_step_motor_vstop,200 +558,AcidPump4M,kreg_step_motor_tzerowait,0 +559,AcidPump4M,kreg_step_motor_enc_resolution,0 +560,AcidPump4M,kreg_step_motor_enable_enc,0 +561,AcidPump4M,kreg_step_motor_dzero_pos,0 +562,AcidPump4M,kret_step_motor_pos_devi_tolerance,0 +563,AcidPump4M,kret_step_motor_io_trigger_append_distance,0 +564,AcidPump5M,kreg_step_motor_shaft,1 +565,AcidPump5M,kreg_step_motor_one_circle_pulse,100 +566,AcidPump5M,kreg_step_motor_one_circle_pulse_denominator,1 +567,AcidPump5M,kreg_step_motor_default_velocity,300 +568,AcidPump5M,kreg_step_motor_low_velocity,0 +569,AcidPump5M,kreg_step_motor_mid_velocity,0 +570,AcidPump5M,kreg_step_motor_high_velocity,0 +571,AcidPump5M,kreg_step_motor_ihold,1 +572,AcidPump5M,kreg_step_motor_irun,20 +573,AcidPump5M,kreg_step_motor_iholddelay,10 +574,AcidPump5M,kreg_step_motor_iglobalscaler,33 +575,AcidPump5M,kreg_step_motor_mres,0 +576,AcidPump5M,kreg_step_motor_run_to_zero_speed,100 +577,AcidPump5M,kreg_step_motor_look_zero_edge_speed,100 +578,AcidPump5M,kreg_step_motor_max_d,0 +579,AcidPump5M,kreg_step_motor_min_d,0 +580,AcidPump5M,kreg_step_motor_in_debug_mode,0 +581,AcidPump5M,kreg_step_motor_vstart,200 +582,AcidPump5M,kreg_step_motor_a1,300 +583,AcidPump5M,kreg_step_motor_amax,300 +584,AcidPump5M,kreg_step_motor_v1,100 +585,AcidPump5M,kreg_step_motor_dmax,300 +586,AcidPump5M,kreg_step_motor_d1,300 +587,AcidPump5M,kreg_step_motor_vstop,200 +588,AcidPump5M,kreg_step_motor_tzerowait,0 +589,AcidPump5M,kreg_step_motor_enc_resolution,0 +590,AcidPump5M,kreg_step_motor_enable_enc,0 +591,AcidPump5M,kreg_step_motor_dzero_pos,0 +592,AcidPump5M,kret_step_motor_pos_devi_tolerance,0 +593,AcidPump5M,kret_step_motor_io_trigger_append_distance,0 +594,AcidPump6M,kreg_step_motor_shaft,1 +595,AcidPump6M,kreg_step_motor_one_circle_pulse,100 +596,AcidPump6M,kreg_step_motor_one_circle_pulse_denominator,1 +597,AcidPump6M,kreg_step_motor_default_velocity,300 +598,AcidPump6M,kreg_step_motor_low_velocity,0 +599,AcidPump6M,kreg_step_motor_mid_velocity,0 +600,AcidPump6M,kreg_step_motor_high_velocity,0 +601,AcidPump6M,kreg_step_motor_ihold,1 +602,AcidPump6M,kreg_step_motor_irun,20 +603,AcidPump6M,kreg_step_motor_iholddelay,10 +604,AcidPump6M,kreg_step_motor_iglobalscaler,33 +605,AcidPump6M,kreg_step_motor_mres,0 +606,AcidPump6M,kreg_step_motor_run_to_zero_speed,100 +607,AcidPump6M,kreg_step_motor_look_zero_edge_speed,100 +608,AcidPump6M,kreg_step_motor_max_d,0 +609,AcidPump6M,kreg_step_motor_min_d,0 +610,AcidPump6M,kreg_step_motor_in_debug_mode,0 +611,AcidPump6M,kreg_step_motor_vstart,200 +612,AcidPump6M,kreg_step_motor_a1,300 +613,AcidPump6M,kreg_step_motor_amax,300 +614,AcidPump6M,kreg_step_motor_v1,100 +615,AcidPump6M,kreg_step_motor_dmax,300 +616,AcidPump6M,kreg_step_motor_d1,300 +617,AcidPump6M,kreg_step_motor_vstop,200 +618,AcidPump6M,kreg_step_motor_tzerowait,0 +619,AcidPump6M,kreg_step_motor_enc_resolution,0 +620,AcidPump6M,kreg_step_motor_enable_enc,0 +621,AcidPump6M,kreg_step_motor_dzero_pos,0 +622,AcidPump6M,kret_step_motor_pos_devi_tolerance,0 +623,AcidPump6M,kret_step_motor_io_trigger_append_distance,0 +624,AcidPump7M,kreg_step_motor_shaft,1 +625,AcidPump7M,kreg_step_motor_one_circle_pulse,100 +626,AcidPump7M,kreg_step_motor_one_circle_pulse_denominator,1 +627,AcidPump7M,kreg_step_motor_default_velocity,500 +628,AcidPump7M,kreg_step_motor_low_velocity,0 +629,AcidPump7M,kreg_step_motor_mid_velocity,0 +630,AcidPump7M,kreg_step_motor_high_velocity,0 +631,AcidPump7M,kreg_step_motor_ihold,5 +632,AcidPump7M,kreg_step_motor_irun,10 +633,AcidPump7M,kreg_step_motor_iholddelay,10 +634,AcidPump7M,kreg_step_motor_iglobalscaler,0 +635,AcidPump7M,kreg_step_motor_mres,0 +636,AcidPump7M,kreg_step_motor_run_to_zero_speed,100 +637,AcidPump7M,kreg_step_motor_look_zero_edge_speed,100 +638,AcidPump7M,kreg_step_motor_max_d,0 +639,AcidPump7M,kreg_step_motor_min_d,0 +640,AcidPump7M,kreg_step_motor_in_debug_mode,0 +641,AcidPump7M,kreg_step_motor_vstart,100 +642,AcidPump7M,kreg_step_motor_a1,50 +643,AcidPump7M,kreg_step_motor_amax,100 +644,AcidPump7M,kreg_step_motor_v1,300 +645,AcidPump7M,kreg_step_motor_dmax,100 +646,AcidPump7M,kreg_step_motor_d1,50 +647,AcidPump7M,kreg_step_motor_vstop,100 +648,AcidPump7M,kreg_step_motor_tzerowait,0 +649,AcidPump7M,kreg_step_motor_enc_resolution,0 +650,AcidPump7M,kreg_step_motor_enable_enc,0 +651,AcidPump7M,kreg_step_motor_dzero_pos,0 +652,AcidPump7M,kret_step_motor_pos_devi_tolerance,0 +653,AcidPump7M,kret_step_motor_io_trigger_append_distance,0 +654,AcidPump8M,kreg_step_motor_shaft,1 +655,AcidPump8M,kreg_step_motor_one_circle_pulse,100 +656,AcidPump8M,kreg_step_motor_one_circle_pulse_denominator,1 +657,AcidPump8M,kreg_step_motor_default_velocity,500 +658,AcidPump8M,kreg_step_motor_low_velocity,0 +659,AcidPump8M,kreg_step_motor_mid_velocity,0 +660,AcidPump8M,kreg_step_motor_high_velocity,0 +661,AcidPump8M,kreg_step_motor_ihold,5 +662,AcidPump8M,kreg_step_motor_irun,10 +663,AcidPump8M,kreg_step_motor_iholddelay,10 +664,AcidPump8M,kreg_step_motor_iglobalscaler,0 +665,AcidPump8M,kreg_step_motor_mres,0 +666,AcidPump8M,kreg_step_motor_run_to_zero_speed,100 +667,AcidPump8M,kreg_step_motor_look_zero_edge_speed,100 +668,AcidPump8M,kreg_step_motor_max_d,0 +669,AcidPump8M,kreg_step_motor_min_d,0 +670,AcidPump8M,kreg_step_motor_in_debug_mode,0 +671,AcidPump8M,kreg_step_motor_vstart,100 +672,AcidPump8M,kreg_step_motor_a1,50 +673,AcidPump8M,kreg_step_motor_amax,100 +674,AcidPump8M,kreg_step_motor_v1,300 +675,AcidPump8M,kreg_step_motor_dmax,100 +676,AcidPump8M,kreg_step_motor_d1,50 +677,AcidPump8M,kreg_step_motor_vstop,100 +678,AcidPump8M,kreg_step_motor_tzerowait,0 +679,AcidPump8M,kreg_step_motor_enc_resolution,0 +680,AcidPump8M,kreg_step_motor_enable_enc,0 +681,AcidPump8M,kreg_step_motor_dzero_pos,0 +682,AcidPump8M,kret_step_motor_pos_devi_tolerance,0 +683,AcidPump8M,kret_step_motor_io_trigger_append_distance,0 +684,MainXSV,kreg_leisai_servo_default_velocity,2500 +685,MainXSV,kreg_leisai_servo_low_velocity,2500 +686,MainXSV,kreg_leisai_servo_mid_velocity,2500 +687,MainXSV,kreg_leisai_servo_high_velocity,2500 +688,MainYSV,kreg_leisai_servo_default_velocity,1500 +689,MainYSV,kreg_leisai_servo_low_velocity,1500 +690,MainYSV,kreg_leisai_servo_mid_velocity,1500 +691,MainYSV,kreg_leisai_servo_high_velocity,1500 \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..0e6c853 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,58 @@ + + + + + + + + + + ${LOG_PATTERN} + + + + + + ${LOG_PATH}/sys-info.log + + ${LOG_PATH}/history/info/sys-info.%d{yyyy-MM-dd}.%i.log.zip + 20MB + 7 + 200MB + + + ${LOG_PATTERN} + + + INFO + + + + + + ${LOG_PATH}/sys-error.log + + ${LOG_PATH}/history/error/sys-error.%d{yyyy-MM-dd}.%i.log.zip + 20MB + 7 + 200MB + + + ${LOG_PATTERN} + + + ERROR + + + + + + + + + + + + + + diff --git a/src/main/resources/sql/init.sql b/src/main/resources/sql/init.sql new file mode 100644 index 0000000..1b168a7 --- /dev/null +++ b/src/main/resources/sql/init.sql @@ -0,0 +1,164 @@ +-- 用户 表 +CREATE TABLE IF NOT EXISTS user +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE, + nickname TEXT, + password TEXT, + role TEXT, + fixed_user TEXT, + deleted TEXT, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT OR IGNORE INTO user (username, nickname, password, role, fixed_user, deleted) +VALUES ('admin', 'Admin', '123456', 'ADMIN', 'ENABLE', 'DISABLE'), + ('test', 'test', '123456', 'ADMIN', 'ENABLE', 'DISABLE'); + + +-- ores 矿石 表 +CREATE TABLE IF NOT EXISTS ores +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + + +-- crafts 工艺 表 +CREATE TABLE IF NOT EXISTS crafts +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR NOT NULL, + steps TEXT, + ores_id INTEGER, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + + +-- container 容器 表 +CREATE TABLE IF NOT EXISTS container +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type INTEGER, + code TEXT, + solution_id INTEGER, + pump_id TEXT, + capacity_total INTEGER, + capacity_used INTEGER, + filled REAL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT OR IGNORE INTO container (id, type, code, solution_id, pump_id, capacity_total, capacity_used) +VALUES (1, 0, 'container_01', 1, 'acid_pump_01', 5000, 0), + (2, 0, 'container_02', 2, 'acid_pump_02', 5000, 2500), + (3, 0, 'container_03', 3, 'acid_pump_03', 5000, 2600), + (4, 0, 'container_04', 4, 'acid_pump_04', 5000, 4000), + (5, 0, 'container_05', 5, 'acid_pump_05', 5000, 2400), + (6, 0, 'container_06', 6, 'acid_pump_06', 5000, 4500), + (7, 0, 'container_07', 7, 'acid_pump_07', 5000, 4900), + (8, 0, 'container_08', 3, 'acid_pump_08', 5000, 100), + (9, 1, 'container_09', NULL, null, 5000, 0); + + +-- solutions 溶液 表 +CREATE TABLE IF NOT EXISTS solutions +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT OR IGNORE INTO solutions (id, name, create_time, update_time) +VALUES (1, '硫酸', '2025-02-18 02:44:07', '2025-02-18 02:44:07'), + (2, '盐酸', '2025-02-18 02:44:07', '2025-02-18 02:44:07'), + (3, '硝酸', '2025-02-18 02:44:07', '2025-02-18 02:44:07'), + (4, '氢氟酸', '2025-02-18 02:46:23', '2025-02-18 02:46:23'), + (5, '过氧酸', '2025-02-18 02:46:35', '2025-02-18 02:46:35'), + (6, '磷酸', '2025-02-18 02:46:43', '2025-02-18 02:46:43'), + (7, '纯水', '2025-02-18 02:46:50', '2025-02-18 02:46:50'); + +-- 设备配置 表 +CREATE TABLE IF NOT EXISTS device_config +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + device TEXT, + regIndex TEXT, + valueType TEXT, + value TEXT, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 设备位置 表 +CREATE TABLE IF NOT EXISTS device_position +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + code TEXT, + type TEXT, + position TEXT, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 系统配置 表 +CREATE TABLE IF NOT EXISTS system_config +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key TEXT NOT NULL UNIQUE, + value TEXT, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 系统日志 表 +CREATE TABLE IF NOT EXISTS system_log +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 实验 表 +CREATE TABLE IF NOT EXISTS tasks +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR NOT NULL, + start_time TIMESTAMP, + end_time TIMESTAMP, + status INTEGER, + create_user INTEGER, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_deleted INTEGER +); + +-- 实验步骤记录 表 +CREATE TABLE IF NOT EXISTS task_steps +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id INTEGER, + step_description TEXT, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 设备参数 表 +CREATE TABLE IF NOT EXISTS device_param_config +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + mid text, + reg_index text, + reg_val INTEGER, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP + +) \ No newline at end of file diff --git a/src/main/resources/static/favicon.ico b/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..cd20d7743824aded4ac62c531cf7b7fb5f75e75a GIT binary patch literal 5648 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 literal 0 HcmV?d00001