Become a Captain for a Day! (Final Project) – Darko Skulikj

And here it is, the last project of the semester. Before saying anything about the project, I just want to express my gratefulness to all the students, instructors, professors and especially Professor Aaron for having an amazing, fun and challenging time during this class.

Concept

The concept of this project is pretty simple. I’m going to make you a captain of a boat for a day. SPOILER ALERT: You don’t need a license! To make this dream come true, I decided to conceptualize for the controls to be very simple, you just click to go left, right and to turn the motor boat on and off. This really takes us into intuitive design, and I believe that users would adapt very simply to this. Other that that I would need to think about the bed of the boat, the DC Motor, the Propeller, the Physics behind it and the whole placement of the Arduino. The rough sketch looked something like this:

 

Production:

For the boat model, I found this boat bed on Thingy Verse and adjusted the dimensions so that it can find the Arduino and the breadboard, as well as the batteries. Here is how the model looked before printing:

After printing it out, I looked into 3d printing a propeller which would actually be strong enough to pull the boat so I found this 3d model:

After that I placed all the parts and coded my logic for the user interaction. The code can be seen below:

#include <Servo.h>

int serialVal0 = 0;
int serialVal1 = 1;
int previousButton2State = LOW;

// Define pins for buttons, servo, and DC motor
const int button1Pin = 2; // Pin for the first button
const int button2Pin = 3; // Pin for the second button
const int button3Pin = 4; // Pin for the third button
const int servoPin = 10;  // Pin for the servo motor
const int motorPin = 11;  // Pin for the DC motor

// Define variables to store the state of buttons and motor
int button1State = 0;
int button2State = 0;
int button3State = 0;
bool motorState = false; // Motor state flag

// Create a servo object
Servo myServo;

void setup() {
  // Initialize serial communication
  Serial.begin(9600);

  // Attach servo to its pin
  myServo.attach(servoPin);

  // Set motor pin as output
  pinMode(motorPin, OUTPUT);

  // Set button pins as inputs
  pinMode(button1Pin, INPUT);
  pinMode(button2Pin, INPUT);
  pinMode(button3Pin, INPUT);

  // Start the handshake
  while (Serial.available() <= 0) {
    Serial.println("0,0"); // Send a starting message
    delay(50);
  }
}

void loop() {
  // Read the state of buttons
  button1State = digitalRead(button1Pin);
  button2State = digitalRead(button2Pin);
  button3State = digitalRead(button3Pin);

  // If button 1 is pressed, turn servo left
  if (button1State == HIGH) {
    myServo.write(120);
    serialVal0 = 80;
    delay(100); // Add a delay to avoid sending data too fast
  }

  // Toggle motor based on button 2 state
  if (button2State == HIGH) {
    if (previousButton2State == LOW) {
      motorState = !motorState; // Toggle motor state only once when the button is released
      digitalWrite(motorPin, motorState); // Set motor state
    }
  } 

  // Update serialVal1 based on motor state
  serialVal1 = motorState ? 1 : 0;

  // Update previous button state
  previousButton2State = button2State;

  // If button 3 is pressed, turn servo right
  if (button3State == HIGH) {
    myServo.write(80);
    serialVal0 = 140;
    delay(100); // Add a delay to avoid sending data too fast
  }

  // Return servo to neutral position if no buttons are pressed
  if (button1State == LOW && button3State == LOW) {
    myServo.write(100); // Neutral position
    serialVal0 = 115;
    delay(100); // Add a delay to avoid sending data too fast
  }

  // Send the values of serialVal0 and serialVal1
  Serial.print(serialVal0);
  Serial.print(',');
  Serial.println(serialVal1);
}

Of course this had to be connected to p5.js so I made a sketch which would provide a nice interface showing the speed and direction of where the boat is headed, it looks like this:

The p5.js code looks like this:

let servoPos; // Variable to store servo position
let motorSpeed; // Variable to store motor speed
let boatImage; // Variable to store boat image
let islandsImage1, islandsImage2, islandsImage3; // Variables to store islands images
let otherBoatsImage; // Variable to store other boats image
let serialSetUp = false; // Variable to track if serial setup is done

// Variables to store positions of objects
let islands1Y, islands2Y, islands3Y, otherBoats1Y, otherBoats2Y;

function preload() {
  // Load boat, islands, and other boats images
  boatImage = loadImage('boat.png');
  islandsImage1 = loadImage('islands.png');
  islandsImage2 = loadImage('islands.png');
  islandsImage3 = loadImage('islands.png');
  otherBoatsImage = loadImage('otherboats.png');
}

