Skip to content

Commit f822f1a

Browse files
committed
New example skecth which controls a lock either from controller or by using a secret knock.
1 parent 8e53f75 commit f822f1a

File tree

1 file changed

+346
-0
lines changed

1 file changed

+346
-0
lines changed
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
/* Secret Knock Trinket
2+
Code for running a secret knock lock on the Adafruit Trinket.
3+
4+
See full instructions here:
5+
https://learn.adafruit.com/secret-knock-activated-drawer-lock/
6+
Version 13.10.31 Built with Arduino IDE 1.0.5
7+
8+
By Steve Hoefer http://grathio.com
9+
Adapted to MySensors by Henrik Ekblad
10+
11+
Licensed under Creative Commons Attribution-Noncommercial-Share Alike 3.0
12+
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
13+
(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.)
14+
15+
------Wiring------
16+
Pin 0: Program button used for recording a new Knock (connect Pin0 -> button -> GND)
17+
Pin 1: Optional: Connect LED here (remember resisor in series)
18+
Pin 2: Optional: Piezo element (for beeps).
19+
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
20+
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).
21+
22+
Connect radio according as usual(you can skip IRQ pin)
23+
http://www.mysensors.org/build/connect_radio
24+
*/
25+
26+
#include <MySensor.h>
27+
#include <SPI.h>
28+
29+
30+
#define CHILD_ID 99 // Id of the sensor child
31+
32+
const byte eepromValid = 121; // If the first byte in eeprom is this then the data is valid.
33+
34+
/*Pin definitions*/
35+
const int programButton = 0; // (Digital 0) Record A New Knock button.
36+
const int ledPin = 1; // (Digital 1) The LED pin (if any)
37+
const int knockSensor = 5; // (Digital 5) for using the microphone digital output (tune knob to register knock)
38+
const int audioOut = 2; // (Digital 2) for using the peizo as an output device. (Thing that goes beep.)
39+
const int lockPin = 4; // (Digital 4) The pin that activates the relay/solenoid lock.
40+
41+
/*Tuning constants. Changing the values below changes the behavior of the device.*/
42+
int threshold = 3; // Minimum signal from the piezo to register as a knock. Higher = less sensitive. Typical values 1 - 10
43+
const int rejectValue = 25; // If an individual knock is off by this percentage of a knock we don't unlock. Typical values 10-30
44+
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
45+
const int knockFadeTime = 150; // Milliseconds we allow a knock to fade before we listen for another one. (Debounce timer.)
46+
const int lockOperateTime = 2500; // Milliseconds that we operate the lock solenoid latch before releasing it.
47+
const int maximumKnocks = 20; // Maximum number of knocks to listen for.
48+
const int knockComplete = 1200; // Longest time to wait for a knock before we assume that it's finished. (milliseconds)
49+
50+
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."
51+
int knockReadings[maximumKnocks]; // When someone knocks this array fills with the delays between knocks.
52+
int knockSensorValue = 0; // Last reading of the knock sensor.
53+
boolean programModeActive = false; // True if we're trying to program a new knock.
54+
55+
bool lockStatus;
56+
MySensor gw;
57+
MyMessage lockMsg(CHILD_ID, V_LOCK_STATUS);
58+
59+
60+
void setup() {
61+
62+
pinMode(ledPin, OUTPUT);
63+
pinMode(knockSensor, INPUT);
64+
pinMode(lockPin, OUTPUT);
65+
pinMode(programButton, INPUT);
66+
digitalWrite(programButton, HIGH); // Enable internal pull up
67+
68+
gw.begin(incomingMessage);
69+
70+
gw.sendSketchInfo("Secret Knock", "1.0");
71+
gw.present(CHILD_ID, S_LOCK);
72+
73+
readSecretKnock(); // Load the secret knock (if any) from EEPROM.
74+
75+
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
76+
delay(500); // Wait a short time
77+
78+
lockStatus = gw.loadState(0); // Read last lock status from eeprom
79+
setLockState(lockStatus, true); // Now set the last known state and send it to controller
80+
81+
delay(500); // This delay is here because the solenoid lock returning to place can otherwise trigger and inadvertent knock.
82+
}
83+
84+
void loop() {
85+
gw.process(); // Process incomming messages
86+
// Listen for any knock at all.
87+
knockSensorValue = digitalRead(knockSensor);
88+
if (digitalRead(programButton) == LOW){ // is the program button pressed?
89+
delay(100); // Cheap debounce.
90+
if (digitalRead(programButton) == LOW){
91+
if (programModeActive == false){ // If we're not in programming mode, turn it on.
92+
programModeActive = true; // Remember we're in programming mode.
93+
digitalWrite(ledPin, HIGH); // Turn on the red light too so the user knows we're programming.
94+
chirp(500, 1500); // And play a tone in case the user can't see the LED.
95+
chirp(500, 1000);
96+
} else { // If we are in programing mode, turn it off.
97+
programModeActive = false;
98+
digitalWrite(ledPin, LOW);
99+
chirp(500, 1000); // Turn off the programming LED and play a sad note.
100+
chirp(500, 1500);
101+
delay(500);
102+
}
103+
while (digitalRead(programButton) == HIGH){
104+
delay(10); // Hang around until the button is released.
105+
}
106+
}
107+
delay(250); // Another cheap debounce. Longer because releasing the button can sometimes be sensed as a knock.
108+
}
109+
110+
111+
if (knockSensorValue == 0) {
112+
if (programModeActive == true){ // Blink the LED when we sense a knock.
113+
digitalWrite(ledPin, LOW);
114+
} else {
115+
digitalWrite(ledPin, HIGH);
116+
}
117+
knockDelay();
118+
if (programModeActive == true){ // Un-blink the LED.
119+
digitalWrite(ledPin, HIGH);
120+
} else {
121+
digitalWrite(ledPin, LOW);
122+
}
123+
listenToSecretKnock(); // We have our first knock. Go and see what other knocks are in store...
124+
}
125+
126+
}
127+
128+
// Records the timing of knocks.
129+
void listenToSecretKnock(){
130+
int i = 0;
131+
// First reset the listening array.
132+
for (i=0; i < maximumKnocks; i++){
133+
knockReadings[i] = 0;
134+
}
135+
136+
int currentKnockNumber = 0; // Position counter for the array.
137+
int startTime = millis(); // Reference for when this knock started.
138+
int now = millis();
139+
140+
do { // Listen for the next knock or wait for it to timeout.
141+
knockSensorValue = digitalRead(knockSensor);
142+
143+
if (knockSensorValue == 0){ // Here's another knock. Save the time between knocks.
144+
Serial.println("knock");
145+
146+
now=millis();
147+
knockReadings[currentKnockNumber] = now - startTime;
148+
currentKnockNumber ++;
149+
startTime = now;
150+
151+
if (programModeActive==true){ // Blink the LED when we sense a knock.
152+
digitalWrite(ledPin, LOW);
153+
} else {
154+
digitalWrite(ledPin, HIGH);
155+
}
156+
knockDelay();
157+
if (programModeActive == true){ // Un-blink the LED.
158+
digitalWrite(ledPin, HIGH);
159+
} else {
160+
digitalWrite(ledPin, LOW);
161+
}
162+
}
163+
164+
now = millis();
165+
166+
// Stop listening if there are too many knocks or there is too much time between knocks.
167+
} while ((now-startTime < knockComplete) && (currentKnockNumber < maximumKnocks));
168+
Serial.println("end");
169+
170+
//we've got our knock recorded, lets see if it's valid
171+
if (programModeActive == false){ // Only do this if we're not recording a new knock.
172+
if (validateKnock() == true){
173+
// Lock/unlock door
174+
setLockState(!lockStatus, true);
175+
} else {
176+
Serial.println("fail unlock");
177+
178+
// knock is invalid. Blink the LED as a warning to others.
179+
for (i=0; i < 4; i++){
180+
digitalWrite(ledPin, HIGH);
181+
delay(50);
182+
digitalWrite(ledPin, LOW);
183+
delay(50);
184+
}
185+
}
186+
} 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.
187+
validateKnock();
188+
}
189+
}
190+
191+
192+
// Unlocks the door.
193+
void setLockState(bool state, bool send){
194+
if (state)
195+
Serial.println("open lock");
196+
else
197+
Serial.println("close lock");
198+
if (send)
199+
gw.send(lockMsg.set(state));
200+
201+
digitalWrite(ledPin, state);
202+
digitalWrite(lockPin, state);
203+
gw.saveState(0,state);
204+
lockStatus = state;
205+
delay(500); // This delay is here because releasing the latch can cause a vibration that will be sensed as a knock.
206+
}
207+
208+
// Checks to see if our knock matches the secret.
209+
// Returns true if it's a good knock, false if it's not.
210+
boolean validateKnock(){
211+
int i = 0;
212+
213+
int currentKnockCount = 0;
214+
int secretKnockCount = 0;
215+
int maxKnockInterval = 0; // We use this later to normalize the times.
216+
217+
for (i=0;i<maximumKnocks;i++){
218+
if (knockReadings[i] > 0){
219+
currentKnockCount++;
220+
}
221+
if (secretCode[i] > 0){
222+
secretKnockCount++;
223+
}
224+
225+
if (knockReadings[i] > maxKnockInterval){ // Collect normalization data while we're looping.
226+
maxKnockInterval = knockReadings[i];
227+
}
228+
}
229+
230+
// If we're recording a new knock, save the info and get out of here.
231+
if (programModeActive == true){
232+
for (i=0; i < maximumKnocks; i++){ // Normalize the time between knocks. (the longest time = 100)
233+
secretCode[i] = map(knockReadings[i], 0, maxKnockInterval, 0, 100);
234+
}
235+
saveSecretKnock(); // save the result to EEPROM
236+
programModeActive = false;
237+
playbackKnock(maxKnockInterval);
238+
return false;
239+
}
240+
241+
if (currentKnockCount != secretKnockCount){ // Easiest check first. If the number of knocks is wrong, don't unlock.
242+
return false;
243+
}
244+
245+
/* Now we compare the relative intervals of our knocks, not the absolute time between them.
246+
(ie: if you do the same pattern slow or fast it should still open the door.)
247+
This makes it less picky, which while making it less secure can also make it
248+
less of a pain to use if you're tempo is a little slow or fast.
249+
*/
250+
int totaltimeDifferences = 0;
251+
int timeDiff = 0;
252+
for (i=0; i < maximumKnocks; i++){ // Normalize the times
253+
knockReadings[i]= map(knockReadings[i], 0, maxKnockInterval, 0, 100);
254+
timeDiff = abs(knockReadings[i] - secretCode[i]);
255+
if (timeDiff > rejectValue){ // Individual value too far out of whack. No access for this knock!
256+
return false;
257+
}
258+
totaltimeDifferences += timeDiff;
259+
}
260+
// It can also fail if the whole thing is too inaccurate.
261+
if (totaltimeDifferences / secretKnockCount > averageRejectValue){
262+
return false;
263+
}
264+
265+
return true;
266+
}
267+
268+
269+
// reads the secret knock from EEPROM. (if any.)
270+
void readSecretKnock(){
271+
byte reading;
272+
int i;
273+
reading = gw.loadState(1);
274+
if (reading == eepromValid){ // only read EEPROM if the signature byte is correct.
275+
for (int i=0; i < maximumKnocks ;i++){
276+
secretCode[i] = gw.loadState(i+2);
277+
}
278+
}
279+
}
280+
281+
282+
//saves a new pattern too eeprom
283+
void saveSecretKnock(){
284+
gw.saveState(1, 0); // clear out the signature. That way we know if we didn't finish the write successfully.
285+
for (int i=0; i < maximumKnocks; i++){
286+
gw.saveState(i+2, secretCode[i]);
287+
}
288+
gw.saveState(1, eepromValid); // all good. Write the signature so we'll know it's all good.
289+
}
290+
291+
// Plays back the pattern of the knock in blinks and beeps
292+
void playbackKnock(int maxKnockInterval){
293+
digitalWrite(ledPin, LOW);
294+
delay(1000);
295+
digitalWrite(ledPin, HIGH);
296+
chirp(200, 1800);
297+
for (int i = 0; i < maximumKnocks ; i++){
298+
digitalWrite(ledPin, LOW);
299+
// only turn it on if there's a delay
300+
if (secretCode[i] > 0){
301+
delay(map(secretCode[i], 0, 100, 0, maxKnockInterval)); // Expand the time back out to what it was. Roughly.
302+
digitalWrite(ledPin, HIGH);
303+
chirp(200, 1800);
304+
}
305+
}
306+
digitalWrite(ledPin, LOW);
307+
}
308+
309+
// Deals with the knock delay thingy.
310+
void knockDelay(){
311+
int itterations = (knockFadeTime / 20); // Wait for the peak to dissipate before listening to next one.
312+
for (int i=0; i < itterations; i++){
313+
delay(10);
314+
analogRead(knockSensor); // This is done in an attempt to defuse the analog sensor's capacitor that will give false readings on high impedance sensors.
315+
delay(10);
316+
}
317+
}
318+
319+
// Plays a non-musical tone on the piezo.
320+
// playTime = milliseconds to play the tone
321+
// delayTime = time in microseconds between ticks. (smaller=higher pitch tone.)
322+
void chirp(int playTime, int delayTime){
323+
long loopTime = (playTime * 1000L) / delayTime;
324+
pinMode(audioOut, OUTPUT);
325+
for(int i=0; i < loopTime; i++){
326+
digitalWrite(audioOut, HIGH);
327+
delayMicroseconds(delayTime);
328+
digitalWrite(audioOut, LOW);
329+
}
330+
pinMode(audioOut, INPUT);
331+
}
332+
333+
334+
335+
void incomingMessage(const MyMessage &message) {
336+
// We only expect one type of message from controller. But we better check anyway.
337+
if (message.type==V_LOCK_STATUS) {
338+
// Change relay state
339+
setLockState(message.getBool(), false);
340+
341+
// Write some debug info
342+
Serial.print("Incoming lock status:");
343+
Serial.println(message.getBool());
344+
}
345+
}
346+

0 commit comments

Comments
 (0)