-
/* Detects patterns of knocks and triggers a motor to unlock
-
it if the pattern is correct.
-
-
By Steve Hoefer http://grathio.com
-
Version 0.1.09.10.24
-
Licensed under Creative Commons Attribution-Noncommercial-Share Alike 3.0
-
-
Analog Pin 0: Piezo speaker (connected to ground with 1M pulldown resistor)
-
Digital Pin 2: Switch to enter a new code. Short this to enter programming mode.
-
Digital Pin 3: DC gear reduction motor attached to the lock. (Or a motor controller or
-
a solenoid or other unlocking mechanisim.)
-
Digital Pin 4: Green LED.
-
Digital Pin 5: Red LED.
-
*/
-
-
// Pin definitions
-
const int knockSensor = 0;
-
// Piezo sensor on pin 0.
-
const int programSwitch = 2;
-
// If this is high we program a new code.
-
const int lockMotor = 3;
-
// Gear motor used to turn the lock.
-
const int redLED = 4;
-
// Status LED
-
const int greenLED = 5;
-
// Status LED
-
-
// Tuning constants.
-
// Could be made vars and hooked to potentiometers for soft configuration, etc.
-
const int threshold = 4;
-
// Minimum signal from the piezo to register as a knock
-
const int rejectValue = 25;
-
// If an individual knock is off by this percentage of a knock we don’t unlock..
-
const int averageRejectValue = 15;
-
// If the average timing of the knocks is off by this percent we don’t unlock.
-
const int knockFadeTime = 150;
-
// milliseconds we allow a knock to fade before we listen for another one. (Debounce timer.)
-
const int lockTurnTime = 450;
-
// milliseconds that we run the motor to get it to go a half turn.
-
-
const int maximumKnocks = 20;
-
// Maximum number of knocks to listen for.
-
const int knockComplete = 1200;
-
// Longest time to wait for a knock before we assume that it’s finished.
-
-
// Variables.
-
int secretCode[maximumKnocks] = {50, 25, 25, 50, 100, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
// Initial setup: "Shave and a Hair Cut, two bits."
-
int knockReadings[maximumKnocks];
-
// When someone knocks this array fills with delays between knocks.
-
int knockSensorValue = 0; // Last reading of the knock sensor.
-
-
void setup() {
-
pinMode(knockSensor, OUTPUT);
-
pinMode(lockMotor, OUTPUT);
-
pinMode(redLED, OUTPUT);
-
pinMode(greenLED, OUTPUT);
-
pinMode(programSwitch, INPUT);
-
-
//Serial.begin(19200);
-
// Uncomment the Serial.bla lines for debugging.
-
//Serial.println("Program start."); // This line too.
-
-
digitalWrite(greenLED, HIGH);
-
// Green LED on, everything is go.
-
}
-
-
void loop() {
-
// Listen for any knock at all.
-
knockSensorValue = analogRead(knockSensor);
-
if (knockSensorValue >=threshold){
-
listenToSecretKnock();
-
}
-
}
-
-
// Records the timing of knocks.
-
void listenToSecretKnock(){
-
//Serial.println("knock starting"); // debug.
-
int i = 0;
-
// First lets reset the listening array.
-
for (i=0;i=threshold){ //got another knock…
-
//record the delay time.
-
now=millis();
-
knockReadings[currentKnockNumber] = now-startTime;
-
currentKnockNumber ++; //increment the counter
-
startTime=now;
-
// and reset our timer for the next knock
-
digitalWrite(greenLED, LOW);
-
delay(knockFadeTime);
-
// again, a little delay to let the knock decay.
-
digitalWrite(greenLED, HIGH);
-
}
-
-
now=millis();
-
-
//did we timeout or run out of knocks?
-
} while ((now-startTime < knockComplete) && (currentKnockNumber < maximumKnocks));
-
-
//we’ve got our knock recorded, lets see if it’s valid
-
-
if (validateKnock() == true){
-
triggerDoorUnlock();
-
} else {
-
//Serial.println("Secret knock failed.");
-
digitalWrite(greenLED, LOW);
-
// We didn’t unlock, so blink the red LED as visual feedback.
-
for (i=0;i<4;i++){
-
digitalWrite(redLED, HIGH);
-
delay(100);
-
digitalWrite(redLED, LOW);
-
delay(100);
-
}
-
digitalWrite(greenLED, HIGH);
-
}
-
}
-
-
// Runs the motor (or whatever) to unlock the door.
-
void triggerDoorUnlock(){
-
//Serial.println("Door unlocked!");
-
int i=0;
-
-
// turn the motor on for a bit.
-
digitalWrite(lockMotor, HIGH);
-
digitalWrite(greenLED, HIGH);
-
// And the green LED too.
-
-
delay (lockTurnTime); // Wait a bit.
-
-
digitalWrite(lockMotor, LOW); // Turn the motor off.
-
-
// Blink the green LED a few times for more visual feedback.
-
for (i=0; i < 5; i++){
-
digitalWrite(greenLED, LOW);
-
delay(100);
-
digitalWrite(greenLED, HIGH);
-
delay(100);
-
}
-
-
}
-
-
// Sees if our knock matches the secret.
-
// returns true if it’s a good knock, false if it’s not.
-
// todo: break it into smaller functions for readability.
-
boolean validateKnock(){
-
int i=0;
-
-
// simplest check first: Did we get the right number of knocks?
-
int currentKnockCount = 0;
-
int secretKnockCount = 0;
-
int maxKnockInterval = 0;
-
// We use this later to normalize the times.
-
-
for (i=0;i 0){
-
currentKnockCount++;
-
}
-
if (secretCode[i] > 0){ //todo: precalculate this.
-
secretKnockCount++;
-
}
-
-
if (knockReadings[i] > maxKnockInterval){
-
// collect normalization data while we’re looping.
-
maxKnockInterval = knockReadings[i];
-
}
-
}
-
-
// If we’re recording a new knock, save the info and get out of here.
-
if (digitalRead(programSwitch)==HIGH){
-
for (i=0;i< maximumKnocks ; i++){
-
digitalWrite(greenLED, LOW);
-
digitalWrite(redLED, LOW); // only turn it on if there’s a delay
-
if (secretCode[i] > 0){
-
delay( map(secretCode[i],0, 100, 0, maxKnockInterval));
-
// Expand the time back out to what it was. Roughly.
-
digitalWrite(greenLED, HIGH);
-
digitalWrite(redLED, HIGH);
-
}
-
delay(50);
-
}
-
return false;
-
// We don’t unlock the door when we are recording a new knock.
-
}
-
-
if (currentKnockCount != secretKnockCount){
-
return false;
-
}
-
-
/* Now we compare the relative intervals of our knocks, not the absolute time between them.
-
(ie: if you do the same pattern slow or fast it should still open the door.)
-
This makes it less picky, which while making it less secure can also make it
-
less of a pain to use if you’re tempo is a little slow or fast.
-
*/
-
int totaltimeDifferences=0;
-
int timeDiff=0;
-
for (i=0;i rejectValue){ // Individual value too far out of whack
-
return false;
-
}
-
totaltimeDifferences += timeDiff;
-
}
-
// It can also fail if the whole thing is too inaccurate.
-
if (totaltimeDifferences/secretKnockCount>averageRejectValue){
-
return false;
-
}
-
-
return true;
-
-
}