From f822f1aa310bda62aafe1f0b0f7246aa7466f35d Mon Sep 17 00:00:00 2001 From: Henrik Ekblad Date: Fri, 15 Aug 2014 23:40:45 +0200 Subject: [PATCH] New example skecth which controls a lock either from controller or by using a secret knock. --- .../SecretKnockSensor/SecretKnockSensor.ino | 346 ++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 libraries/MySensors/examples/SecretKnockSensor/SecretKnockSensor.ino diff --git a/libraries/MySensors/examples/SecretKnockSensor/SecretKnockSensor.ino b/libraries/MySensors/examples/SecretKnockSensor/SecretKnockSensor.ino new file mode 100644 index 0000000..6fa8b59 --- /dev/null +++ b/libraries/MySensors/examples/SecretKnockSensor/SecretKnockSensor.ino @@ -0,0 +1,346 @@ +/* Secret Knock Trinket + Code for running a secret knock lock on the Adafruit Trinket. + + See full instructions here: + 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 A1 (Analog 1): A sound sensor 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 a 1. Relay which open door or lock or 2. transistor or 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 +*/ + +#include +#include + + +#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. +boolean programModeActive = false; // True if we're trying to program a new knock. + +bool lockStatus; +MySensor gw; +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 + + gw.begin(incomingMessage); + + gw.sendSketchInfo("Secret Knock", "1.0"); + gw.present(CHILD_ID, S_LOCK); + + 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 = gw.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 loop() { + gw.process(); // Process incomming messages + // 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 programing 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) == HIGH){ + 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. + int startTime = millis(); // Reference for when this knock started. + int now = millis(); + + 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 + 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 send){ + if (state) + Serial.println("open lock"); + else + Serial.println("close lock"); + if (send) + gw.send(lockMsg.set(state)); + + digitalWrite(ledPin, state); + digitalWrite(lockPin, state); + gw.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. +boolean 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 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; + int timeDiff = 0; + for (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; + int i; + reading = gw.loadState(1); + if (reading == eepromValid){ // only read EEPROM if the signature byte is correct. + for (int i=0; i < maximumKnocks ;i++){ + secretCode[i] = gw.loadState(i+2); + } + } +} + + +//saves a new pattern too eeprom +void saveSecretKnock(){ + gw.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++){ + gw.saveState(i+2, secretCode[i]); + } + gw.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 incomingMessage(const MyMessage &message) { + // We only expect one type of message from controller. But we better check anyway. + if (message.type==V_LOCK_STATUS) { + // Change relay state + setLockState(message.getBool(), false); + + // Write some debug info + Serial.print("Incoming lock status:"); + Serial.println(message.getBool()); + } +} +