Driver station control
We want to do one final thing before we wrap up. It would be really cool if we could control the robot by clicking on the field map on the driver station. As it turns out, the Robot Mapper program is capable of sending two different commands to the robot:
p x y g
The first command consists of the letter ‘p’ followed by and X and Y position (in inches). This command will be sent to the robot whenever you click on the map. We will use this position and the RotateToPosition and DriveToPosition commands to make the robot move to that location.
The second command consists of only the letter ‘g’ which will be sent when the Reset button on the mapper program is pressed. We will use this command to reset the gyro. The gyro we are using is fairly stable, but it does have some drift which causes it to become increasingly off over time. We can re-register the gyro position by aligning the robot so that it is exactly facing the tower, and pressing the Reset button.
Now in our Navigation class we already have a function ProcessData that will be called when the class receives data from the driver station. All we need to do is add a couple of cases to handle the data:
@Override public void ProcessData(String command) { Logger.Log("Navigator", 0, String.format("ProcessData: %s", command)); switch (command.charAt(0)) { case 'd': Drive(command.substring(1).trim()); break; case 'g': ResetGyro(); break; } }
The Drive function will handle the position command. It will need to parse the X and Y positions and then create and call the proper RotateToPointCommand and DriveToPointCommand commands.
private void Drive(String args) { double a[] = ParseDoubles(args, 2); if (a != null) { CommandGroup group = new CommandGroup("DriveToPoint"); group.addSequential(new RotateToPointCommand(a[0], a[1])); group.addSequential(new DriveToPointCommand(a[0], a[1])); group.start(); } }
Note that we are again creating a CommandGroup and adding the two needed commands to rotate and then drive. We then can start this command immediately by calling the start() function of the CommandGroup.
Also, because the X and Y values of this command are floating point numbers, we cannot use the ParseIntegers function we used before, so we must create a new ParseDoubles function along the same line:
private double[] ParseDoubles(String str, int count) { double[] args = new double[count]; int i = 0; String[] tokens = str.trim().split(" "); for (String token : tokens) { try { args[i] = Double.parseDouble(token); } catch (NumberFormatException nfe) { break; } if (++i >= count) { break; } } if (i == count) { return(args); } return(null); }
Finally, for the reset gyro command we create:
private void ResetGyro() { m_gyro.Reset(); }
Your Navigator.java file should now look like:
package robot; import commands.DriveToPointCommand; import commands.RotateToPointCommand; import robot.PiCamera.PiCameraData; import robotCore.Encoder; import robotCore.Gyro; import robotCore.Logger; import robotCore.Network; import robotCore.Network.NetworkReceiver; import wpilib.CommandGroup; import robotCore.RobotBase; public class Navigator implements NetworkReceiver { private static final int k_networkPort = 5803; private static final double k_ticksPerInch = 8000.0/38.5; private Gyro m_gyro = new Gyro(); private RobotPosition m_position = null; private Encoder m_leftEncoder = null; private Encoder m_rightEncoder = null; private int m_lastLeftEncoder = 0; private int m_lastRightEncoder = 0; private Network m_network = null; public class RobotPosition { public double m_x; public double m_y; public double m_yaw; RobotPosition(double x, double y, double yaw) { m_x = x; m_y = y; m_yaw = yaw; } } public RobotPosition GetPosition() { synchronized(this) { return(m_position); } } private boolean IsTargetVisible(PiCameraData data) { return( (data.m_y1 > 2) && (data.m_y2 > 2) && (data.m_x > 2) && ((data.m_x + data.m_width) < 638)); } private void Update() { PiCameraData data = Robot.m_piCamera.GetCameraData(); int yaw = m_gyro.GetYaw(); int leftEncoder = m_leftEncoder.get(); int rightEncoder = m_rightEncoder.get(); if ((data != null) && IsTargetVisible(data)) { double top = (data.m_y1 + data.m_y2) / 2; double center = (data.m_x + (data.m_width / 2)); double angleToCenter = 0.1014*center - 34.061; double distance = (0.000271*top*top + 0.0146*top + 16.64) * (268.46 / (268.46 - 0.0577*angleToCenter*angleToCenter)); double angle = yaw + angleToCenter; double x = -distance * Math.sin(angle * Math.PI/180); double y = distance * Math.cos(angle * Math.PI/180); synchronized(this) { m_position = new RobotPosition(x, y, yaw); } m_network.SendMessage(String.format("c %.1f %.2f %.2f", m_position.m_yaw, m_position.m_x, m_position.m_y)); } else if (m_position != null) { double deltaLeft = (leftEncoder - m_lastLeftEncoder) / k_ticksPerInch; double deltaRight = (rightEncoder - m_lastRightEncoder) / k_ticksPerInch; double distance = (deltaLeft + deltaRight) / 2; if (distance != 0) { double radians = (m_position.m_yaw * Math.PI) / 180; double deltaX = distance * Math.sin(radians); double deltaY = distance * Math.cos(radians); synchronized(this) { m_position = new RobotPosition(m_position.m_x + deltaX, m_position.m_y - deltaY, yaw); } m_network.SendMessage(String.format("p %.1f %.2f %.2f", m_position.m_yaw, m_position.m_x, m_position.m_y)); } } m_lastLeftEncoder = leftEncoder; m_lastRightEncoder = rightEncoder; Robot.Sleep(50); } public void Init() { m_gyro.Reset(); m_leftEncoder = Robot.m_driveSubsystem.GetLeftEncoder().Clone(); m_rightEncoder = Robot.m_driveSubsystem.GetRightEncoder().Clone(); RobotBase.Schedule(new Runnable() { @Override public void run() { Update(); } }, 50); m_network = new Network(); m_network.Listen(this, k_networkPort); } private double[] ParseDoubles(String str, int count) { double[] args = new double[count]; int i = 0; String[] tokens = str.trim().split(" "); for (String token : tokens) { try { args[i] = Double.parseDouble(token); } catch (NumberFormatException nfe) { break; } if (++i >= count) { break; } } if (i == count) { return(args); } return(null); } private void Drive(String args) { double a[] = ParseDoubles(args, 2); if (a != null) { CommandGroup group = new CommandGroup("DriveToPoint"); group.addSequential(new RotateToPointCommand(a[0], a[1])); group.addSequential(new DriveToPointCommand(a[0], a[1])); group.start(); } } private void ResetGyro() { m_gyro.Reset(); } @Override public void ProcessData(String command) { Logger.Log("Navigator", 0, String.format("ProcessData: %s", command)); switch (command.charAt(0)) { case 'd': Drive(command.substring(1).trim()); break; case 'g': ResetGyro(); break; } } public static double NormalizeAngle(double angle) { if (angle > 180) { angle -= 360; } else if (angle <= -180) { angle += 360; } return(angle); } }
Now deploy and run your program and verify that you can click on the map to make your robot move to that point. Also, line the robot up with the tower and press the Reset button on the mapper program and see that the gyro value is reset.
This concludes our tutorial.