From ab3a2cd0a391aed6d862390beac74f2ab3a5da67 Mon Sep 17 00:00:00 2001 From: Antonio Zugaldia Date: Mon, 28 Aug 2017 23:08:38 -0400 Subject: [PATCH] Driver (#47) * rename http listener for clarity * refactor RC driver into its own class * refactor web driver into its own class * refactor tensorflow pic taker into its own class * refactor photo-taking classes * fix up HTTPRequestListener in mobile app --- .../robocar/mobile/debug/DebugActivity.java | 4 +- .../zugaldia/robocar/app/MainActivity.java | 247 ++++-------------- .../robocar/app/autonomous/CameraDriver.java | 61 +++++ .../app/autonomous/TensorFlowTrainer.java | 93 +++++++ .../LocalhostDriver.java} | 32 +-- .../zugaldia/robocar/app/manual/RCDriver.java | 142 ++++++++++ .../software/camera/CameraOperator.java | 42 +-- .../robocar/software/camera/SpeedOwner.java | 10 - ...Listener.java => HTTPRequestListener.java} | 2 +- .../software/webserver/LocalWebServer.java | 6 +- 10 files changed, 377 insertions(+), 262 deletions(-) create mode 100644 robocar/app/src/main/java/com/zugaldia/robocar/app/autonomous/CameraDriver.java create mode 100644 robocar/app/src/main/java/com/zugaldia/robocar/app/autonomous/TensorFlowTrainer.java rename robocar/app/src/main/java/com/zugaldia/robocar/app/{RobocarSpeedChanger.java => manual/LocalhostDriver.java} (76%) create mode 100644 robocar/app/src/main/java/com/zugaldia/robocar/app/manual/RCDriver.java delete mode 100644 robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/camera/SpeedOwner.java rename robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/{RequestListener.java => HTTPRequestListener.java} (93%) diff --git a/mobile/app/src/main/java/com/zugaldia/robocar/mobile/debug/DebugActivity.java b/mobile/app/src/main/java/com/zugaldia/robocar/mobile/debug/DebugActivity.java index 299fed3..ea8d847 100644 --- a/mobile/app/src/main/java/com/zugaldia/robocar/mobile/debug/DebugActivity.java +++ b/mobile/app/src/main/java/com/zugaldia/robocar/mobile/debug/DebugActivity.java @@ -22,8 +22,8 @@ import com.zugaldia.robocar.mobile.R; import com.zugaldia.robocar.mobile.controller.IntentRouter; import com.zugaldia.robocar.software.controller.nes30.Nes30Manager; +import com.zugaldia.robocar.software.webserver.HTTPRequestListener; import com.zugaldia.robocar.software.webserver.LocalWebServer; -import com.zugaldia.robocar.software.webserver.RequestListener; import com.zugaldia.robocar.software.webserver.RobocarClient; import com.zugaldia.robocar.software.webserver.models.RobocarMove; import com.zugaldia.robocar.software.webserver.models.RobocarResponse; @@ -40,7 +40,7 @@ import timber.log.Timber; public class DebugActivity extends AppCompatActivity - implements NavigationView.OnNavigationItemSelectedListener, RequestListener { + implements NavigationView.OnNavigationItemSelectedListener, HTTPRequestListener { @BindView(R.id.button_left) Button buttonLeft; diff --git a/robocar/app/src/main/java/com/zugaldia/robocar/app/MainActivity.java b/robocar/app/src/main/java/com/zugaldia/robocar/app/MainActivity.java index 120c72f..3e7243a 100644 --- a/robocar/app/src/main/java/com/zugaldia/robocar/app/MainActivity.java +++ b/robocar/app/src/main/java/com/zugaldia/robocar/app/MainActivity.java @@ -5,58 +5,38 @@ import android.support.v7.app.AppCompatActivity; import android.view.KeyEvent; -import com.zugaldia.robocar.hardware.adafruit2348.AdafruitDcMotor; +import com.zugaldia.robocar.app.autonomous.CameraDriver; +import com.zugaldia.robocar.app.autonomous.TensorFlowTrainer; +import com.zugaldia.robocar.app.manual.RCDriver; +import com.zugaldia.robocar.app.manual.LocalhostDriver; import com.zugaldia.robocar.hardware.adafruit2348.AdafruitMotorHat; -import com.zugaldia.robocar.software.camera.CameraOperator; -import com.zugaldia.robocar.software.camera.CameraOperatorListener; -import com.zugaldia.robocar.software.camera.SpeedOwner; import com.zugaldia.robocar.software.controller.nes30.Nes30Connection; import com.zugaldia.robocar.software.controller.nes30.Nes30Listener; import com.zugaldia.robocar.software.controller.nes30.Nes30Manager; import com.zugaldia.robocar.software.webserver.LocalWebServer; -import com.zugaldia.robocar.software.webserver.RequestListener; +import com.zugaldia.robocar.software.webserver.HTTPRequestListener; import com.zugaldia.robocar.software.webserver.models.RobocarMove; import com.zugaldia.robocar.software.webserver.models.RobocarResponse; import com.zugaldia.robocar.software.webserver.models.RobocarSpeed; import com.zugaldia.robocar.software.webserver.models.RobocarStatus; import java.io.IOException; -import java.util.Timer; -import java.util.TimerTask; import fi.iki.elonen.NanoHTTPD; import timber.log.Timber; -public class MainActivity extends AppCompatActivity - implements Nes30Listener, RequestListener, SpeedOwner, CameraOperatorListener { - - // Set the speed, from 0 (off) to 255 (max speed) - private static final int MOTOR_SPEED = 255; - private static final int MOTOR_SPEED_SLOW = 95; +public class MainActivity extends AppCompatActivity implements Nes30Listener, HTTPRequestListener { private Nes30Manager nes30Manager; private Nes30Connection nes30Connection; - private boolean isMoving = false; private AdafruitMotorHat motorHat; - private AdafruitDcMotor motorFrontLeft; - private AdafruitDcMotor motorFrontRight; - private AdafruitDcMotor motorBackLeft; - private AdafruitDcMotor motorBackRight; - - //TODO: refactoring: make these into a class, maybe call it ButtonPressStates - private boolean isUpPressed = false; - private boolean isDownPressed = false; - private boolean isLeftPressed = false; - private boolean isRightPressed = false; - private boolean isUpOrDownPressed = false; - private boolean allButtonsReleased = true; - - private CameraOperator cameraOperator; - private Timer timer; - private int cameraMode; - private static final int CAMERA_MODE_ONE = 1; - private static final int CAMERA_MODE_MULTIPLE = 2; + + private RCDriver rcDriver; + private LocalhostDriver localhostDriver; + + private CameraDriver cameraDriver; + private TensorFlowTrainer tensorFlowTrainer; // I2C Name public static final String I2C_DEVICE_NAME = "I2C1"; @@ -68,48 +48,20 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - // Controller - nes30Manager = new Nes30Manager(this); - // Motors motorHat = new AdafruitMotorHat(I2C_DEVICE_NAME, MOTOR_HAT_I2C_ADDRESS, false); - motorFrontLeft = motorHat.getMotor(1); - motorBackLeft = motorHat.getMotor(2); - motorFrontRight = motorHat.getMotor(3); - motorBackRight = motorHat.getMotor(4); - // Local web server - setupWebServer(); - - // NES30 BT connection + // Remote control (for RCDriver) setupBluetooth(); - // Camera - cameraOperator = new CameraOperator(this, this, this); - } + // Local web server (for LocalhostDriver) + setupWebServer(); - @Override - public int[] getSpeeds() { - return new int[] { - motorHat.getMotor(1).getLastSpeed(), - motorHat.getMotor(2).getLastSpeed(), - motorHat.getMotor(3).getLastSpeed(), - motorHat.getMotor(4).getLastSpeed()}; + // Manual drivers (always available) + rcDriver = new RCDriver(motorHat); + localhostDriver = new LocalhostDriver(motorHat); } - private void setMotorSpeedsBasedOnButtonsPressed() { - - boolean isLowSpeedOnLeft = isLeftPressed && isUpOrDownPressed; - boolean isLowSpeedOnRight = isRightPressed && isUpOrDownPressed; - - int speedLeft = isLowSpeedOnLeft ? MOTOR_SPEED_SLOW : MOTOR_SPEED; - int speedRight = isLowSpeedOnRight ? MOTOR_SPEED_SLOW : MOTOR_SPEED; - - motorFrontLeft.setSpeed(speedLeft); - motorBackLeft.setSpeed(speedLeft); - motorFrontRight.setSpeed(speedRight); - motorBackRight.setSpeed(speedRight); - } private void setupWebServer() { LocalWebServer localWebServer = new LocalWebServer(this); @@ -121,6 +73,7 @@ private void setupWebServer() { } private void setupBluetooth() { + nes30Manager = new Nes30Manager(this); nes30Connection = new Nes30Connection(this, RobocarConstants.NES30_MAC_ADDRESS); Timber.d("BT status: %b", nes30Connection.isEnabled()); Timber.d("Paired devices: %d", nes30Connection.getPairedDevices().size()); @@ -136,10 +89,9 @@ private void setupBluetooth() { @Override protected void onDestroy() { super.onDestroy(); - release(); + rcDriver.release(); motorHat.close(); nes30Connection.cancelDiscovery(); - endSession(); } /* @@ -166,129 +118,74 @@ public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { return nes30Manager.onKeyMultiple(keyCode, count, event); } + /* + * Implements Nes30Listener + */ + @Override public void onKeyPress(@Nes30Manager.ButtonCode int keyCode, boolean isDown) { - updateButtonPressedStates(keyCode, isDown); - - if (allButtonsReleased && isMoving) { - isMoving = false; - release(); - return; - } - // We start moving the moment the key is pressed - isMoving = true; - - setMotorSpeedsBasedOnButtonsPressed(); - switch (keyCode) { case Nes30Manager.BUTTON_UP_CODE: - moveForward(); + rcDriver.moveForward(keyCode, isDown); break; case Nes30Manager.BUTTON_DOWN_CODE: - moveBackward(); + rcDriver.moveBackward(keyCode, isDown); break; case Nes30Manager.BUTTON_LEFT_CODE: - if (!isUpOrDownPressed) { - turnLeft(); - } + rcDriver.turnLeft(keyCode, isDown); break; case Nes30Manager.BUTTON_RIGHT_CODE: - if (!isUpOrDownPressed) { - turnRight(); - } + rcDriver.turnRight(keyCode, isDown); break; case Nes30Manager.BUTTON_X_CODE: if (isDown) { Timber.d("Starting camera session for single pics."); - cameraMode = CAMERA_MODE_ONE; - startSession(); } break; case Nes30Manager.BUTTON_Y_CODE: if (isDown) { Timber.d("Starting camera session for multiple pics."); - cameraMode = CAMERA_MODE_MULTIPLE; - startSession(); + if (tensorFlowTrainer == null) { + tensorFlowTrainer = new TensorFlowTrainer(this, motorHat); + } + tensorFlowTrainer.startSession(); } break; case Nes30Manager.BUTTON_A_CODE: if (isDown) { Timber.d("Stopping camera session."); - endSession(); + tensorFlowTrainer.endSession(); } break; - case Nes30Manager.BUTTON_KONAMI: - // Do your magic here ;-) - break; - default: - // No action + case Nes30Manager.BUTTON_B_CODE: + Timber.d("Button B pressed."); break; - } - } - - private void updateButtonPressedStates(@Nes30Manager.ButtonCode int keyCode, boolean isDown) { - switch (keyCode) { - case Nes30Manager.BUTTON_UP_CODE: - isUpPressed = isDown; + case Nes30Manager.BUTTON_L_CODE: + if (cameraDriver == null) { + cameraDriver = new CameraDriver(this, motorHat); + } + cameraDriver.start(); break; - case Nes30Manager.BUTTON_DOWN_CODE: - isDownPressed = isDown; + case Nes30Manager.BUTTON_R_CODE: + if (cameraDriver != null) { + cameraDriver.stop(); + } break; - case Nes30Manager.BUTTON_LEFT_CODE: - isLeftPressed = isDown; + case Nes30Manager.BUTTON_SELECT_CODE: + Timber.d("Select button pressed."); break; - case Nes30Manager.BUTTON_RIGHT_CODE: - isRightPressed = isDown; + case Nes30Manager.BUTTON_START_CODE: + Timber.d("Start button pressed."); break; - default: - // No action + case Nes30Manager.BUTTON_KONAMI: + // Do your magic here ;-) break; } - - isUpOrDownPressed = isUpPressed || isDownPressed; - - allButtonsReleased = !(isUpPressed || isDownPressed || isLeftPressed || isRightPressed); } - private void moveForward() { - Timber.d("Moving forward."); - motorFrontLeft.run(AdafruitMotorHat.FORWARD); - motorFrontRight.run(AdafruitMotorHat.BACKWARD); - motorBackLeft.run(AdafruitMotorHat.BACKWARD); - motorBackRight.run(AdafruitMotorHat.FORWARD); - } - - private void moveBackward() { - Timber.d("Moving backward."); - motorFrontLeft.run(AdafruitMotorHat.BACKWARD); - motorFrontRight.run(AdafruitMotorHat.FORWARD); - motorBackLeft.run(AdafruitMotorHat.FORWARD); - motorBackRight.run(AdafruitMotorHat.BACKWARD); - } - - private void turnLeft() { - Timber.d("Turning left."); - motorFrontLeft.run(AdafruitMotorHat.BACKWARD); - motorFrontRight.run(AdafruitMotorHat.BACKWARD); - motorBackLeft.run(AdafruitMotorHat.FORWARD); - motorBackRight.run(AdafruitMotorHat.FORWARD); - } - - private void turnRight() { - Timber.d("Turning right."); - motorFrontLeft.run(AdafruitMotorHat.FORWARD); - motorFrontRight.run(AdafruitMotorHat.FORWARD); - motorBackLeft.run(AdafruitMotorHat.BACKWARD); - motorBackRight.run(AdafruitMotorHat.BACKWARD); - } - - private void release() { - Timber.d("Release."); - motorFrontLeft.run(AdafruitMotorHat.RELEASE); - motorFrontRight.run(AdafruitMotorHat.RELEASE); - motorBackLeft.run(AdafruitMotorHat.RELEASE); - motorBackRight.run(AdafruitMotorHat.RELEASE); - } + /* + * Implement RequestListener (web server) + */ @Override public void onRequest(NanoHTTPD.IHTTPSession session) { @@ -310,42 +207,8 @@ public RobocarResponse onSpeed(RobocarSpeed speed) { if (speed == null) { return new RobocarResponse(400, "Bad Request"); } - RobocarSpeedChanger speedChanger = new RobocarSpeedChanger( - motorFrontLeft, motorFrontRight, motorBackLeft, motorBackRight); - speedChanger.changeSpeed(speed); - return new RobocarResponse(200, "OK"); - } - - private void startSession() { - if (!cameraOperator.isInSession()) { - cameraOperator.startSession(); - } else if (cameraMode == CAMERA_MODE_ONE) { - sessionStarted(); - } - } - - @Override - public void sessionStarted() { - switch (cameraMode) { - case CAMERA_MODE_ONE: - cameraOperator.takePicture(); - break; - case CAMERA_MODE_MULTIPLE: - timer = new Timer("CAMERA_TRAINING"); - timer.schedule(new TimerTask() { - @Override - public void run() { - cameraOperator.takePicture(); - } - }, 0, 250); // 0 delay, 250 period - break; - } - } - private void endSession() { - if (timer != null) { - timer.cancel(); - } - cameraOperator.endSession(); + localhostDriver.changeSpeed(speed); + return new RobocarResponse(200, "OK"); } } diff --git a/robocar/app/src/main/java/com/zugaldia/robocar/app/autonomous/CameraDriver.java b/robocar/app/src/main/java/com/zugaldia/robocar/app/autonomous/CameraDriver.java new file mode 100644 index 0000000..1e9ff0e --- /dev/null +++ b/robocar/app/src/main/java/com/zugaldia/robocar/app/autonomous/CameraDriver.java @@ -0,0 +1,61 @@ +package com.zugaldia.robocar.app.autonomous; + +import android.content.Context; +import android.media.ImageReader; + +import com.zugaldia.robocar.hardware.adafruit2348.AdafruitMotorHat; +import com.zugaldia.robocar.software.camera.CameraOperator; +import com.zugaldia.robocar.software.camera.CameraOperatorListener; +import com.zugaldia.robocar.software.camera.ImageSaver; + +import java.io.File; + +import timber.log.Timber; + +/** + * Autonomous driving using CV processing on camera photos. + */ +public class CameraDriver implements CameraOperatorListener, ImageReader.OnImageAvailableListener { + + private final static String PHOTO_FILENAME = "cv.jpg"; + + private AdafruitMotorHat motorHat; + private CameraOperator cameraOperator; + + public CameraDriver(Context context, AdafruitMotorHat motorHat) { + this.motorHat = motorHat; + cameraOperator = new CameraOperator(context, this); + } + + public void start() { + cameraOperator.startSession(this); + } + + public void stop() { + cameraOperator.endSession(); + } + + @Override + public void sessionStarted() { + Timber.d("Camera is ready, taking a picture."); + cameraOperator.takePicture(); + } + + @Override + public void onImageAvailable(final ImageReader reader) { + Timber.d("Picture is available."); + new Thread(new Runnable() { + @Override + public void run() { + File root = ImageSaver.getRoot(CameraOperator.ROBOCAR_FOLDER); + new ImageSaver(reader.acquireLatestImage(), root, PHOTO_FILENAME).run(); + processPhoto(root, PHOTO_FILENAME); + } + }).start(); + } + + private void processPhoto(File root, String filename) { + File path = new File(root, filename); + Timber.d("TODO: Process %s", path.getAbsolutePath()); + } +} diff --git a/robocar/app/src/main/java/com/zugaldia/robocar/app/autonomous/TensorFlowTrainer.java b/robocar/app/src/main/java/com/zugaldia/robocar/app/autonomous/TensorFlowTrainer.java new file mode 100644 index 0000000..f0bc79b --- /dev/null +++ b/robocar/app/src/main/java/com/zugaldia/robocar/app/autonomous/TensorFlowTrainer.java @@ -0,0 +1,93 @@ +package com.zugaldia.robocar.app.autonomous; + +import android.content.Context; +import android.media.ImageReader; + +import com.zugaldia.robocar.hardware.adafruit2348.AdafruitMotorHat; +import com.zugaldia.robocar.software.camera.CameraOperator; +import com.zugaldia.robocar.software.camera.CameraOperatorListener; +import com.zugaldia.robocar.software.camera.ImageSaver; + +import java.io.File; +import java.util.Date; +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; + +import timber.log.Timber; + +/** + * Captures photos that can be used to train a TensorFlow model. + */ +public class TensorFlowTrainer implements + CameraOperatorListener, ImageReader.OnImageAvailableListener { + + private AdafruitMotorHat motorHat; + private CameraOperator cameraOperator; + + private String sessionId; + private int sessionCount; + private Timer timer; + + public TensorFlowTrainer(Context context, AdafruitMotorHat motorHat) { + this.motorHat = motorHat; + cameraOperator = new CameraOperator(context, this); + } + + public int[] getSpeeds() { + return new int[] { + motorHat.getMotor(1).getLastSpeed(), + motorHat.getMotor(2).getLastSpeed(), + motorHat.getMotor(3).getLastSpeed(), + motorHat.getMotor(4).getLastSpeed()}; + } + + public void startSession() { + if (!cameraOperator.isInSession()) { + sessionId = UUID.randomUUID().toString().replace("-", ""); + sessionCount = 0; + cameraOperator.startSession(this); + } + } + + @Override + public void sessionStarted() { + timer = new Timer("CAMERA_TRAINING"); + timer.schedule(new TimerTask() { + @Override + public void run() { + cameraOperator.takePicture(); + } + }, 0, 250); // 0 delay, 250 period + } + + public void endSession() { + if (timer != null) { + timer.cancel(); + } + cameraOperator.endSession(); + } + + @Override + public void onImageAvailable(final ImageReader reader) { + Timber.d("Image available."); + new Thread(new Runnable() { + @Override + public void run() { + File root = ImageSaver.getRoot(CameraOperator.ROBOCAR_FOLDER); + ImageSaver imageSaver = new ImageSaver(reader.acquireLatestImage(), root, getFilename()); + imageSaver.run(); + } + }).start(); + } + + private String getFilename() { + String timestamp = CameraOperator.DATE_FORMAT.format(new Date()); + int[] speeds = getSpeeds(); + String speedState = String.format(Locale.US, "%d-%d-%d-%d", + speeds[0], speeds[1], speeds[2], speeds[3]); + return String.format(Locale.US, "robocar-%s-%d-%s-%s.jpg", + sessionId, sessionCount++, timestamp, speedState); + } +} diff --git a/robocar/app/src/main/java/com/zugaldia/robocar/app/RobocarSpeedChanger.java b/robocar/app/src/main/java/com/zugaldia/robocar/app/manual/LocalhostDriver.java similarity index 76% rename from robocar/app/src/main/java/com/zugaldia/robocar/app/RobocarSpeedChanger.java rename to robocar/app/src/main/java/com/zugaldia/robocar/app/manual/LocalhostDriver.java index 4460aff..60e1fd8 100644 --- a/robocar/app/src/main/java/com/zugaldia/robocar/app/RobocarSpeedChanger.java +++ b/robocar/app/src/main/java/com/zugaldia/robocar/app/manual/LocalhostDriver.java @@ -1,4 +1,4 @@ -package com.zugaldia.robocar.app; +package com.zugaldia.robocar.app.manual; import com.zugaldia.robocar.hardware.adafruit2348.AdafruitDcMotor; import com.zugaldia.robocar.hardware.adafruit2348.AdafruitMotorHat; @@ -6,10 +6,9 @@ import com.zugaldia.robocar.software.webserver.models.RobocarSpeed; /** - * Created by Halim.Salameh on 5/9/2017. + * Drives the Robocar using localhost (invoked by the companion app). */ - -class RobocarSpeedChanger { +public class LocalhostDriver { // If speed was set too low, the motor could burn. private static final int MIN_SPEED = 64; @@ -19,20 +18,14 @@ class RobocarSpeedChanger { private AdafruitDcMotor motorBackLeft; private AdafruitDcMotor motorBackRight; - public RobocarSpeedChanger( - AdafruitDcMotor motorFrontLeft, - AdafruitDcMotor motorFrontRight, - AdafruitDcMotor motorBackLeft, - AdafruitDcMotor motorBackRight) { - - this.motorFrontLeft = motorFrontLeft; - this.motorFrontRight = motorFrontRight; - this.motorBackLeft = motorBackLeft; - this.motorBackRight = motorBackRight; + public LocalhostDriver(AdafruitMotorHat motorHat) { + motorFrontLeft = motorHat.getMotor(1); + motorBackLeft = motorHat.getMotor(2); + motorFrontRight = motorHat.getMotor(3); + motorBackRight = motorHat.getMotor(4); } public void changeSpeed(RobocarSpeed speed) { - if (speed.getLeft() != null) { setLeftSpeed(speed.getLeft()); } @@ -43,7 +36,6 @@ public void changeSpeed(RobocarSpeed speed) { } public void setLeftSpeed(int speed) { - int unsignedSpeed = Math.abs(speed); if (unsignedSpeed < MIN_SPEED) { unsignedSpeed = 0; @@ -62,7 +54,6 @@ public void setLeftSpeed(int speed) { } public void setRightSpeed(int speed) { - int unsignedSpeed = Math.abs(speed); if (unsignedSpeed < MIN_SPEED) { unsignedSpeed = 0; @@ -71,13 +62,14 @@ public void setRightSpeed(int speed) { motorBackRight.setSpeed(unsignedSpeed); motorFrontRight.setSpeed(unsignedSpeed); - if (speed > 0) { // Positive speed motor directions + if (speed > 0) { + // Positive speed motor directions motorFrontRight.run(AdafruitMotorHat.BACKWARD); motorBackRight.run(AdafruitMotorHat.FORWARD); - } else { // Negative speed motor directions + } else { + // Negative speed motor directions motorFrontRight.run(AdafruitMotorHat.FORWARD); motorBackRight.run(AdafruitMotorHat.BACKWARD); } - } } diff --git a/robocar/app/src/main/java/com/zugaldia/robocar/app/manual/RCDriver.java b/robocar/app/src/main/java/com/zugaldia/robocar/app/manual/RCDriver.java new file mode 100644 index 0000000..88832b9 --- /dev/null +++ b/robocar/app/src/main/java/com/zugaldia/robocar/app/manual/RCDriver.java @@ -0,0 +1,142 @@ +package com.zugaldia.robocar.app.manual; + +import com.zugaldia.robocar.hardware.adafruit2348.AdafruitDcMotor; +import com.zugaldia.robocar.hardware.adafruit2348.AdafruitMotorHat; +import com.zugaldia.robocar.software.controller.nes30.Nes30Manager; + +import timber.log.Timber; + +/** + * Remote control driver. + */ +public class RCDriver { + + // Set the speed, from 0 (off) to 255 (max speed) + private static final int MOTOR_SPEED_SLOW = 95; + private static final int MOTOR_SPEED = 255; + + private AdafruitDcMotor motorFrontLeft; + private AdafruitDcMotor motorFrontRight; + private AdafruitDcMotor motorBackLeft; + private AdafruitDcMotor motorBackRight; + + private boolean isUpPressed = false; + private boolean isDownPressed = false; + private boolean isLeftPressed = false; + private boolean isRightPressed = false; + private boolean isUpOrDownPressed = false; + private boolean allButtonsReleased = true; + + private boolean isMoving = false; + + public RCDriver(AdafruitMotorHat motorHat) { + motorFrontLeft = motorHat.getMotor(1); + motorBackLeft = motorHat.getMotor(2); + motorFrontRight = motorHat.getMotor(3); + motorBackRight = motorHat.getMotor(4); + } + + public void updateButtonPressedStates(@Nes30Manager.ButtonCode int keyCode, boolean isDown) { + switch (keyCode) { + case Nes30Manager.BUTTON_UP_CODE: + isUpPressed = isDown; + break; + case Nes30Manager.BUTTON_DOWN_CODE: + isDownPressed = isDown; + break; + case Nes30Manager.BUTTON_LEFT_CODE: + isLeftPressed = isDown; + break; + case Nes30Manager.BUTTON_RIGHT_CODE: + isRightPressed = isDown; + break; + default: + // No action + break; + } + + isUpOrDownPressed = isUpPressed || isDownPressed; + allButtonsReleased = !(isUpPressed || isDownPressed || isLeftPressed || isRightPressed); + } + + private void preCheck(@Nes30Manager.ButtonCode int keyCode, boolean isDown) { + updateButtonPressedStates(keyCode, isDown); + if (allButtonsReleased && isMoving) { + isMoving = false; + release(); + return; + } + + // We start moving the moment the key is pressed + isMoving = true; + + setMotorSpeedsBasedOnButtonsPressed(); + } + + public void moveForward(@Nes30Manager.ButtonCode int keyCode, boolean isDown) { + preCheck(keyCode, isDown); + + Timber.d("Moving forward."); + motorFrontLeft.run(AdafruitMotorHat.FORWARD); + motorFrontRight.run(AdafruitMotorHat.BACKWARD); + motorBackLeft.run(AdafruitMotorHat.BACKWARD); + motorBackRight.run(AdafruitMotorHat.FORWARD); + } + + public void moveBackward(@Nes30Manager.ButtonCode int keyCode, boolean isDown) { + preCheck(keyCode, isDown); + + Timber.d("Moving backward."); + motorFrontLeft.run(AdafruitMotorHat.BACKWARD); + motorFrontRight.run(AdafruitMotorHat.FORWARD); + motorBackLeft.run(AdafruitMotorHat.FORWARD); + motorBackRight.run(AdafruitMotorHat.BACKWARD); + } + + public void turnLeft(@Nes30Manager.ButtonCode int keyCode, boolean isDown) { + preCheck(keyCode, isDown); + if (isUpOrDownPressed) { + return; + } + + Timber.d("Turning left."); + motorFrontLeft.run(AdafruitMotorHat.BACKWARD); + motorFrontRight.run(AdafruitMotorHat.BACKWARD); + motorBackLeft.run(AdafruitMotorHat.FORWARD); + motorBackRight.run(AdafruitMotorHat.FORWARD); + } + + public void turnRight(@Nes30Manager.ButtonCode int keyCode, boolean isDown) { + preCheck(keyCode, isDown); + if (isUpOrDownPressed) { + return; + } + + Timber.d("Turning right."); + motorFrontLeft.run(AdafruitMotorHat.FORWARD); + motorFrontRight.run(AdafruitMotorHat.FORWARD); + motorBackLeft.run(AdafruitMotorHat.BACKWARD); + motorBackRight.run(AdafruitMotorHat.BACKWARD); + } + + public void release() { + Timber.d("Release."); + motorFrontLeft.run(AdafruitMotorHat.RELEASE); + motorFrontRight.run(AdafruitMotorHat.RELEASE); + motorBackLeft.run(AdafruitMotorHat.RELEASE); + motorBackRight.run(AdafruitMotorHat.RELEASE); + } + + private void setMotorSpeedsBasedOnButtonsPressed() { + boolean isLowSpeedOnLeft = isLeftPressed && isUpOrDownPressed; + boolean isLowSpeedOnRight = isRightPressed && isUpOrDownPressed; + + int speedLeft = isLowSpeedOnLeft ? MOTOR_SPEED_SLOW : MOTOR_SPEED; + int speedRight = isLowSpeedOnRight ? MOTOR_SPEED_SLOW : MOTOR_SPEED; + + motorFrontLeft.setSpeed(speedLeft); + motorBackLeft.setSpeed(speedLeft); + motorFrontRight.setSpeed(speedRight); + motorBackRight.setSpeed(speedRight); + } +} diff --git a/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/camera/CameraOperator.java b/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/camera/CameraOperator.java index b8c3e8b..d773082 100644 --- a/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/camera/CameraOperator.java +++ b/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/camera/CameraOperator.java @@ -21,10 +21,8 @@ import java.io.File; import java.text.SimpleDateFormat; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.Locale; -import java.util.UUID; import timber.log.Timber; @@ -35,23 +33,20 @@ * in external storage for easier access. */ -public class CameraOperator implements - SessionCallback.SessionCallbackListener, - ImageReader.OnImageAvailableListener { +public class CameraOperator implements SessionCallback.SessionCallbackListener { + + public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.US); + public static final String ROBOCAR_FOLDER = "robocar"; private static final int CAMERA_INDEX = 0; private static final int IMAGE_WIDTH = 320; private static final int IMAGE_HEIGHT = 240; private static final int IMAGE_FORMAT = ImageFormat.JPEG; private static final int MAX_IMAGES = 5; - private static final String ROBOCAR_FOLDER = "robocar"; - private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.US); private CameraOperatorListener listener; - private SpeedOwner speedOwner; private boolean inSession; - private File root; private boolean autofocusSupported = false; private DeviceCallback deviceCallback; @@ -62,13 +57,10 @@ public class CameraOperator implements private Handler backgroundHandler; private ImageReader imageReader; - private String sessionId; - private int sessionCount; - public CameraOperator(Context context, CameraOperatorListener listener, SpeedOwner speedOwner) { + public CameraOperator(Context context, CameraOperatorListener listener) { Timber.d("Building camera training object."); this.listener = listener; - this.speedOwner = speedOwner; try { init(context); @@ -93,7 +85,7 @@ private void init(Context context) throws CameraAccessException { return; } - root = ImageSaver.getRoot(ROBOCAR_FOLDER); + File root = ImageSaver.getRoot(ROBOCAR_FOLDER); if (root == null) { Timber.e("Failed to create destination folder."); return; @@ -147,7 +139,7 @@ private void stopBackgroundThread() { } } - public void startSession() { + public void startSession(ImageReader.OnImageAvailableListener onImageAvailableListener) { Timber.d("Starting a session."); if (inSession) { Timber.d("Session already started, end it first."); @@ -161,7 +153,7 @@ public void startSession() { } imageReader = ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_FORMAT, MAX_IMAGES); - imageReader.setOnImageAvailableListener(this, backgroundHandler); + imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler); List outputs = Collections.singletonList(imageReader.getSurface()); sessionCallback = new SessionCallback(this); try { @@ -170,9 +162,6 @@ public void startSession() { } catch (CameraAccessException e) { Timber.e(e, "Failed to start a camera session."); } - - sessionId = UUID.randomUUID().toString().replace("-", ""); - sessionCount = 0; } public void takePicture() { @@ -237,21 +226,6 @@ public void onConfigured() { listener.sessionStarted(); } - /** - * This method indicates we got an image from the camera. - */ - @Override - public void onImageAvailable(ImageReader reader) { - Timber.d("Image available."); - String timestamp = dateFormat.format(new Date()); - int[] speeds = speedOwner.getSpeeds(); - String speedState = String.format(Locale.US, "%d-%d-%d-%d", - speeds[0], speeds[1], speeds[2], speeds[3]); - String filename = String.format(Locale.US, "robocar-%s-%d-%s-%s.jpg", - sessionId, sessionCount++, timestamp, speedState); - backgroundHandler.post(new ImageSaver(reader.acquireLatestImage(), root, filename)); - } - /** * Helpful debugging method: Dump all supported camera formats to log. You don't need to run * this for normal operation, but it's very helpful when porting this code to different diff --git a/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/camera/SpeedOwner.java b/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/camera/SpeedOwner.java deleted file mode 100644 index ec2c67d..0000000 --- a/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/camera/SpeedOwner.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.zugaldia.robocar.software.camera; - -/** - * Created by antonio on 5/26/17. - */ -public interface SpeedOwner { - - int[] getSpeeds(); - -} diff --git a/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/RequestListener.java b/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/HTTPRequestListener.java similarity index 93% rename from robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/RequestListener.java rename to robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/HTTPRequestListener.java index 570c518..4f75216 100644 --- a/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/RequestListener.java +++ b/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/HTTPRequestListener.java @@ -11,7 +11,7 @@ * Created by antonio on 4/5/17. */ -public interface RequestListener { +public interface HTTPRequestListener { void onRequest(NanoHTTPD.IHTTPSession session); diff --git a/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/LocalWebServer.java b/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/LocalWebServer.java index de954a0..3936f75 100644 --- a/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/LocalWebServer.java +++ b/robocar/libsoftware/src/main/java/com/zugaldia/robocar/software/webserver/LocalWebServer.java @@ -30,14 +30,14 @@ public class LocalWebServer extends NanoHTTPD { public static final String ENDPOINT_POST_MOVE = "api/move"; public static final String ENDPOINT_POST_SPEED = "api/speed"; - private RequestListener requestListener; + private HTTPRequestListener requestListener; - public LocalWebServer(RequestListener requestListener) { + public LocalWebServer(HTTPRequestListener requestListener) { super(8080); this.requestListener = requestListener; } - public LocalWebServer(RequestListener requestListener, int port) { + public LocalWebServer(HTTPRequestListener requestListener, int port) { super(port); this.requestListener = requestListener; }