package lejos.robotics.proposal; import java.util.ArrayList; import lejos.nxt.Battery; import lejos.robotics.MoveListener; import lejos.robotics.Movement; import lejos.robotics.TachoMotor; /* * WARNING: THIS CLASS IS SHARED BETWEEN THE classes AND pccomms PROJECTS. * DO NOT EDIT THE VERSION IN pccomms AS IT WILL BE OVERWRITTEN WHEN THE PROJECT IS BUILT. */ /** * The DifferentialPilot class is a software abstraction of the Pilot mechanism of a * NXT robot. It contains methods to control robot movements: travel forward or * backward in a straight line or a circular path or rotate to a new direction.<br> * Note: this class will only work with two independently controlled motors to * steer differentially, so it can rotate within its own footprint (i.e. turn on * one spot).<br> * It can be used with robots that have reversed motor design: the robot moves * in the direction opposite to the the direction of motor rotation. Uses the * TachoMotor class, which regulates motor speed using the NXT motor's built in * tachometer.<br> * It automatically updates the Pose of a robot if the Pose calls the * addMoveListener() method on this class. * Some methods optionally return immediately so the thread that called the * method can monitor sensors, get current pose, and call stop() if necessary.<br> * Example: * <p> * <code><pre> * PdifferentialPilot pilot = new DifferentialPilot(2.1f, 4.4f, Motor.A, Motor.C, true); // parameters in inches * pilot.setRobotSpeed(10); // inches per second * pilot.travel(12); // inches * pilot.rotate(-90); // degree clockwise * pilot.travel(-12,true); * while(pilot.isMoving())Thread.yield(); * pilot.rotate(-90); * pilot.rotateTo(270); * pilot.steer(-50,180,true); * while(pilot.isMoving())Thread.yield(); * pilot.steer(100); * try{Thread.sleep(1000);} * catch(InterruptedException e){} * pilot.stop(); * </pre></code> * </p> * **/ public class DifferentialPilot implements ArcRotatePilot { /** * Allocates a TachoPilot object, and sets the physical parameters of the * NXT robot.<br> * Assumes Motor.forward() causes the robot to move forward. * * @param wheelDiameter * Diameter of the tire, in any convenient units (diameter in mm * is usually printed on the tire). * @param trackWidth * Distance between center of right tire and center of left tire, * in same units as wheelDiameter. * @param leftMotor * The left Motor (e.g., Motor.C). * @param rightMotor * The right Motor (e.g., Motor.A). */ public DifferentialPilot(final float wheelDiameter, final float trackWidth, final TachoMotor leftMotor, final TachoMotor rightMotor) { this(wheelDiameter, trackWidth, leftMotor, rightMotor, false); } /** * Allocates a TachoPilot object, and sets the physical parameters of the * NXT robot.<br> * * @param wheelDiameter * Diameter of the tire, in any convenient units (diameter in mm * is usually printed on the tire). * @param trackWidth * Distance between center of right tire and center of left tire, * in same units as wheelDiameter. * @param leftMotor * The left Motor (e.g., Motor.C). * @param rightMotor * The right Motor (e.g., Motor.A). * @param reverse * If true, the NXT robot moves forward when the motors are * running backward. */ public DifferentialPilot(final float wheelDiameter, final float trackWidth, final TachoMotor leftMotor, final TachoMotor rightMotor, final boolean reverse) { this(wheelDiameter, wheelDiameter, trackWidth, leftMotor, rightMotor, reverse); } /** * Allocates a TachoPilot object, and sets the physical parameters of the * NXT robot.<br> * * @param leftWheelDiameter * Diameter of the left wheel, in any convenient units (diameter * in mm is usually printed on the tire). * @param rightWheelDiameter * Diameter of the right wheel. You can actually fit * intentionally wheels with different size to your robot. If you * fitted wheels with the same size, but your robot is not going * straight, try swapping the wheels and see if it deviates into * the other direction. That would indicate a small difference in * wheel size. Adjust wheel size accordingly. The minimum change * in wheel size which will actually have an effect is given by * minChange = A*wheelDiameter*wheelDiameter/(1-(A*wheelDiameter) * where A = PI/(moveSpeed*360). Thus for a moveSpeed of 25 * cm/second and a wheelDiameter of 5,5 cm the minChange is about * 0,01058 cm. The reason for this is, that different while sizes * will result in different motor speed. And that is given as an * integer in degree per second. * @param trackWidth * Distance between center of right tire and center of left tire, * in same units as wheelDiameter. * @param leftMotor * The left Motor (e.g., Motor.C). * @param rightMotor * The right Motor (e.g., Motor.A). * @param reverse * If true, the NXT robot moves forward when the motors are * running backward. */ public DifferentialPilot(final float leftWheelDiameter, final float rightWheelDiameter, final float trackWidth, final TachoMotor leftMotor, final TachoMotor rightMotor, final boolean reverse) { _left = leftMotor; _leftWheelDiameter = leftWheelDiameter; _leftTurnRatio = trackWidth / leftWheelDiameter; _leftDegPerDistance = 360 / ((float) Math.PI * leftWheelDiameter); // right _right = rightMotor; _rightWheelDiameter = rightWheelDiameter; _rightTurnRatio = trackWidth / rightWheelDiameter; _rightDegPerDistance = 360 / ((float) Math.PI * rightWheelDiameter); // both _trackWidth = trackWidth; _parity = (byte) (reverse ? -1 : 1); setSpeed(360); monitor.setDaemon(true); monitor.start(); } public void addMoveListener(MoveListener aListener) { listeners.add(aListener); } /** * @return left motor. */ public TachoMotor getLeft() { return _left; } /** * @return right motor. */ public TachoMotor getRight() { return _right; } /** * @return tachoCount of left motor. Positive value means motor has moved * the robot forward. */ public int getLeftCount() { return _parity * _left.getTachoCount(); } /** * @return tachoCount of the right motor. Positive value means motor has * moved the robot forward. */ public int getRightCount() { return _parity * _right.getTachoCount(); } /** * @return actual speed of left motor in degrees per second. A negative * value if motor is rotating backwards. Updated every 100 ms. **/ public int getLeftActualSpeed() { return _left.getRotationSpeed(); } /** * @return actual speed of right motor in degrees per second. A negative * value if motor is rotating backwards. Updated every 100 ms. **/ public int getRightActualSpeed() { return _right.getRotationSpeed(); } /** * @return ratio of motor revolutions per 360 degree rotation of the robot. * If your robot has wheels with different size, it is the average. */ public float getTurnRatio() { return (_leftTurnRatio + _rightTurnRatio) / 2.0f; } /** * Sets speed of both motors, as well as moveSpeed and turnSpeed. Only use * if your wheels have the same size. * * @param speed * The wanted speed in degrees per second. */ public void setSpeed(final int speed) { _motorSpeed = speed; _robotMoveSpeed = speed / Math.max(_leftDegPerDistance, _rightDegPerDistance); _robotTurnSpeed = speed / Math.max(_leftTurnRatio, _rightTurnRatio); setSpeed(speed, speed); } protected void setSpeed(final int leftSpeed, final int rightSpeed) { _left.regulateSpeed(_regulating); _left.smoothAcceleration(!isMoving()); _right.regulateSpeed(_regulating); _right.smoothAcceleration(!isMoving()); _left.setSpeed(leftSpeed); _right.setSpeed(rightSpeed); } /** * also sets _motorSpeed * * @see lejos.robotics.navigation.Pilot#setMoveSpeed(float) */ public void setMoveSpeed(float speed) { _robotMoveSpeed = speed; _motorSpeed = Math.round(0.5f * speed * (_leftDegPerDistance + _rightDegPerDistance)); setSpeed(Math.round(speed * _leftDegPerDistance), Math.round(speed * _rightDegPerDistance)); } /** * @see lejos.robotics.navigation.Pilot#getMoveSpeed() */ public float getMoveSpeed() { return _robotMoveSpeed; } /** * @see lejos.robotics.navigation.Pilot#getMoveMaxSpeed() */ public float getMoveMaxSpeed() { // it is generally assumed, that the maximum accurate speed of Motor is // 100 degree/second * Voltage return Battery.getVoltage() * 100.0f / Math.max(_leftDegPerDistance, _rightDegPerDistance); // max degree/second divided by degree/unit = unit/second } /** * @see lejos.robotics.navigation.Pilot#setTurnSpeed(float) */ public void setTurnSpeed(float speed) { _robotTurnSpeed = speed; setSpeed(Math.round(speed * _leftTurnRatio), Math.round(speed * _rightTurnRatio)); } /** * @see lejos.robotics.navigation.Pilot#getTurnSpeed() */ public float getTurnSpeed() { return _robotTurnSpeed; } /** * @see lejos.robotics.navigation.Pilot#getTurnMaxSpeed() */ public float getTurnMaxSpeed() { // it is generally assumed, that the maximum accurate speed of Motor is // 100 degree/second * Voltage return Battery.getVoltage() * 100.0f / Math.max(_leftTurnRatio, _rightTurnRatio); // max degree/second divided by degree/unit = unit/second } /** * @return true if the NXT robot is moving. **/ public boolean isMoving() { return _left.isMoving() || _right.isMoving(); // ||_left.isRotating()||_right.isRotating();//ensure that motor.rotate() is complete } /** * @return distance traveled since last movement started. **/ public float getTravelDistance() { float left = _left.getTachoCount() / _leftDegPerDistance; float right = _right.getTachoCount() / _rightDegPerDistance; return _parity * (left + right) / 2.0f; } /** * @return the angle of rotation of the robot since last started * tacho count; */ public float getAngle() { float angle = _parity * ((_right.getTachoCount() / _rightTurnRatio) - (_left.getTachoCount() / _leftTurnRatio)) / 2.0f; return angle; } /** * Resets tacho count for both motors. **/ public void reset() { _left.resetTachoCount(); _right.resetTachoCount(); } /** * Moves the NXT robot forward until stop() is called. */ public void forward() { if(isMoving())stop(); _moveType = Movement.MovementType.TRAVEL; movementStart(); reset(); setSpeed(Math.round(_robotMoveSpeed * _leftDegPerDistance), Math.round(_robotMoveSpeed * _rightDegPerDistance)); if (_parity == 1) fwd(); else bak(); } /** * Moves the NXT robot backward until stop() is called. */ public void backward() { if (isMoving()) stop(); _moveType = Movement.MovementType.TRAVEL; movementStart(); reset(); setSpeed(Math.round(_robotMoveSpeed * _leftDegPerDistance), Math.round(_robotMoveSpeed * _rightDegPerDistance)); if (_parity == 1) bak(); else fwd(); } /** * Motors forward. This is called by forward() and backward(). */ private void fwd() { _left.forward(); _right.forward(); } /** * Motors backward. This is called by forward() and backward(). */ private void bak() { _left.backward(); _right.backward(); } /** * Rotates the NXT robot through a specific angle. Returns when angle is * reached. Wheels turn in opposite directions producing a zero radius turn.<br> * Note: Requires correct values for wheel diameter and track width. * * @param angle * The wanted angle of rotation in degrees. Positive angle rotate * left (clockwise), negative right. */ public Movement rotate(final float angle) { return rotate(angle, false); } /** * Rotates the NXT robot through a specific angle. Returns when angle is * reached. Wheels turn in opposite directions producing a zero radius turn.<br> * Note: Requires correct values for wheel diameter and track width. * * @param angle * The wanted angle of rotation in degrees. Positive angle rotate * left (clockwise), negative right. * @param immediateReturn * If true this method returns immediately. */ public Movement rotate(final float angle, final boolean immediateReturn) { if(isMoving())stop(); _moveType = Movement.MovementType.ROTATE; reset(); movementStart(); _alert = immediateReturn; setSpeed(Math.round(_robotTurnSpeed * _leftTurnRatio), Math.round(_robotTurnSpeed * _rightTurnRatio)); int rotateAngleLeft = _parity * (int) (angle * _leftTurnRatio); int rotateAngleRight = _parity * (int) (angle * _rightTurnRatio); _left.rotate(-rotateAngleLeft, true); _right.rotate(rotateAngleRight, immediateReturn); if (!immediateReturn) { while (isMoving()) Thread.yield(); stop(); } return getMovement(); } /** * This method can be overridden by subclasses to stop the robot if a hazard * is detected * * @return true iff no hazard is detected */ protected boolean continueMoving() { return true; } /** * Stops the NXT robot. */ public Movement stop() { _alert = false; _left.stop(); _right.stop(); while (isMoving()) Thread.yield(); movementStop(); return getMovement(); } /** * Moves the NXT robot a specific distance in an (hopefully) straight line.<br> * A positive distance causes forward motion, a negative distance moves * backward. If a drift correction has been specified in the constructor it * will be applied to the left motor. * * @param distance * The distance to move. Unit of measure for distance must be * same as wheelDiameter and trackWidth. **/ public Movement travel(final float distance) { return travel(distance, false); } /** * Moves the NXT robot a specific distance in an (hopefully) straight line.<br> * A positive distance causes forward motion, a negative distance moves * backward. If a drift correction has been specified in the constructor it * will be applied to the left motor. * * @param distance * The distance to move. Unit of measure for distance must be * same as wheelDiameter and trackWidth. * @param immediateReturn * If true this method returns immediately. */ public Movement travel(final float distance, final boolean immediateReturn) { if(isMoving())stop(); _moveType = Movement.MovementType.TRAVEL; reset(); movementStart(); _alert = immediateReturn; setSpeed(Math.round(_robotMoveSpeed * _leftDegPerDistance), Math.round(_robotMoveSpeed * _rightDegPerDistance)); _left.rotate((int) (_parity * distance * _leftDegPerDistance), true); _right.rotate((int) (_parity * distance * _rightDegPerDistance), true); _alert = immediateReturn; if (!immediateReturn) { while (isMoving() && continueMoving()) Thread.yield(); stop(); } return getMovement(); } public void steer(final float turnRate) { steer(turnRate, Float.POSITIVE_INFINITY, true); } public Movement steer(final float turnRate, float angle) { return steer(turnRate, angle, false); } public Movement steer(final float turnRate, final float angle, final boolean immediateReturn) { if(isMoving())stop(); _moveType = Movement.MovementType.ARC; reset(); movementStart(); _alert = immediateReturn; // TODO: make this work with wheels of different size TachoMotor inside; TachoMotor outside; float rate = turnRate; if (rate < -200) rate = -200; else if (rate > 200)rate = 200; else if (rate == 0) { if (angle < 0) backward(); else forward(); return getMovement(); } if (turnRate < 0) { inside = _right; outside = _left; rate = -rate; } else { inside = _left; outside = _right; } outside.setSpeed(_motorSpeed); float steerRatio = 1 - rate / 100.0f; inside.setSpeed((int) (_motorSpeed * steerRatio)); if (angle == Float.POSITIVE_INFINITY) // no limit angle for turn { if (_parity == 1) outside.forward(); else outside.backward(); if (_parity * steerRatio > 0) { inside.forward(); } else { inside.backward(); } return getMovement(); } // end no turn limit float rotAngle = angle * _trackWidth * 2 / (_leftWheelDiameter * (1 - steerRatio)); inside.rotate(_parity * (int) (rotAngle * steerRatio), true); outside.rotate(_parity * (int) rotAngle, true); if (!immediateReturn) { while (isMoving() && continueMoving()) Thread.yield(); stop(); } return getMovement(); } public void arc(final float radius) { steer(turnRate(radius)); } public Movement arc(final float radius, final float angle) { return steer(turnRate(radius), angle); } public Movement arc(final float radius, final float angle, final boolean immediateReturn) { return steer(turnRate(radius), angle, immediateReturn); } public Movement travelArc(float radius, float distance) { return travelArc(radius, distance, false); } public Movement travelArc(float radius, float distance, boolean immediateReturn) { double angle = (distance * 180) / (Math.PI * radius); return arc(radius, (float) angle, immediateReturn); } /** * Calculates the turn rate corresponding to the turn radius; <br> * use as the parameter for steer() negative argument means center of turn * is on right, so angle of turn is negative * * @param radius * @return steer() */ protected int turnRate(final float radius) { int direction; float radiusToUse; if (radius < 0) { direction = -1; radiusToUse = -radius; } else { direction = 1; radiusToUse = radius; } float ratio = (2 * radiusToUse - _trackWidth) / (2 * radiusToUse + _trackWidth); return Math.round(direction * 100 * (1 - ratio)); } protected void movementStart() { for (MoveListener p : listeners) { p.movementStarted(new Movement(_moveType, 0, 0, true ), this); } } protected void movementStop() { for (MoveListener p : listeners) { Movement move = new Movement(_moveType, getTravelDistance(), getAngle(), false); p.movementStopped(move, this); } } public void setMinRadius(float radius){ _minRadius = radius; } public float getMinRadius(){return _minRadius;} public Movement getMovement() { return new Movement(_moveType, getTravelDistance(), getAngle(), isMoving()); } /** * The only purpose of this inner class is to detect when an immediate return * method exits because the distance or angle has been reached. */ private class Monitor extends Thread { public void run() { while (true) { // note: the pilot may stop or _alert may be cancelled by the main // thread at any time. Do not call stopNow if either happens while (!_alert) { Thread.yield();//no movement in progress } while (!isMoving()) { Thread.yield(); } while (isMoving()) { Thread.yield(); } synchronized (monitor) { if (_alert) { _alert = false; movementStop(); // updates listeners } }//end synchronized } } } /** * should be true if an immediate return movement is in progress. * used by monitor */ protected boolean _alert = false; /** * the pilot listeners */ protected ArrayList<MoveListener> listeners = new ArrayList<MoveListener>(); /** * type of the current movement */ protected Movement.MovementType _moveType; private Monitor monitor = new Monitor(); /** * Left motor. */ protected final TachoMotor _left; /** * Right motor. */ protected final TachoMotor _right; /** * Left motor degrees per unit of travel. */ protected final float _leftDegPerDistance; /** * Right motor degrees per unit of travel. */ protected final float _rightDegPerDistance; /** * Left motor revolutions for 360 degree rotation of robot (motors running * in opposite directions). Calculated from wheel diameter and track width. * Used by rotate() and steer() methods. **/ protected final float _leftTurnRatio; /** * Right motor revolutions for 360 degree rotation of robot (motors running * in opposite directions). Calculated from wheel diameter and track width. * Used by rotate() and steer() methods. **/ protected final float _rightTurnRatio; /** * Speed of robot for moving in wheel diameter units per seconds. Set by * setSpeed(), setMoveSpeed() */ protected float _robotMoveSpeed; /** * Speed of robot for turning in degree per seconds. */ protected float _robotTurnSpeed; /** * Motor speed degrees per second. Used by forward(),backward() and steer(). */ protected int _motorSpeed; /** * Motor rotation forward makes robot move forward if parity == 1. */ protected byte _parity; /** * If true, motor speed regulation is turned on. Default = true. */ protected boolean _regulating = true; /** * Distance between wheels. Used in steer() and rotate(). */ protected final float _trackWidth; /** * Diameter of left wheel. */ protected final float _leftWheelDiameter; /** * Diameter of right wheel. */ protected final float _rightWheelDiameter; protected float _minRadius = 0; public float getMovementIncrement() { // TODO Auto-generated method stub return 0; } public float getAngleIncrement() { // TODO Auto-generated method stub return 0; } }