Секретный датчик удара, регистрирует удары и позволяет открыть дверь или сделать некоторые другие действия, если был введен правильный стук.
Последовательность ударов хранится в EEPROM датчика и запоминается даже после отключения питания.
Вы можете записать новую последовательность путем присоединения кнопки.
Демонстрация
Данное короткое видео показывает полную функциональность
Подключение
Начните с подключения радио.
Большинство внешних деталей не является обязательным (за исключением звукового датчика). Светодиод (или динамик/пьезо) указывает, что при перекодировании режим начинается/заканчивается и повторяется стук после того, как вы вошли в режим.
Датчики | Arduino | Комментарий |
---|---|---|
Кнопка | Pin 0 | Кнопка для записи новых ударов (от кнопки к GND) |
LED | Pin 1 | (Опционально) Светодиод статуса. Поставить резистор 510 Ом последовательно с светодиодом к +5В. |
Динамик | Pin 2 | (Опционально) Статус динамик или пьезо |
Реле или соленоид | Pin 4 | (Опционально) Подключите счто вы хотите (реле,соленоид) |
Sound sensor | Pin 5 | Использовать цифровой выход от звукового датчика. Не забудьте настроить чувствительность. |
Пример
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
/* * The MySensors Arduino library handles the wireless radio link and protocol * between your home built sensors/actuators and HA controller of choice. * The sensors forms a self healing radio network with optional repeaters. Each * repeater and gateway builds a routing tables in EEPROM which keeps track of the * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad <henrik.ekblad@mysensors.org> * Copyright (C) 2013-2019 Sensnology AB * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors * * Documentation: http://www.mysensors.org * Support Forum: http://forum.mysensors.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * ******************************* * * REVISION HISTORY * Version 1.0 - Henrik Ekblad * * DESCRIPTION * * Secret Knock Sensor * http://www.mysensors.org/build/knock * * See original instructions here (note: The MySensors adopted code might differ in wiring. The instructions below is correct): * https://learn.adafruit.com/secret-knock-activated-drawer-lock/ * Version 13.10.31 Built with Arduino IDE 1.0.5 * * By Steve Hoefer http://grathio.com * Adapted to MySensors by Henrik Ekblad * * Licensed under Creative Commons Attribution-Noncommercial-Share Alike 3.0 * http://creativecommons.org/licenses/by-nc-sa/3.0/us/ * (In short: Do what you want, as long as you credit me, don't relicense it, and don't sell it or use it in anything you sell without contacting me.) * * ------Wiring------ * Pin 0: Program button used for recording a new Knock (connect Pin0 -> button -> GND) * Pin 1: Optional: Connect LED here (remember resisor in series) * Pin 2: Optional: Piezo element (for beeps). * Pin 5: A sound sensor (digital output) for sensing knocks. See MySensors purchase guide. I used this: http://rover.ebay.com/rover/1/711-53200-19255-0/1?icep_ff3=2&pub=5575069610&toolid=10001&campid=5337433187&customid=&icep_item=200941260251&ipn=psmain&icep_vectorid=229466&kwid=902099&mtid=824&kw=lg * Pin 4: Connects to either 1. Relay which open door or lock or * 2. transistor that opens a solenoid lock when HIGH (see adafruit guide for this option). * * * Connect radio according as usual(you can skip IRQ pin) * http://www.mysensors.org/build/connect_radio */ // Enable debug prints to serial monitor #define MY_DEBUG // Enable and select radio type attached #define MY_RADIO_RF24 //#define MY_RADIO_NRF5_ESB //#define MY_RADIO_RFM69 //#define MY_RADIO_RFM95 #include <MySensors.h> #define CHILD_ID 99 // Id of the sensor child const byte eepromValid = 121; // If the first byte in eeprom is this then the data is valid. /*Pin definitions*/ const int programButton = 0; // (Digital 0) Record A New Knock button. const int ledPin = 1; // (Digital 1) The LED pin (if any) const int knockSensor = 5; // (Digital 5) for using the microphone digital output (tune knob to register knock) const int audioOut = 2; // (Digital 2) for using the peizo as an output device. (Thing that goes beep.) const int lockPin = 4; // (Digital 4) The pin that activates the relay/solenoid lock. /*Tuning constants. Changing the values below changes the behavior of the device.*/ int threshold = 3; // Minimum signal from the piezo to register as a knock. Higher = less sensitive. Typical values 1 - 10 const int rejectValue = 25; // If an individual knock is off by this percentage of a knock we don't unlock. Typical values 10-30 const int averageRejectValue = 15; // If the average timing of all the knocks is off by this percent we don't unlock. Typical values 5-20 const int knockFadeTime = 150; // Milliseconds we allow a knock to fade before we listen for another one. (Debounce timer.) const int lockOperateTime = 2500; // Milliseconds that we operate the lock solenoid latch before releasing it. 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. (milliseconds) byte 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 the delays between knocks. int knockSensorValue = 0; // Last reading of the knock sensor. bool programModeActive = false; // True if we're trying to program a new knock. bool lockStatus; MyMessage lockMsg(CHILD_ID, V_LOCK_STATUS); void setup() { pinMode(ledPin, OUTPUT); pinMode(knockSensor, INPUT); pinMode(lockPin, OUTPUT); pinMode(programButton, INPUT); digitalWrite(programButton, HIGH); // Enable internal pull up readSecretKnock(); // Load the secret knock (if any) from EEPROM. digitalWrite(lockPin, HIGH); // Unlock the door for a bit when we power up. For system check and to allow a way in if the key is forgotten delay(500); // Wait a short time lockStatus = loadState(0); // Read last lock status from eeprom setLockState(lockStatus, true); // Now set the last known state and send it to controller delay(500); // This delay is here because the solenoid lock returning to place can otherwise trigger and inadvertent knock. } void presentation() { sendSketchInfo("Secret Knock", "1.0"); present(CHILD_ID, S_LOCK); } void loop() { // Listen for any knock at all. knockSensorValue = digitalRead(knockSensor); if (digitalRead(programButton) == LOW) { // is the program button pressed? delay(100); // Cheap debounce. if (digitalRead(programButton) == LOW) { if (programModeActive == false) { // If we're not in programming mode, turn it on. programModeActive = true; // Remember we're in programming mode. digitalWrite(ledPin, HIGH); // Turn on the red light too so the user knows we're programming. chirp(500, 1500); // And play a tone in case the user can't see the LED. chirp(500, 1000); } else { // If we are in programming mode, turn it off. programModeActive = false; digitalWrite(ledPin, LOW); chirp(500, 1000); // Turn off the programming LED and play a sad note. chirp(500, 1500); delay(500); } while (digitalRead(programButton) == LOW) { delay(10); // Hang around until the button is released. } } delay(250); // Another cheap debounce. Longer because releasing the button can sometimes be sensed as a knock. } if (knockSensorValue == 0) { if (programModeActive == true) { // Blink the LED when we sense a knock. digitalWrite(ledPin, LOW); } else { digitalWrite(ledPin, HIGH); } knockDelay(); if (programModeActive == true) { // Un-blink the LED. digitalWrite(ledPin, HIGH); } else { digitalWrite(ledPin, LOW); } listenToSecretKnock(); // We have our first knock. Go and see what other knocks are in store... } } // Records the timing of knocks. void listenToSecretKnock() { int i = 0; // First reset the listening array. for (i=0; i < maximumKnocks; i++) { knockReadings[i] = 0; } int currentKnockNumber = 0; // Position counter for the array. uint32_t startTime = millis(); // Reference for when this knock started. uint32_t now; do { // Listen for the next knock or wait for it to timeout. knockSensorValue = digitalRead(knockSensor); if (knockSensorValue == 0) { // Here's another knock. Save the time between knocks. Serial.println("knock"); now=millis(); knockReadings[currentKnockNumber] = now - startTime; currentKnockNumber ++; startTime = now; if (programModeActive==true) { // Blink the LED when we sense a knock. digitalWrite(ledPin, LOW); } else { digitalWrite(ledPin, HIGH); } knockDelay(); if (programModeActive == true) { // Un-blink the LED. digitalWrite(ledPin, HIGH); } else { digitalWrite(ledPin, LOW); } } now = millis(); // Stop listening if there are too many knocks or there is too much time between knocks. } while ((now-startTime < knockComplete) && (currentKnockNumber < maximumKnocks)); Serial.println("end"); //we've got our knock recorded, lets see if it's valid if (programModeActive == false) { // Only do this if we're not recording a new knock. if (validateKnock() == true) { // Lock/unlock door chirp(500, 1500); // And play a tone in case the user can't see the LED. chirp(500, 1000); setLockState(!lockStatus, true); } else { Serial.println("fail unlock"); // knock is invalid. Blink the LED as a warning to others. for (i=0; i < 4; i++) { digitalWrite(ledPin, HIGH); delay(50); digitalWrite(ledPin, LOW); delay(50); } } } else { // If we're in programming mode we still validate the lock because it makes some numbers we need, we just don't do anything with the return. validateKnock(); } } // Unlocks the door. void setLockState(bool state, bool doSend) { if (state) { Serial.println("open lock"); } else { Serial.println("close lock"); } if (doSend) { send(lockMsg.set(state)); } digitalWrite(ledPin, state); digitalWrite(lockPin, state); saveState(0,state); lockStatus = state; delay(500); // This delay is here because releasing the latch can cause a vibration that will be sensed as a knock. } // Checks to see if our knock matches the secret. // Returns true if it's a good knock, false if it's not. bool validateKnock() { int i = 0; int currentKnockCount = 0; int secretKnockCount = 0; int maxKnockInterval = 0; // We use this later to normalize the times. for (i=0; i<maximumKnocks; i++) { if (knockReadings[i] > 0) { currentKnockCount++; } if (secretCode[i] > 0) { 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 (programModeActive == true) { for (i=0; i < maximumKnocks; i++) { // Normalize the time between knocks. (the longest time = 100) secretCode[i] = map(knockReadings[i], 0, maxKnockInterval, 0, 100); } saveSecretKnock(); // save the result to EEPROM programModeActive = false; playbackKnock(maxKnockInterval); return false; } if (currentKnockCount != secretKnockCount) { // Easiest check first. If the number of knocks is wrong, don't unlock. 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; for (int timeDiff = 0, i=0; i < maximumKnocks; i++) { // Normalize the times knockReadings[i]= map(knockReadings[i], 0, maxKnockInterval, 0, 100); timeDiff = abs(knockReadings[i] - secretCode[i]); if (timeDiff > rejectValue) { // Individual value too far out of whack. No access for this knock! return false; } totaltimeDifferences += timeDiff; } // It can also fail if the whole thing is too inaccurate. if (totaltimeDifferences / secretKnockCount > averageRejectValue) { return false; } return true; } // reads the secret knock from EEPROM. (if any.) void readSecretKnock() { byte reading; reading = loadState(1); if (reading == eepromValid) { // only read EEPROM if the signature byte is correct. for (int i=0; i < maximumKnocks ; i++) { secretCode[i] = loadState(i+2); } } } //saves a new pattern too eeprom void saveSecretKnock() { saveState(1, 0); // clear out the signature. That way we know if we didn't finish the write successfully. for (int i=0; i < maximumKnocks; i++) { saveState(i+2, secretCode[i]); } saveState(1, eepromValid); // all good. Write the signature so we'll know it's all good. } // Plays back the pattern of the knock in blinks and beeps void playbackKnock(int maxKnockInterval) { digitalWrite(ledPin, LOW); delay(1000); digitalWrite(ledPin, HIGH); chirp(200, 1800); for (int i = 0; i < maximumKnocks ; i++) { digitalWrite(ledPin, 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(ledPin, HIGH); chirp(200, 1800); } } digitalWrite(ledPin, LOW); } // Deals with the knock delay thingy. void knockDelay() { int itterations = (knockFadeTime / 20); // Wait for the peak to dissipate before listening to next one. for (int i=0; i < itterations; i++) { delay(10); analogRead( knockSensor); // This is done in an attempt to defuse the analog sensor's capacitor that will give false readings on high impedance sensors. delay(10); } } // Plays a non-musical tone on the piezo. // playTime = milliseconds to play the tone // delayTime = time in microseconds between ticks. (smaller=higher pitch tone.) void chirp(int playTime, int delayTime) { long loopTime = (playTime * 1000L) / delayTime; pinMode(audioOut, OUTPUT); for(int i=0; i < loopTime; i++) { digitalWrite(audioOut, HIGH); delayMicroseconds(delayTime); digitalWrite(audioOut, LOW); } pinMode(audioOut, INPUT); } void receive(const MyMessage &message) { // We only expect one type of message from controller. But we better check anyway. if (message.getType()==V_LOCK_STATUS) { // Change relay state setLockState(message.getBool(), false); // Write some debug info Serial.print("Incoming lock status:"); Serial.println(message.getBool()); } } |