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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#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
  }