// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.components.runtime; import com.google.appinventor.components.annotations.DesignerComponent; import com.google.appinventor.components.annotations.DesignerProperty; import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleFunction; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.runtime.util.ErrorMessages; import java.util.ArrayList; import java.util.List; // TODO(lizlooney) - We need to document what configuration of robot this component will work // with. /** * A component that provides a high-level interface to a LEGO MINDSTORMS NXT * robot, with functions that can move and turn the robot. * * @author lizlooney@google.com (Liz Looney) */ @DesignerComponent(version = YaVersion.NXT_DRIVE_COMPONENT_VERSION, description = "A component that provides a high-level interface to a LEGO MINDSTORMS NXT " + "robot, with functions that can move and turn the robot.", category = ComponentCategory.LEGOMINDSTORMS, nonVisible = true, iconName = "images/legoMindstormsNxt.png") @SimpleObject public class NxtDrive extends LegoMindstormsNxtBase { // Constants for setOutputState parameters. private static final int MODE_MOTORON = 0x01; private static final int MODE_BRAKE = 0x02; private static final int MODE_REGULATED = 0x04; private static final int REGULATION_MODE_IDLE = 0x00; private static final int REGULATION_MODE_MOTOR_SPEED = 0x01; private static final int REGULATION_MODE_MOTOR_SYNC = 0x02; private static final int MOTOR_RUN_STATE_IDLE = 0x00; private static final int MOTOR_RUN_STATE_RAMPUP = 0x10; private static final int MOTOR_RUN_STATE_RUNNING = 0x20; private static final int MOTOR_RUN_STATE_RAMPDOWN = 0x40; private String driveMotors; private List<Integer> driveMotorPorts; private double wheelDiameter; private boolean stopBeforeDisconnect; /** * Creates a new NxtDrive component. */ public NxtDrive(ComponentContainer container) { super(container, "NxtDrive"); DriveMotors("CB"); // C & B are the left & right drive motors of the ShooterBot robot. WheelDiameter(4.32f); StopBeforeDisconnect(true); } @Override public void beforeDisconnect(BluetoothConnectionBase bluetoothConnection) { if (stopBeforeDisconnect) { for (int port : driveMotorPorts) { setOutputState("Disconnect", port, 0, MODE_BRAKE, REGULATION_MODE_IDLE, 0, MOTOR_RUN_STATE_IDLE, 0); } } } /** * Returns the motor ports that are used for driving. */ @SimpleProperty(description = "The motor ports that are used for driving: the left wheel's " + "motor port followed by the right wheel's motor port.", category = PropertyCategory.BEHAVIOR, userVisible = false) public String DriveMotors() { return driveMotors; } /** * Specifies the motor ports that are used for driving. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "CB") @SimpleProperty public void DriveMotors(String motorPortLetters) { driveMotors = motorPortLetters; driveMotorPorts = new ArrayList<Integer>(); for (int i = 0; i < motorPortLetters.length(); i++) { char ch = motorPortLetters.charAt(i); try { driveMotorPorts.add(convertMotorPortLetterToNumber(ch)); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, "DriveMotors", ErrorMessages.ERROR_NXT_INVALID_MOTOR_PORT, ch); } } } /** * Returns the diameter of the wheels used for driving. */ @SimpleProperty(description = "The diameter of the wheels used for driving.", category = PropertyCategory.BEHAVIOR, userVisible = false) public float WheelDiameter() { return (float) wheelDiameter; } /** * Returns the diameter of the wheels used for driving. * * @param wheelDiameter the diameter of the wheel */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_FLOAT, defaultValue = "4.32") @SimpleProperty public void WheelDiameter(float wheelDiameter) { this.wheelDiameter = wheelDiameter; } /** * Returns whether to stop the drive motors before disconnecting. */ @SimpleProperty(description = "Whether to stop the drive motors before disconnecting.", category = PropertyCategory.BEHAVIOR) public boolean StopBeforeDisconnect() { return stopBeforeDisconnect; } /** * Specifies whether to stop the drive motors before disconnecting. * * @param stopBeforeDisconnect whether to stop the drive motors before disconnecting */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "True") @SimpleProperty public void StopBeforeDisconnect(boolean stopBeforeDisconnect) { this.stopBeforeDisconnect = stopBeforeDisconnect; } @SimpleFunction(description = "Move the robot forward indefinitely, with the " + "specified percentage of maximum power, by powering both drive motors forward.") public void MoveForwardIndefinitely(int power) { move("MoveForwardIndefinitely", power, 0L); } @SimpleFunction(description = "Move the robot backward indefinitely, with the " + "specified percentage of maximum power, by powering both drive motors backward.") public void MoveBackwardIndefinitely(int power) { move("MoveBackwardIndefinitely", -power, 0L); } @SimpleFunction(description = "Move the robot forward the given distance, with the " + "specified percentage of maximum power, by powering both drive motors forward.") public void MoveForward(int power, double distance) { long tachoLimit = (long) (360.0 * distance / (wheelDiameter * Math.PI)); // This doesn't work accurately, but it is the best we can do with bluetooth direct commands. move("MoveForward", power, tachoLimit); } @SimpleFunction(description = "Move the robot backward the given distance, with the " + "specified percentage of maximum power, by powering both drive motors backward.") public void MoveBackward(int power, double distance) { long tachoLimit = (long) (360.0 * distance / (wheelDiameter * Math.PI)); // This doesn't work accurately, but it is the best we can do with bluetooth direct commands. move("MoveBackward", -power, tachoLimit); } private void move(String functionName, int power, long tachoLimit) { if (!checkBluetooth(functionName)) { return; } for (int port : driveMotorPorts) { setOutputState(functionName, port, power, MODE_MOTORON, REGULATION_MODE_MOTOR_SPEED, 0, MOTOR_RUN_STATE_RUNNING, tachoLimit); } } @SimpleFunction(description = "Turn the robot clockwise indefinitely, with the specified " + "percentage of maximum power, by powering the left drive motor forward and the right " + "drive motor backward.") public void TurnClockwiseIndefinitely(int power) { int numDriveMotors = driveMotorPorts.size(); if (numDriveMotors >= 2) { int forwardMotorIndex = 0; // left int backwardMotorIndex = numDriveMotors - 1; // right turnIndefinitely("TurnClockwiseIndefinitely", power, forwardMotorIndex, backwardMotorIndex); } } @SimpleFunction(description = "Turn the robot counterclockwise indefinitely, with the " + "specified percentage of maximum power, by powering the right drive motor forward and " + "the left drive motor backward.") public void TurnCounterClockwiseIndefinitely(int power) { int numDriveMotors = driveMotorPorts.size(); if (numDriveMotors >= 2) { int forwardMotorIndex = numDriveMotors - 1; // right int backwardMotorIndex = 0; // left turnIndefinitely("TurnCounterClockwiseIndefinitely", power, forwardMotorIndex, backwardMotorIndex); } } private void turnIndefinitely(String functionName, int power, int forwardMotorIndex, int reverseMotorIndex) { if (!checkBluetooth(functionName)) { return; } setOutputState(functionName, driveMotorPorts.get(forwardMotorIndex), power, MODE_MOTORON, REGULATION_MODE_MOTOR_SPEED, 0, MOTOR_RUN_STATE_RUNNING, 0); setOutputState(functionName, driveMotorPorts.get(reverseMotorIndex), -power, MODE_MOTORON, REGULATION_MODE_MOTOR_SPEED, 0, MOTOR_RUN_STATE_RUNNING, 0); } // TODO(lizlooney) - it would be nice to have TurnClockwise and TurnCounterClockwise (or // TurnRight and TurnLeft?) methods that take an angle (in degrees). I think we'd need to know // the distance between the drive wheels, so that could be a property (similar to // the WheelDiameter property). @SimpleFunction(description = "Stop the drive motors of the robot.") public void Stop() { String functionName = "Stop"; if (!checkBluetooth(functionName)) { return; } for (int port : driveMotorPorts) { setOutputState(functionName, port, 0, MODE_BRAKE, REGULATION_MODE_IDLE, 0, MOTOR_RUN_STATE_IDLE, 0); } } }