| | |
| | | import com.zy.acs.framework.exception.CoolException; |
| | | import com.zy.acs.manager.common.config.HikOrderProperties; |
| | | import com.zy.acs.manager.core.constant.HikAngleConstant; |
| | | import com.zy.acs.manager.core.service.MapService; |
| | | import com.zy.acs.manager.manager.entity.Action; |
| | | import com.zy.acs.manager.manager.entity.AgvDetail; |
| | | import com.zy.acs.manager.manager.entity.AgvModel; |
| | |
| | | import com.zy.acs.manager.manager.enums.ActionTypeType; |
| | | import com.zy.acs.manager.manager.service.AgvDetailService; |
| | | import com.zy.acs.manager.manager.service.CodeService; |
| | | import com.zy.acs.manager.system.service.ConfigService; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | private CodeService codeService; |
| | | @Autowired |
| | | private AgvDetailService agvDetailService; |
| | | @Autowired |
| | | private ConfigService configService; |
| | | |
| | | private final RedisSupport redis = RedisSupport.defaultRedisSupport; |
| | | |
| | |
| | | int edgeIndex = 0; |
| | | |
| | | NodeCursor currentNode = createNode(actionGroupId, firstAction.getCode(), nodeIndex++, sequenceId++); |
| | | Double currentDirection = resolveInitialNodeDirection(firstAction); |
| | | applyNodeTheta(currentNode.node, currentDirection); |
| | | nodes.add(currentNode.node); |
| | | |
| | | Double currentTheta = currentNode.node.getNodePosition().getTheta(); |
| | | for (int i = 0; i < actionList.size(); i++) { |
| | | Action action = actionList.get(i); |
| | | ActionTypeType actionType = resolveActionType(action); |
| | |
| | | |
| | | switch (actionType) { |
| | | case TurnCorner: |
| | | if (i == 0) { |
| | | AgvDetail agvDetail = agvDetailService.selectMajorByAgvId(firstAction.getAgvId()); |
| | | currentTheta = parseThetaRadians(String.valueOf(agvDetail.getAgvAngle())); |
| | | currentNode.node.getNodePosition().setTheta(currentTheta); |
| | | } else { |
| | | currentTheta = parseThetaRadians(action.getParams()); |
| | | currentNode.node.getNodePosition().setTheta(currentTheta); |
| | | } |
| | | currentDirection = parseDirectionDegrees(action.getParams()); |
| | | currentNode.node.getActions().add(buildRotateAgvAction(action, currentDirection)); |
| | | break; |
| | | case StraightAheadTurnable: |
| | | case StraightAheadUnturnable: |
| | | case StraightBackTurnable: |
| | | case StraightBackUnturnable: |
| | | String endCode = findNextMoveEndCode(actionList, i, currentNode.code); |
| | | Double edgeTheta = currentTheta != null ? currentTheta : calculateTheta(currentNode.code, endCode); |
| | | HkOrderEdge edge = createEdge(actionGroupId, edgeIndex++, sequenceId++, currentNode, endCode, edgeTheta, action, agvModel); |
| | | Double travelDirection = calculateTravelDirection(currentNode.code, endCode); |
| | | Double moveDirection = resolveMoveDirection(currentDirection, travelDirection, actionType, actionGroupId, currentNode.code, endCode); |
| | | Double edgeOrientation = resolveEdgeOrientation(moveDirection, travelDirection); |
| | | HkOrderEdge edge = createEdge(actionGroupId, edgeIndex++, sequenceId++, currentNode, endCode, edgeOrientation, action, agvModel); |
| | | edges.add(edge); |
| | | |
| | | NodeCursor nextNode = createNode(actionGroupId, endCode, nodeIndex++, sequenceId++); |
| | | nextNode.node.getNodePosition().setTheta(edgeTheta); |
| | | applyNodeTheta(nextNode.node, moveDirection); |
| | | nodes.add(nextNode.node); |
| | | |
| | | currentNode = nextNode; |
| | | currentTheta = edgeTheta; |
| | | currentDirection = moveDirection; |
| | | break; |
| | | case FinishPath: |
| | | break; |
| | | default: |
| | | currentNode.node.getActions().add(buildNodeAction(action, actionType, currentTheta)); |
| | | currentNode.node.getActions().add(buildNodeAction(action, actionType, toHikThetaRadians(currentDirection))); |
| | | break; |
| | | } |
| | | } |
| | |
| | | long sequenceId, |
| | | NodeCursor startNode, |
| | | String endCode, |
| | | Double edgeTheta, |
| | | Double edgeOrientation, |
| | | Action action, |
| | | AgvModel agvModel) { |
| | | HkOrderEdge edge = new HkOrderEdge(); |
| | |
| | | // edge.setEndNodeId(buildId(actionGroupId, "N", edgeIndex + 1, endCode)); |
| | | edge.setEndNodeId(endCode); |
| | | edge.setMaxSpeed(resolveMaxSpeed(action, agvModel)); |
| | | // GLOBAL |
| | | // TANGENTIAL |
| | | edge.setOrientationType("TANGENTIAL"); |
| | | edge.setOrientation(0.0); |
| | | // edge.setOrientation(edgeTheta); |
| | | edge.setOrientation(edgeOrientation); |
| | | edge.setRotationAllowed(Boolean.FALSE); |
| | | edge.setActions(new ArrayList<>()); |
| | | return edge; |
| | |
| | | return hkAction; |
| | | } |
| | | |
| | | private HkAction buildRotateAgvAction(Action action, Double targetDirection) { |
| | | HkAction hkAction = new HkAction(); |
| | | hkAction.setActionId("A" + action.getId()); |
| | | hkAction.setActionDescription(ActionTypeType.TurnCorner.desc); |
| | | hkAction.setBlockingType(HkBlockingType.HARD); |
| | | hkAction.setActionType(HkActionType.ROTATE_AGV); |
| | | |
| | | List<HkActionParameter> parameters = new ArrayList<>(); |
| | | addParameter(parameters, "sourceActionType", ActionTypeType.TurnCorner.name()); |
| | | addParameter(parameters, "sourceActionId", action.getId()); |
| | | addParameter(parameters, "angle", toHikThetaRadians(targetDirection)); |
| | | hkAction.setActionParameters(parameters); |
| | | return hkAction; |
| | | } |
| | | |
| | | private HkActionType resolveHkActionType(ActionTypeType actionType) { |
| | | switch (actionType) { |
| | | case ReadyTakeFromShelvesLoc: |
| | |
| | | return new HeightDepthParam(heightDepthDto.getHeight(), heightDepthDto.getDepth()); |
| | | } |
| | | |
| | | private Double parseThetaRadians(String params) { |
| | | Double thetaDegrees = parseDouble(params); |
| | | private Double resolveInitialNodeDirection(Action firstAction) { |
| | | if (firstAction == null || firstAction.getAgvId() == null) { |
| | | return null; |
| | | } |
| | | AgvDetail agvDetail = agvDetailService.selectMajorByAgvId(firstAction.getAgvId()); |
| | | if (agvDetail == null || agvDetail.getAgvAngle() == null) { |
| | | return null; |
| | | } |
| | | return MapService.mapToNearest(agvDetail.getAgvAngle()); |
| | | } |
| | | |
| | | private Double parseDirectionDegrees(String params) { |
| | | return parseDouble(params); |
| | | } |
| | | |
| | | private Double toHikThetaRadians(Double thetaDegrees) { |
| | | if (thetaDegrees == null) { |
| | | return null; |
| | | } |
| | | return HikAngleConstant.toHikAngleRadians(Math.toRadians(thetaDegrees)); |
| | | } |
| | | |
| | | private Double calculateTheta(String startCode, String endCode) { |
| | | private void applyNodeTheta(HkOrderNode node, Double directionDegrees) { |
| | | if (node == null || node.getNodePosition() == null) { |
| | | return; |
| | | } |
| | | node.getNodePosition().setTheta(toHikThetaRadians(directionDegrees)); |
| | | } |
| | | |
| | | private Double calculateTravelDirection(String startCode, String endCode) { |
| | | Code start = codeService.getCacheByData(startCode); |
| | | Code end = codeService.getCacheByData(endCode); |
| | | if (start == null || end == null) { |
| | | throw new CoolException("hik order adaptation failed: code position missing"); |
| | | } |
| | | double rcsTheta = Math.atan2(end.getY() - start.getY(), end.getX() - start.getX()); |
| | | return HikAngleConstant.toHikAngleRadians(rcsTheta); |
| | | Integer angleOffsetVal = configService.getVal("mapAngleOffsetVal", Integer.class); |
| | | if (angleOffsetVal == null) { |
| | | angleOffsetVal = 0; |
| | | } |
| | | double deltaX = end.getX() - start.getX(); |
| | | double deltaY = end.getY() - start.getY(); |
| | | double angle = Math.atan2(deltaX, deltaY); |
| | | angle = Math.toDegrees(angle) + angleOffsetVal; |
| | | return normalizeDegrees(angle); |
| | | } |
| | | |
| | | private Double resolveMoveDirection(Double currentDirection, |
| | | Double travelDirection, |
| | | ActionTypeType actionType, |
| | | String actionGroupId, |
| | | String startCode, |
| | | String endCode) { |
| | | Double expectedDirection = inferMoveDirection(travelDirection, actionType); |
| | | if (currentDirection == null) { |
| | | return expectedDirection; |
| | | } |
| | | if (!sameDegrees(currentDirection, expectedDirection)) { |
| | | log.warn("hik order direction adjusted by move semantics, groupId={}, startCode={}, endCode={}, nodeDirection={}, expectedDirection={}, moveType={}", |
| | | actionGroupId, |
| | | startCode, |
| | | endCode, |
| | | currentDirection, |
| | | expectedDirection, |
| | | actionType); |
| | | } |
| | | return expectedDirection; |
| | | } |
| | | |
| | | private Double inferMoveDirection(Double travelDirection, ActionTypeType actionType) { |
| | | if (travelDirection == null) { |
| | | return null; |
| | | } |
| | | switch (actionType) { |
| | | case StraightAheadTurnable: |
| | | case StraightAheadUnturnable: |
| | | return normalizeDegrees(travelDirection); |
| | | case StraightBackTurnable: |
| | | case StraightBackUnturnable: |
| | | return normalizeDegrees(travelDirection + 180D); |
| | | default: |
| | | throw new CoolException("hik order adaptation failed: unsupported move action type=" + actionType); |
| | | } |
| | | } |
| | | |
| | | private Double resolveEdgeOrientation(Double moveDirection, Double travelDirection) { |
| | | if (moveDirection == null || travelDirection == null) { |
| | | return null; |
| | | } |
| | | double delta = normalizeDegrees(moveDirection - travelDirection); |
| | | if (almostZeroOrFull(delta)) { |
| | | return 0.0; |
| | | } |
| | | if (almostEqual(delta, 180D)) { |
| | | return Math.PI; |
| | | } |
| | | throw new CoolException("hik order adaptation failed: move direction is not tangential, moveDirection=" |
| | | + moveDirection + ", travelDirection=" + travelDirection); |
| | | } |
| | | |
| | | private Double normalizeDegrees(Double degrees) { |
| | | if (degrees == null) { |
| | | return null; |
| | | } |
| | | double normalized = degrees % 360D; |
| | | return normalized >= 0 ? normalized : normalized + 360D; |
| | | } |
| | | |
| | | private boolean sameDegrees(Double left, Double right) { |
| | | if (left == null || right == null) { |
| | | return Objects.equals(left, right); |
| | | } |
| | | double delta = normalizeDegrees(left - right); |
| | | return almostZeroOrFull(delta); |
| | | } |
| | | |
| | | private boolean almostZeroOrFull(double value) { |
| | | return almostEqual(value, 0D) || almostEqual(value, 360D); |
| | | } |
| | | |
| | | private boolean almostEqual(double left, double right) { |
| | | return Math.abs(left - right) < 1e-6; |
| | | } |
| | | |
| | | private Double resolveMaxSpeed(Action action, AgvModel agvModel) { |