├── README.md └── knock_lock_custom.ino /README.md: -------------------------------------------------------------------------------- 1 | arduino-secret-knock 2 | ==================== 3 | 4 | Arduino code for implementing knock detection. 5 | -------------------------------------------------------------------------------- /knock_lock_custom.ino: -------------------------------------------------------------------------------- 1 | /* Detects patterns of knocks and triggers a motor to unlock 2 | it if the pattern is correct. 3 | 4 | Original Code: 5 | By Steve Hoefer http://grathio.com 6 | Version 0.1.10.20.10 7 | Licensed under Creative Commons Attribution-Noncommercial-Share Alike 3.0 8 | http://creativecommons.org/licenses/by-nc-sa/3.0/us/ 9 | (In short: Do what you want, just be sure to include this line and the four above it, and don't sell it or use it in anything you sell without contacting me.) 10 | 11 | Altered by Charles Lai to use a servo motor and slightly different behavior 12 | 13 | Analog Pin 0: Piezo speaker (connected to ground with 1M pulldown resistor) 14 | Digital Pin 2: Switch to enter a new code. Short this to enter programming mode. 15 | Digital Pin 3: DC gear reduction motor attached to the lock. (Or a motor controller or a solenoid or other unlocking mechanisim.) 16 | Digital Pin 4: Red LED. 17 | Digital Pin 5: Green LED. 18 | 19 | */ 20 | #include 21 | 22 | // Servo Library 23 | Servo myServo; 24 | 25 | // Pin definitions 26 | const int knockSensor = 0; // Piezo sensor on pin 0. 27 | const int programSwitch = 2; // If this is high we program a new code. 28 | const int lockMotor = 9; // Gear motor used to turn the lock. 29 | const int redLED = 5; // Status LED 30 | const int greenLED = 4; // Status LED 31 | const int yellowLED = 3; // Status LED 32 | 33 | // Tuning constants. Could be made vars and hoooked to potentiometers for soft configuration, etc. 34 | const int threshold = 3; // Minimum signal from the piezo to register as a knock 35 | const int rejectValue = 25; // If an individual knock is off by this percentage of a knock we don't unlock.. 36 | const int averageRejectValue = 15; // If the average timing of the knocks is off by this percent we don't unlock. 37 | const int knockFadeTime = 150; // milliseconds we allow a knock to fade before we listen for another one. (Debounce timer.) 38 | const int enterTime = 100; // seconds we wait before we lock the door 39 | 40 | const int maximumKnocks = 20; // Maximum number of knocks to listen for. 41 | const int knockComplete = 1200; // Longest time to wait for a knock before we assume that it's finished. 42 | 43 | 44 | // Variables. 45 | 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." 46 | int knockReadings[maximumKnocks]; // When someone knocks this array fills with delays between knocks. 47 | int knockSensorValue = 0; // Last reading of the knock sensor. 48 | int programButtonPressed = false; // Flag so we remember the programming button setting at the end of the cycle. 49 | 50 | void setup() { 51 | myServo.attach(9); 52 | pinMode(redLED, OUTPUT); 53 | pinMode(greenLED, OUTPUT); 54 | pinMode(yellowLED, OUTPUT); 55 | pinMode(programSwitch, INPUT); 56 | myServo.write(90); 57 | 58 | Serial.begin(9600); // Uncomment the Serial.bla lines for debugging. 59 | Serial.println("Program start."); // but feel free to comment them out after it's working right. 60 | 61 | digitalWrite(greenLED, HIGH); // Green LED on, everything is go. 62 | } 63 | 64 | void loop() { 65 | // Listen for any knock at all. 66 | knockSensorValue = analogRead(knockSensor); 67 | 68 | if (digitalRead(programSwitch)==HIGH){ // is the program button pressed? 69 | programButtonPressed = true; // Yes, so lets save that state 70 | digitalWrite(redLED, HIGH); // and turn on the red light too so we know we're programming. 71 | } else { 72 | programButtonPressed = false; 73 | digitalWrite(redLED, LOW); 74 | } 75 | 76 | if (knockSensorValue >=threshold){ 77 | listenToSecretKnock(); 78 | } 79 | } 80 | 81 | // Records the timing of knocks. 82 | void listenToSecretKnock(){ 83 | Serial.println("knock starting"); 84 | 85 | int i = 0; 86 | // First lets reset the listening array. 87 | for (i=0;i=threshold){ //got another knock... 108 | //record the delay time. 109 | Serial.println("knock."); 110 | now=millis(); 111 | knockReadings[currentKnockNumber] = now-startTime; 112 | currentKnockNumber ++; //increment the counter 113 | startTime=now; 114 | // and reset our timer for the next knock 115 | digitalWrite(greenLED, LOW); 116 | if (programButtonPressed==true){ 117 | digitalWrite(redLED, LOW); // and the red one too if we're programming a new knock. 118 | } 119 | delay(knockFadeTime); // again, a little delay to let the knock decay. 120 | digitalWrite(greenLED, HIGH); 121 | if (programButtonPressed==true){ 122 | digitalWrite(redLED, HIGH); 123 | } 124 | } 125 | 126 | now=millis(); 127 | 128 | //did we timeout or run out of knocks? 129 | } while ((now-startTime < knockComplete) && (currentKnockNumber < maximumKnocks)); 130 | 131 | //we've got our knock recorded, lets see if it's valid 132 | if (programButtonPressed==false){ // only if we're not in progrmaing mode. 133 | if (validateKnock() == true){ 134 | triggerDoorUnlock(); 135 | } else { 136 | Serial.println("Secret knock failed."); 137 | digitalWrite(greenLED, LOW); // We didn't unlock, so blink the red LED as visual feedback. 138 | for (i=0;i<4;i++){ 139 | digitalWrite(redLED, HIGH); 140 | delay(100); 141 | digitalWrite(redLED, LOW); 142 | delay(100); 143 | } 144 | digitalWrite(greenLED, HIGH); 145 | } 146 | } else { // if we're in programming mode we still validate the lock, we just don't do anything with the lock 147 | validateKnock(); 148 | // and we blink the green and red alternately to show that program is complete. 149 | Serial.println("New lock stored."); 150 | digitalWrite(redLED, LOW); 151 | digitalWrite(greenLED, HIGH); 152 | for (i=0;i<3;i++){ 153 | delay(100); 154 | digitalWrite(redLED, HIGH); 155 | digitalWrite(greenLED, LOW); 156 | delay(100); 157 | digitalWrite(redLED, LOW); 158 | digitalWrite(greenLED, HIGH); 159 | } 160 | } 161 | } 162 | 163 | 164 | // Runs the motor (or whatever) to unlock the door. 165 | void triggerDoorUnlock(){ 166 | Serial.println("Door unlocked!"); 167 | int i=0; 168 | 169 | // turn the servo and unlock the door 170 | myServo.write(0); 171 | 172 | // Blink the green LED a few times for more visual feedback. 173 | for (i=0; i < 5; i++){ 174 | digitalWrite(greenLED, LOW); 175 | delay(100); 176 | digitalWrite(greenLED, HIGH); 177 | delay(100); 178 | } 179 | 180 | // While the button is not pressed, keep the door unlocked 181 | while (digitalRead(programSwitch)==LOW) { 182 | 183 | } 184 | 185 | // wait a bit before locking the door 186 | delay (enterTime); 187 | 188 | // Blink the green LED a few times for more visual feedback. 189 | for (i=0; i < 5; i++){ 190 | digitalWrite(yellowLED, HIGH); 191 | delay(100); 192 | digitalWrite(yellowLED, LOW); 193 | delay(100); 194 | } 195 | 196 | // Lock the door again 197 | myServo.write(90); 198 | 199 | } 200 | 201 | // Sees if our knock matches the secret. 202 | // returns true if it's a good knock, false if it's not. 203 | // todo: break it into smaller functions for readability. 204 | boolean validateKnock(){ 205 | int i=0; 206 | 207 | // simplest check first: Did we get the right number of knocks? 208 | int currentKnockCount = 0; 209 | int secretKnockCount = 0; 210 | int maxKnockInterval = 0; // We use this later to normalize the times. 211 | 212 | for (i=0;i 0){ 214 | currentKnockCount++; 215 | } 216 | if (secretCode[i] > 0){ //todo: precalculate this. 217 | secretKnockCount++; 218 | } 219 | 220 | if (knockReadings[i] > maxKnockInterval){ // collect normalization data while we're looping. 221 | maxKnockInterval = knockReadings[i]; 222 | } 223 | } 224 | 225 | // If we're recording a new knock, save the info and get out of here. 226 | if (programButtonPressed==true){ 227 | for (i=0;i 0){ 242 | delay( map(secretCode[i],0, 100, 0, maxKnockInterval)); // Expand the time back out to what it was. Roughly. 243 | digitalWrite(greenLED, HIGH); 244 | digitalWrite(redLED, HIGH); 245 | } 246 | delay(50); 247 | } 248 | return false; // We don't unlock the door when we are recording a new knock. 249 | } 250 | 251 | if (currentKnockCount != secretKnockCount){ 252 | return false; 253 | } 254 | 255 | /* Now we compare the relative intervals of our knocks, not the absolute time between them. 256 | (ie: if you do the same pattern slow or fast it should still open the door.) 257 | This makes it less picky, which while making it less secure can also make it 258 | less of a pain to use if you're tempo is a little slow or fast. 259 | */ 260 | int totaltimeDifferences=0; 261 | int timeDiff=0; 262 | for (i=0;i rejectValue){ // Individual value too far out of whack 266 | return false; 267 | } 268 | totaltimeDifferences += timeDiff; 269 | } 270 | // It can also fail if the whole thing is too inaccurate. 271 | if (totaltimeDifferences/secretKnockCount>averageRejectValue){ 272 | return false; 273 | } 274 | 275 | return true; 276 | 277 | } 278 | --------------------------------------------------------------------------------