function setup() {
  createCanvas(800, 600); // Larger canvas size
  textSize(24); // Bigger font size
  // Display initial message centered on the canvas
  textAlign(CENTER, CENTER);
  setGradient(0, 0, width, height, color(0, 191, 255), color(0, 0, 128)); // Background gradient
  fill(255); // White text color
  text("Press spacebar to turn the boat motor on", width / 2, height / 2);

  // Initialize positions of objects
  islands1Y = height / 2;
  islands2Y = height / 2;
  islands3Y = height / 2;
  otherBoats1Y = height / 2;
  otherBoats2Y = height / 2;
}

function readSerial(data) {
  if (data != null) {
    // Split the incoming data by comma
    let dataArray = split(trim(data), ",");
    // If the right length, then proceed
    if (dataArray.length == 2) {
      // Parse the values as integers and store them in servoPos and motorSpeed
      servoPos = int(dataArray[0]);
      motorSpeed = int(dataArray[1]);
      console.log("Servo position: " + servoPos + ", Motor speed: " + motorSpeed);
    }
  }
  
  //////////////////////////////////
    //SEND TO ARDUINO HERE (handshake)
    //////////////////////////////////
    let sendToArduino = 0 + "\n";
    writeSerial(sendToArduino);
}

function draw() {
  // If serial setup is not done, return
  if (!serialSetUp) return;

  // Background gradient resembling water
  setGradient(0, 0, width, height, color(0, 191, 255), color(0, 0, 128));

  // Display boat heading status centered above boat
 

  // Move and draw islands images
  islands1Y += 1; // Speed of islands movement
  if (islands1Y > height) {
    islands1Y = 0; // Reset when islands moves off the screen
  }
  image(islandsImage1, 140, islands1Y, 100, 100); // Islands image on the left side

  islands2Y += 1.5; // Speed of islands movement
  if (islands2Y > height) {
    islands2Y = 0; // Reset when islands moves off the screen
  }
  image(islandsImage2, 250, islands2Y, 50, 50); // Islands image on the left side

  islands3Y += 2; // Speed of islands movement
  if (islands3Y > height) {
    islands3Y = 0; // Reset when islands moves off the screen
  }
  image(islandsImage3, 0, islands3Y, 150, 150); // Islands image on the left side

  // Move and draw other boats images
  otherBoats1Y += 1.2; // Speed of other boats movement
  if (otherBoats1Y > height) {
    otherBoats1Y = 0; // Reset when other boats moves off the screen
  }
  image(otherBoatsImage, 500, otherBoats1Y, 90, 180); // Other boats image on the right side

  otherBoats2Y += 1.8; // Speed of other boats movement
  if (otherBoats2Y > height) {
    otherBoats2Y = 0; // Reset when other boats moves off the screen
  }
  image(otherBoatsImage, 600, otherBoats2Y, 90, 180); // Other boats image on the right side
  
   fill(255); // White text color
  textAlign(CENTER);
  if (servoPos == 115)
    text("The boat is heading Straight!", width / 2, boatImage.height / 2 - 20); // Adjusted position
  else if (servoPos == 80)
    text("The boat is heading to the Right!", width / 2, boatImage.height / 2 - 20); // Adjusted position
  else if (servoPos == 140)
    text("The boat is heading to the Left!", width / 2, boatImage.height / 2 - 20); // Adjusted position

  // Draw boat image with rotation based on servo position
  push();
  translate(width / 2, height / 2); // Center of the screen
  rotate(radians(-90)); // Rotate to point upwards
  if (servoPos == 80) {
    rotate(radians(20)); // Rotate slightly to the right
  } else if (servoPos == 140) {
    rotate(radians(-20)); // Rotate slightly to the left
  }
  imageMode(CENTER);
  image(boatImage, 0, 0, 250, 150); // Draw boat image
  pop();

  // Display motor speed centered below boat with larger font size
  textSize(24); // Larger font size
  textAlign(CENTER);
  if(motorSpeed ==0)
    text("Motor Speed: HIGH ", width / 2, height - 20);
  else if(motorSpeed == 1)
    text("Motor Speed: LOW ", width / 2, height - 20);
}

// Function to draw a gradient background
function setGradient(x, y, w, h, c1, c2) {
  noFill();
  for (let i = y; i <= y + h; i++) {
    let inter = map(i, y, y + h, 0, 1);
    let c = lerpColor(c1, c2, inter);
    stroke(c);
    line(x, i, x + w, i);
  }
}

function keyPressed() {
  if (key == " ") {
    if (!serialSetUp) {
      setUpSerial();
      serialSetUp = true;
    }
  }
}

I also added some styrofoam padding on the bottom for added support and easier floating.

Here are some pictures from the production process:

And finally, here is the final video Presentation of it working:

Conclusion:

Overall this project was very fun challenging and I really think I learned a lot during the making.

Even though this is the end of the road for this class, this is only the beginning for me in exploring this area and I’m really excited of what happens next!

Darko

Leave a Reply