The curious worm inside this box always wants to explore the world.  When it seems quiet and calm outside, it will slowly open the box to peer outside.  But it’s also very timid: When something from the outside approaches too quickly or is too close to the opening, the box will slam shut as the worm quickly hides and shivers for a while.  Attempting to approach the box slowly will not scare the worm, and he will remain peering out with the lid open.

The box is made of 6mm wood, with two servo motors and an ultrasonic range sensor. The box will gradually open up to explore the world, and when the range sensor detects an object nearby or approaching at high velocity, the box will close up and start the vibration servo motor.

Solidworks files: cad

See below for code:

// Demo 4: Curious Box created by Jen Kwang and Henry Zhang for course 16-223

// Allows for a curious worm to poke its head out of a box slowly when
// no objects are within near range of HC-SR04 ultrasonic ranger or when no
// nearby objects are approaching very fast.  Worm will become frightened and
// slam box shut otherwise.
//
// Credit for much of the code below for use of ultrasonic ranger goes to:
//     Copyright (c) 2016, Garth Zeglin.  All rights reserved. Licensed under the
//     terms of the BSD 3-clause license as included in LICENSE.
//
// For the original file modified/tutorial for ultrasonic ranger, see:
// https://courses.ideate.cmu.edu/16-223/f2018/text/ex/Arduino/read-sonar/read-sonar.html#exercise-read-sonar


// ================================================================================
#include <Servo.h>

const int TRIG_PIN = 8;
const int ECHO_PIN = 7;
const int SERVO_PIN = 9;
const int SHAKE_PIN = 10;

Servo svo;
Servo shaker;

// The rated distance limit of the sensor, in cm.
const int MAX_DISTANCE = 450;

// A typical speed of sound, specified in cm/sec.
const long SOUND_SPEED = 34000;

// Determine the maximum time to wait for an echo. The maximum rated distance is
// 4.5 meters; if no echo is received within the duration representing this
// round-trip distance, stop measuring.  The timeout is specified in
// microseconds.
const long TIMEOUT = (2 * MAX_DISTANCE * 1000000) / SOUND_SPEED;

// ================================================================================
// Configure the hardware once after booting up.  This runs once after pressing
// reset or powering up the board.

void setup()
{
  svo.attach(SERVO_PIN);
  shaker.attach(SHAKE_PIN);
  shaker.write(20);
  svo.write(10);

  // Initialize the serial UART at 9600 bits per second.
  Serial.begin(9600);

  // Initialize the trigger pin for output.
  pinMode(TRIG_PIN, OUTPUT);
  digitalWrite(TRIG_PIN, LOW);

  // Initialize the echo pin for input.
  pinMode(ECHO_PIN, INPUT);
}


int angle = 10;
int i = 1;
int vel = 0;
float distance1 = 0;
float distance2 = 0;
int count = 0;

// ================================================================================
void loop()
{
  // Read the distance as an uncalibrated timing value in microseconds.
  long duration = ping_sonar(); // function is defined below

  // If valid, scale into real-world units.
  if (duration > 0) {

  }
  // Convert to a distance.  Note that the speed of sound is specified in
  // cm/sec, so the duration is scaled from microsecondst o seconds.  The
  // factor of 2 accounts for the round-trip doubling the time.
  distance2 = distance1;
  distance1 = (duration * 1e-6 * SOUND_SPEED) / 2;
  vel = (distance2 - distance1);

  Serial.print("  Count: ");
  Serial.print(count);
  Serial.print("  Vel: ");
  Serial.print(vel);
  Serial.print("  Servo: ");
  Serial.print(angle);
  Serial.print("  Ping: ");
  Serial.print(duration);
  Serial.print(" usec   Distance: ");
  Serial.print(distance1);
  Serial.println(" cm");

  i = 1;



  // no movement needed
  if (angle >= 125) {
    delay(300);
  }

  // too close or too fast hand movement (at any distance).
  if (distance1 <= 10 || ((vel >= 60 || vel <= -60) && distance1 < 100)) {
    angle = 50;
    svo.write(angle);
    // shivering action
    for (int i = 0; i < 15; i++) {
      if (i % 2 == 0) {
        shaker.write(35);
        delay(90);
      }
      else {
        shaker.write(0);
        delay(90);
      }
    }
    delay(1000);
  }

  //close and slow hand movement (but not too close)
  else if ((distance1 >= 20 && angle < 125) || (distance1 > 10 && distance1 < 20 && (vel >= 0 && vel < 20 || vel <= 0 && vel > -20))) {
    angle = angle + i * 5;
    svo.write(angle);
    delay(300);
  }

  else {
    // if no pulse detected
    Serial.println("No ping.");
  }

  vel = (distance2 - distance1);
  // Allow a little extra time for the sonar to recover.
  delay(30);
}

// ================================================================================
// Ping function to run one measurement cycle using the sonar.  Returns a ping
// travel duration in microseconds, or 0 if no echo was observed.

long ping_sonar(void)
{
  // Generate a short trigger pulse.
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // Measure the pulse length
  return pulseIn(ECHO_PIN, HIGH, TIMEOUT);
}