import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; // Lejos imports import lejos.nxt.*; import lejos.nxt.comm.*; // Collect commands, write to screen public class SynchroCtrl2 { //Defines the buttons private static final Button button_left = Button.LEFT; private static final Button button_right = Button.RIGHT; private static final Button button_enter = Button.ENTER; public static Communicator mainCommunicator; public static KickThread kickThread; public static DriveThread driveThread; public static SteeringThread steeringThread; public static SteeringMotorThread steeringLeftThread; public static SteeringMotorThread steeringRightThread; public static CounterThread counterThread; public static CommandHandler commandHandler; public static SensorChecker sensorCheck; public static void main(String[] args) throws InterruptedException{ commandHandler = new CommandHandler(); mainCommunicator = new Communicator(); kickThread = new KickThread(); driveThread = new DriveThread(); steeringThread = new SteeringThread(); steeringLeftThread = new SteeringLeftThread(); steeringRightThread = new SteeringRightThread(); sensorCheck = new SensorChecker(); //counterThread = new CounterThread(); commandHandler.start(); mainCommunicator.start(); kickThread.start(); driveThread.start(); steeringThread.start(); steeringLeftThread.start(); steeringRightThread.start(); sensorCheck.start(); } } class Movement { //Defines the motors used for steering the right and left wheels public static final Motor motor_left = Motor.A; public static final Motor motor_right = Motor.B; //Defines the motor used for the kicker public static final Motor motor_kick = Motor.C; //Defines the number of motor turns to wheel turns public static final double rotConstant = 56.0 / 24.0; //Defines the sensor port used to power the communication light public static final SensorPort port_comlight = SensorPort.S1; } class CommandHandler extends Thread { public static boolean kick; public static int drive_left; public static int drive_right; public static int steer_angle; public static int command_id; public void setKickState(boolean Val){ SynchroCtrl2.kickThread.setKickState(Val); synchronized (SynchroCtrl2.kickThread) { SynchroCtrl2.kickThread.notify(); } } public void setTargetDriveVal(int left, int right){ SynchroCtrl2.driveThread.setTargetDriveVal(left, right); synchronized (SynchroCtrl2.driveThread) { SynchroCtrl2.driveThread.notify(); } } public void setTargetSteeringAngle(int Angle){ SynchroCtrl2.steeringThread.setTargetSteeringAngle(Angle); synchronized (SynchroCtrl2.steeringThread) { SynchroCtrl2.steeringThread.notify(); } } public void run(){ while(true) { RConsole.println("handler pre-wait"); synchronized (this) { try{ wait(); }catch(InterruptedException e){ } } RConsole.println("handler post-wait"); executeCommand(); } } private void executeCommand() { switch (command_id) { case 1: moveTo(steer_angle, drive_left, true); break; case 2: orientTo(steer_angle); break; case 3: moveTo(steer_angle, drive_left, false); break; default: /* By default, just execute the raw commands: */ int speedL = drive_left * SynchroCtrl2.steeringLeftThread.getDirection(steer_angle); int speedR = drive_right * SynchroCtrl2.steeringRightThread.getDirection(steer_angle); setTargetDriveVal(speedL, speedR); setTargetSteeringAngle(steer_angle); break; } setKickState(kick); } /* Wait until steering has finished. The wheels move at ~2 rad/s ~= 120°/s. */ private void waitForSteering(double deltaD) { final int granularity = 10; int sleep_time; if (deltaD == 0) sleep_time = 1200; else sleep_time = (int)Math.round(1000.0/120.0 * Math.abs(deltaD)); while(sleep_time > 0) { sleep_time -= granularity; synchronized (this) { try{ wait(granularity); }catch(InterruptedException e){ } if (SynchroCtrl2.steeringLeftThread.Ready() && SynchroCtrl2.steeringRightThread.Ready()) { break; } } } } /* Move towards some bearing at positive integer speed */ private void moveTo(int angle, int speed, boolean strict) { setTargetSteeringAngle(angle); double deltaD = SynchroCtrl2.steeringThread.getClosestAngle(angle); final int thresh = 3; if (deltaD > thresh) { setTargetDriveVal(0, 0); if (strict) waitForSteering(3*deltaD); else waitForSteering(deltaD); } int speedL = speed*SynchroCtrl2.steeringLeftThread.getDirection(angle); int speedR = speed*SynchroCtrl2.steeringRightThread.getDirection(angle); final double[] X = { 36.03, 31.30}; int sleep_time = (int)Math.round( X[0]*angle + X[1] ); setTargetDriveVal(speedL, speedR); } private void orientTo(int angle) { setTargetDriveVal(0, 0); setTargetSteeringAngle(135); waitForSteering(0); /* The data below was used to find the polynomial. It was obtained by sleeping (4*angle) ms initially. - 188 turns of 10° turn the robot ~1080° - 46 turns of 20° turn the robot ~730°. - 15 turns of 30° turn the robot ~360°. - 15 turns of 359° make the robot turn very close to 15.5 turns. The best-fitting first-order polynomial for these has the coefficients (from highest to lowest order): */ final double[] X = { 4.066, 31.0}; int sleep_time = (int)Math.round( X[0]*angle + X[1] ); setTargetDriveVal(2, -2); try{ Thread.sleep(sleep_time); } catch (InterruptedException e){ } setTargetDriveVal(0, 0); } } class Communicator extends Thread { // Defines variables used for the managing bluetooth connection private static BTConnection connection; private static DataInputStream inputStream; private static DataOutputStream outputStream; public static boolean isConnected = false; public void run(){ connect(); try{ collectMessage(); } catch (InterruptedException e){ LCD.drawString("Msg Col Interrupt", 0,7); } } //Aims to establish a conection over Bluetooth private void connect(){ LCD.drawString("Trying to connect", 0,7); // Wait until connected connection = Bluetooth.waitForConnection(); LCD.drawString("Connected", 0,7); isConnected = true; inputStream = connection.openDataInputStream(); outputStream = connection.openDataOutputStream(); LCD.drawString("Connection opened", 0,7); } private void collectMessage() throws InterruptedException{ RConsole.openUSB(5); RConsole.println("Start comm. loop"); for (int N = 0 ;; ++N) { try{ //Bluetooth.getConnectionStatus(); LCD.drawString("Recv:"+Integer.toString(N), 8, 2); int message = inputStream.readInt(); LCD.drawString("Rcvd:"+Integer.toString(N), 8, 3); //LCD.drawString("display"+Integer.toString(N), 6, 0); LCD.drawString(" ", 6, 6); LCD.drawString("Msg: "+Integer.toString(message), 0, 6); //LCD.drawString("decode:"+Integer.toString(N), 6, 1); parseMessage(message); //LCD.drawString("decoded:"+Integer.toString(N), 6, 0); } catch (IOException e) { isConnected = false; LCD.drawString("Error: connect back up", 0,7); connection = Bluetooth.waitForConnection(); LCD.drawString("Connection opened", 0,7); } } } //Parses integer messages private void parseMessage(int message){ int reset = message & 1; int kick = (message >>> 1) & 1; int motor_dleft = (message >>> 2) & 7; int motor_dright = (message >>> 5) & 7; int steer_angle = (message >>> 8) & 511; int command_id = (message >>> 17) & 511; int dleft_dir = (motor_dleft & 4) > 0 ? -1 : 1; int drive_left = dleft_dir * (motor_dleft & 3); int dright_dir = (motor_dright & 4) > 0 ? -1 : 1; int drive_right = dright_dir * (motor_dright & 3); RConsole.println("parseMessage"); CommandHandler handler = SynchroCtrl2.commandHandler; synchronized (handler) { handler.kick = kick != 0; handler.drive_left = drive_left; handler.drive_right = drive_right; handler.steer_angle = steer_angle; handler.command_id = command_id; RConsole.println("notify handler"); handler.notify(); } } // send sensor data back public static void sendBackMessage(int messageBack) throws IOException{ outputStream.writeInt(messageBack); outputStream.flush(); } } class CounterThread extends Thread{ public void run(){ int count = 0; while (true){ LCD.drawString(Integer.toString(count++), 12,0); count %= 1000; try{ Thread.sleep(100); }catch(InterruptedException e){ } } } } class KickThread extends Thread{ private static boolean targetKickState = false; public void setKickState(boolean val) { targetKickState = val; } public void run(){ while (true){ RConsole.println("kick thread pre-wait"); synchronized (this) { try{ wait(); }catch(InterruptedException e){ } } RConsole.println("kick thread post-wait"); if (targetKickState == true) { LCD.drawString("KICK!",11,1); Movement.motor_kick.setSpeed(900); Movement.motor_kick.rotate((-120*(5/3))); Movement.motor_kick.rotate((120*(5/3))); } else { LCD.drawString(" ",10,1); } } } } class DriveThread extends Thread{ private static int targetDriveLeftVal = 0; private static int targetDriveRightVal = 0; private int counter = 0; boolean ready; private int current_command = 0; public boolean Ready() { return current_command == counter && ready; } public void setTargetDriveVal(int left, int right) { targetDriveLeftVal = left; targetDriveRightVal = right; ++counter; } public void run(){ Multiplexor chip = new Multiplexor(SensorPort.S4); int prev = -1; while(true){ current_command = counter; if(current_command == prev) { try{ Thread.sleep(10); }catch(InterruptedException e){ } continue; } ready = false; int left = targetDriveLeftVal; LCD.drawString("L"+Integer.toString(left),0,1); if (left >= 0) LCD.drawString(" ",2,1); if (left == 0) chip.setMotors(0,0,0); else chip.setMotors(left/Math.abs(left), Math.abs(left), 0); int right = targetDriveRightVal; LCD.drawString("R"+Integer.toString(right),3,1); if (right >= 0) LCD.drawString(" ",5,1); if (right == 0) chip.setMotors(0,0,1); else chip.setMotors(right/Math.abs(right), Math.abs(right), 1); ready = true; prev = current_command; } } } class SteeringThread extends Thread{ private static final double thresholdAngle = 30.0; private static final double thresholdAngleR = Math.toRadians(thresholdAngle); public static final double countModulo = Math.round(360 * Movement.rotConstant); private int currentSteeringCount = 0; private int toCount; protected int targetSteeringAngle = 0; private void drawLCD(int angle) { if (angle < 10) LCD.drawString(" ", 8 ,1); else if (angle < 100) LCD.drawString(" ", 9, 1); LCD.drawString(Integer.toString(angle), 7 ,1); } /* Return the difference in target angle and the current wheel * orientation(s) in radians. */ private double getDeltaAngleR(int angle) { int cur_angle = getCurrentSteeringAngle(); double delta = angle - cur_angle; // deltaR is in range [-pi,pi] double rad = Math.toRadians(delta); double deltaR = Math.atan2( Math.sin(rad), Math.cos(rad) ); return deltaR; } /* Return -1 if the motor direction should be reversed for a given * target angle, and 1 otherwise. */ public int getDirection(int angle) { double deltaR = getDeltaAngleR(angle); double angle2 = Math.abs(deltaR) % (2*Math.PI); if (angle2 > Math.PI) angle2 = Math.PI - angle2; if (Math.abs(angle2) <= Math.PI/2.0) { return 1; } else { return -1; } } /* Return an angle in [-90°, 90°]. * Use with getDirection to move the robot correctly. */ public double getClosestAngle(int angle) { double deltaR = getDeltaAngleR(angle); double sign = deltaR / Math.abs(deltaR); // We only need to turn max 90° since we can move backwards // just as easily. if (Math.abs(deltaR) > Math.PI/2.0) { deltaR -= sign*Math.PI; } if (Math.abs(deltaR) < thresholdAngleR/2.0) { deltaR = 0; } else if (Math.abs(deltaR) >= thresholdAngleR/2.0 && Math.abs(deltaR) < thresholdAngleR) { deltaR = sign * thresholdAngleR; } return Math.toDegrees(deltaR); } public void run(){ while(true){ RConsole.println("steer thread pre-wait"); synchronized (this) { try{ wait(); }catch(InterruptedException e){ } } RConsole.println("steer thread post-wait"); int target = targetSteeringAngle; drawLCD(target); SynchroCtrl2.steeringLeftThread.setTargetSteeringAngle( target ); SynchroCtrl2.steeringRightThread.setTargetSteeringAngle( target ); } } protected void setCurrentSteeringCount(int count){ currentSteeringCount = count; } public int getCurrentSteeringAngle() { return (int)(Math.round(getCurrentSteeringCount() / Movement.rotConstant)); } public int getCurrentSteeringCount() { return currentSteeringCount; } public int getToCount(){ return toCount; } public void setToCount(int count){ toCount = count; } public void setToAngle(int angle){ setToCount((int)Math.round(angle * Movement.rotConstant)); } public int getToAngle() { return (int)Math.round(getToCount() / Movement.rotConstant); } public void setTargetSteeringAngle(int val) { targetSteeringAngle = val; } } abstract class SteeringMotorThread extends SteeringThread { private final Motor motor; protected int counter = 0; boolean ready; private int current_command = 0; public boolean Ready() { return current_command == counter && ready; } public void setTargetSteeringAngle(int val) { super.setTargetSteeringAngle(val); ++counter; } public SteeringMotorThread(Motor motor){ this.motor = motor; } public abstract void drawLCD(int count); public void run(){ motor.resetTachoCount(); motor.regulateSpeed(true); motor.smoothAcceleration(true); int prev = -1; while(true){ current_command = counter; if(current_command == prev) { try{ Thread.sleep(10); }catch(InterruptedException e){ } continue; } ready = false; int target = targetSteeringAngle; double deltaD = getClosestAngle(target); int cur_count = getCurrentSteeringCount(); int turn_count = (int)Math.round(Movement.rotConstant * deltaD); int new_count = cur_count + (int)Math.round(turn_count); new_count %= countModulo; setCurrentSteeringCount(new_count); //LCD.drawString(Integer.toString(target)+" "+Integer.toString((int)deltaD), 1, 5); if (true) drawLCD((int)deltaD); if (turn_count != 0) { motor.rotate(turn_count); } ready = true; prev = current_command; synchronized (SynchroCtrl2.commandHandler) { SynchroCtrl2.commandHandler.notify(); } } } public int getClosestDirection(int target, int cur) { int delta = target - cur; int mod = (int)countModulo; return (delta + mod/2) % mod - mod/2; } } class SteeringLeftThread extends SteeringMotorThread{ public SteeringLeftThread(){ super(Movement.motor_left); } public void drawLCD(int count) { if (count < 10) LCD.drawString(" ", 1 ,2); else if (count < 100) LCD.drawString(" ", 2 ,2); LCD.drawString(Integer.toString(count), 0 ,2); } } class SteeringRightThread extends SteeringMotorThread{ public SteeringRightThread(){ super(Movement.motor_right); } public void drawLCD(int count) { if (count < 10) LCD.drawString(" ", 5 ,2); else if (count < 100) LCD.drawString(" ", 6 ,2); LCD.drawString(Integer.toString(count), 4 ,2); } } class SensorChecker extends Thread{ // Define sensor ports public static final SensorPort leftTouch = SensorPort.S3; public static final SensorPort rightTouch = SensorPort.S2; public static final SensorPort frontTouch = SensorPort.S1; public void run(){ // Set sensor ports to touch TouchSensor front = new TouchSensor(frontTouch); TouchSensor left = new TouchSensor(leftTouch); TouchSensor right = new TouchSensor(rightTouch); // Screen messages String fsensorstate; String lsensorstate; String rsensorstate; int[] sensorMessage = new int[3]; while (true) { // Back message - made up of primes // Mod on other side to check which sensors are on if(front.isPressed()){ fsensorstate = "PRESSED"; sensorMessage[0] = 2; } else { fsensorstate = "FINE "; sensorMessage[0] = 1; } LCD.drawString("Front: " + fsensorstate, 0,4); if(left.isPressed()){ lsensorstate = "PRESSED"; sensorMessage[1] = 3; } else { lsensorstate = "FINE "; sensorMessage[1] = 1; } LCD.drawString("Left: " + lsensorstate, 0,5); if(right.isPressed()){ rsensorstate = "PRESSED"; sensorMessage[2] = 5; } else { rsensorstate = "FINE "; sensorMessage[2] = 1; } LCD.drawString("Right: " + rsensorstate, 0,6); try { if(Communicator.isConnected){ Communicator.sendBackMessage(sensorMessage[0]*sensorMessage[1]*sensorMessage[2]); } } catch (IOException e1) { } try{ Thread.sleep(100); }catch(InterruptedException e){ } } } }