Team: Marione & Max (maluoch & mkornyev)

Objectives:
We wanted to create a retractable keyholder that would open when someone went near it to grab their key, and closed accordingly. However, we wanted the device to also communicate something from a remote presence:

If a person were to walk up close to the keyholder, that would mean that they wanted to invite their friend to come over.

To convey this, we wanted the friend’s device to wiggle, signaling that they should come over. Our program sends a binary message through the cks server to trigger the remote servo appropriately.

Outcomes:
The outcome was originally ineffective. It would be much too aggressive in moving keyholder arm, and would respond to all erroneous sensor input.

We iterated on our design, and applied a smoothing filter to correct the outliers in our input. Our final prototype also used a smooth servo arm motion to deliver the keys.

These outcomes can be seen below:

#include <Servo.h&gt;

// PINS
const int SERVO_PIN = 6;
const int ECHO_PIN = 7;
const int TRIGGER_PIN = 8;

// CONSTANTS
const int CLOSED_ANGLE = 120;
const int OPENED_ANGLE = 30;
const int ACTIVE_DISTANCE = 60; // Centimeters
const int DELAY_MILLIS = 400;
const int OPEN = 1;
const int CLOSE = 0;

// GLOBALS
int prev_value = 0;
int prevprev_value = 0;
float cm = 0;
Servo s;

void setup()
{
  Serial.begin(115200);
  
  // Servo setup
  pinMode(SERVO_PIN, OUTPUT);
  s.attach(SERVO_PIN);
  s.write(CLOSED_ANGLE);
  
  // Ranger setup 
  pinMode(TRIGGER_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
}

void loop()
{
  pollHardwareInput();
  pollRemoteInput();

  delay(DELAY_MILLIS);
}

void pollHardwareInput() {
  cm = 0.01723 * readUltrasonicDistance();

  if (humanIsNear(cm) &amp;&amp; s.read() == CLOSED_ANGLE) {
    moveServo(OPEN);
  }

  if (!humanIsNear(cm) &amp;&amp; s.read() == OPENED_ANGLE) {
    moveServo(CLOSE);
  }
}

void pollRemoteInput() {
  while(Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH);
    int value = Serial.parseInt();
    Serial.find('\n');

    // REMOTE PRESENCE: Wiggle the servo 
    int currAngle = s.read();

    for(int i=0; i<2; i++) {
      s.write(currAngle - 20);
      delay(100);
      s.write(currAngle);
      delay(100); 
    }
  }
  digitalWrite(LED_BUILTIN, LOW);
}

void moveServo(int openServo) {
  if(openServo == OPEN) {
    Serial.println(OPEN); // TRANSMIT: A person has approached
    for(int i = CLOSED_ANGLE; i &gt;= OPENED_ANGLE; i--) {
      s.write(i);
      delay(10);
    }
  } else {
    Serial.println(CLOSE); // TRANSMIT: A person has left 
    for(int i = OPENED_ANGLE; i <= CLOSED_ANGLE; i++) {
      s.write(i);
      delay(10);
    }
  }  
}

bool humanIsNear(int distance) {
  // Apply filter 
  distance = medianFilter(distance);
  
  if (distance < ACTIVE_DISTANCE) {
    return true;
  }
  return false;
}

long readUltrasonicDistance()
{
  digitalWrite(TRIGGER_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIGGER_PIN, LOW);
  return pulseIn(ECHO_PIN, HIGH);
}

// Median Filter Code
// Adopted from Gatrth Zeglin @ CMU 

int medianFilter(int curr_value)
{
  static int prevprev_value = 0, prev_value = 0;
  int median = medianOfThree(prevprev_value, prev_value, curr_value);
  prevprev_value = prev_value;
  prev_value = curr_value;
  return median;
}

int medianOfThree(int a, int b, int c)
{
  if (a < b) {
    if (b < c)      return b; // ABC
    else if (a < c) return c; // ACB
    else            return a; // CAB
  } else {
    if (a < c)      return a; // BAC
    else if (b < c) return c; // BCA
    else            return b; // CBA
  }    
}