-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDoorman.cpp
More file actions
371 lines (302 loc) · 10.9 KB
/
Doorman.cpp
File metadata and controls
371 lines (302 loc) · 10.9 KB
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
//Doorman.cpp - Doorman Class Method Definitions
//Part of ArduinoDoorman, an Arduino-Based Access Control System
//Ethan Goff, January 2014
#include "Doorman.h"
//Static Class Members
SwitchController * Doorman::SwitchBank;
Keychain * Doorman::Keys;
Keypad * Doorman::InputKeypad;
uint8_t Doorman::StateLED;
bool Doorman::PublicAccessModeOn;
bool Doorman::ProgramModeOn;
unsigned long Doorman::StateLEDReferenceTime;
//Constructor
Doorman::Doorman(Keypad * inputKeypad)
{
//Because the Keypad class cannot be lazy-instantiated, we pass it from the main
// sketchinto the static instance of the Doorman class
InputKeypad = inputKeypad;
//Define the other static members. The initial indicator LED, as well as both
// mode booleans, are arbitrary - they are checked within the first second of
// operation regardless (and at a defined interval throughout the system's uptime)
Keys = &Keychain();
SwitchBank = &SwitchController();
StateLED = GREEN_LED_OUTPUT_PIN;
PublicAccessModeOn = false;
ProgramModeOn = false;
StateLEDReferenceTime = millis(); //Sample the initial reference time
}
// Main system update method //
//The system operates in 3 modes -
// Normal Mode: checks entered keys against the keychain
// Public Access Mode: opens the door at any keypress
// Programming Mode: adds novel keys to the keychain or removes existing keys
//
//After the initial setup, the system simply cycles through this method repetatively.
//First, the system checks the two mode control switches to see if the mode should
//be changed before continuing. Then the system updates the mode indication LED if
//needed. Then, the system probes the Keypad, acting on a meaningful receipt as the
//current mode implies.
void Doorman::Update()
{
ProbeLatchingSwitches(); //Check the mode control switches
UpdateIndicatorLED(); //Update the mode LED
//Probe the Keypad for a keypress.
//currentInput holds the 'seed' keypress (the first entered by a user)
// or a c-string terminator to indacte nothing is received
char currentInput = InputKeypad->getKey();
//If the system is in public access mode and any key is pressed OR if the
// override switch is active, we open the door without further checks.
if( (PublicAccessModeOn && currentInput != '\0') || RecievingOverrideRequest() )
OpenDoor();
//Otherwise, if the system received a meaningful keypress...
else if(currentInput != '\0' && currentInput != '#' && currentInput != '*')
{
//...it attempts to get the other three digits of the code.
int Attempt = getCode(3, int(currentInput-'0') );
if(Attempt != TIMEDOUT)//If the getCode method didn't time-out
{
//If the system is in programming mode, it forwards the key to the
// programming method.
if(ProgramModeOn)
{
LED(FORCE_LEDS_OFF); //Clear the LEDs first
//The Programming Method returns an event token, which selects the
// appropriate LED indication
LED(Keys->ProgramKey(Attempt));
}
//Otherwise, it checks if the Key exists in Keychain
else if( Keys->KeyExists(Attempt) )
OpenDoor();
//If the key isn't found in the keychain, indicate an incorrect attempt
else
LED(INCORRECT_GUESS);
}
else
LED(TIMEDOUT);
}
//If none of the above occur, we do nothing, and the checks start anew
else
return;
}
//Updates the system's mode-indicator LED (if necessary)
void Doorman::UpdateIndicatorLED()
{
//If we aren't in Public Access Mode, we toggle the current indicator LED
// back and forth at a defined rate
if(millis() - StateLEDReferenceTime > WAITING_ON_INPUT_LED_HOLD_TIME && !PublicAccessModeOn)
LED(TOGGLE_STATE_LED_ON_OFF);
}
//Check the latching switches and reset the check timer
void Doorman::ProbeLatchingSwitches()
{
PublicAccessModeOn = RecievingPublicAccessRequest();
//Programming mode is meant to be exclusive (and superceded by) Public Access Mode
if(!PublicAccessModeOn)
ProgramModeOn = RecievingProgramRequest();
}
//Returns either an input code or a TIMEDOUT token
// targetLength: digit count for the code to be read
// timeout: time in seconds to allow for a code to be entered, refreshed after each keypress
int Doorman::getCode(const unsigned int& targetLength, unsigned int seed = 0)
{
//inputAttempt holds the security code guess that is being entered by a user
int inputAttempt = seed;
//digitsEntered counts the number of input digits entered by the user
int digitsEntered = 0;
//referenceTime holds an initially arbitrary time, which is checked against
// the current time to check for the elapsed difference
unsigned long referenceTime = millis();
//Look for input until the requested number of digits have been entered
// or the operation times out
while(digitsEntered < targetLength && millis() - referenceTime < INPUT_TIMEOUT)
{
//We update our indicator LED twice as fast to indicate key presses
// are being received
if(millis() - StateLEDReferenceTime > (WAITING_ON_INPUT_LED_HOLD_TIME)/2)
{
LED(TOGGLE_STATE_LED_ON_OFF);
}
//Sample for a keypress
char currentInput = InputKeypad->getKey();
//If the user enters a valid numeric key
if( currentInput != '#' && currentInput != '*' && currentInput != '\0')
{
//Append the inputed key to the input code
inputAttempt*=10;
inputAttempt+= int(currentInput-'0');
digitsEntered++; //increment the digits counter
referenceTime = millis(); //reset the timeout
}
}
//If the user failed to finish entering a code, return the suitable token
if(millis() - referenceTime >= INPUT_TIMEOUT)
return TIMEDOUT;
else //Otherwise, return the entered code
return inputAttempt;
}
//Opens the door by activating the relay controlling power to the door lock
void Doorman::OpenDoor()
{
//Set digital output to relay contol to LOW for an ammount of time,
// unlatching the door lock
digitalWrite(DOOR_RELAY_CONTROL_OUTPUT_PIN, LOW);
LED(DOOR_OPENED); //Indicate a successful open via the LEDs
//Re-Latch the door
digitalWrite(DOOR_RELAY_CONTROL_OUTPUT_PIN, HIGH);
}
//Checks if the system is set to Programming Mode
bool Doorman::RecievingProgramRequest()
{
//The Program mode is designed to be triggered by a latching switch -
// this method is written more generally (since debounce is still neccessary
// with a latching switch, this method should be called occassionally to check
// for changes once programming has begun.
//Check once...
if(SwitchBank->ProgramSwitchIsActive())
{
delay(SWITCH_DEBOUNCE_DELAY);
//...then re-check after some de-bouncing/de-noising time
if(SwitchBank->ProgramSwitchIsActive())
{
//If the mode is switched on, turn off the LEDs and switch to
// the yellow indicator LED to indicate the system has entered
// programming mode
if(!ProgramModeOn)
{
LED(FORCE_LEDS_OFF);
StateLED = YELLOW_LED_OUTPUT_PIN;
}
return true;
}
}
//If the mode is switched on, turn off the LEDs and switch to
// the green indicator LED to indicate the system has left
// programming mode
if(ProgramModeOn)
{
LED(FORCE_LEDS_OFF);
StateLED = GREEN_LED_OUTPUT_PIN;
}
return false;
}
//Checks if the system is set to Public Access Mode
bool Doorman::RecievingPublicAccessRequest()
{
//Similarly to the program mode, the public access mode is designed to be
// triggered by a latching switch. The two are meant to be mutually exclusive;
// However, public access mode takes precedence, creating a sink state
// until released.
//Check once...
if(SwitchBank->PublicAccessModeSwitchIsActive())
{
delay(SWITCH_DEBOUNCE_DELAY);
//...then re-check after some de-bouncing/de-noising time
if(SwitchBank->PublicAccessModeSwitchIsActive())
{
//Indicate that we have entered Public Access mode
LED(PUBLIC_ACCESS_SWITCHED_ON);
return true;
}
}
return false;
}
//Checks if a user is requesting an override
bool Doorman::RecievingOverrideRequest()
{
//Unlike the mode switches, the override request is designed
//to be triggered with a momentary switch.
//Check once...
if(SwitchBank->OverrideSwitchIsActive())
{
delay(SWITCH_DEBOUNCE_DELAY);
//...then re-check after some de-bouncing/de-noising time
if(SwitchBank->OverrideSwitchIsActive())
return true;
}
return false;
}
//Handles requests to indicate events via the LEDs
// eventToken: Constants corresponding to specific events
void Doorman::LED(const unsigned int & eventToken)
{
switch (eventToken)
{
case INCORRECT_GUESS:
//Clear the LEDs first
digitalWrite(GREEN_LED_OUTPUT_PIN, HIGH );
digitalWrite(YELLOW_LED_OUTPUT_PIN, HIGH );
digitalWrite(RED_LED_OUTPUT_PIN, HIGH );
//Blink the red LED once
digitalWrite(RED_LED_OUTPUT_PIN, LOW);
delay(WRONG_ATTEMPT_INDICATION_DURATION);
digitalWrite(RED_LED_OUTPUT_PIN, HIGH);
return;
case TOGGLE_STATE_LED_ON_OFF:
//Switch the state LED
digitalWrite(StateLED, !digitalRead(StateLED) );
StateLEDReferenceTime = millis();
return;
case FORCE_LEDS_OFF:
digitalWrite(GREEN_LED_OUTPUT_PIN, HIGH );
digitalWrite(YELLOW_LED_OUTPUT_PIN, HIGH );
digitalWrite(RED_LED_OUTPUT_PIN, HIGH );
return;
case PUBLIC_ACCESS_SWITCHED_ON:
//Ensure the green LED is forced on
digitalWrite(GREEN_LED_OUTPUT_PIN, LOW);
return;
case KEY_ADDED: //Cascade the LEDs from red to yellow to green, 3 times
for(int i=0; i<3 ; i++)
{
for(int LED = RED_LED_OUTPUT_PIN; LED >= GREEN_LED_OUTPUT_PIN; LED--)
{
digitalWrite(LED, LOW);
delay(PROGRAMMING_CHANGE_LED_HOLD_TIME);
digitalWrite(LED, HIGH);
}
}
return;
case KEY_REMOVED: //Cascade the LEDs from green to yellow to red, 3 times
for(int i=0; i<3 ; i++)
{
for(int LED = GREEN_LED_OUTPUT_PIN; LED <= RED_LED_OUTPUT_PIN; LED++)
{
digitalWrite(LED, LOW);
delay(PROGRAMMING_CHANGE_LED_HOLD_TIME);
digitalWrite(LED, HIGH);
}
}
return;
case DOOR_OPENED:
//The system will blink all LEDs simultaneously when the door is opened
digitalWrite(GREEN_LED_OUTPUT_PIN, HIGH );
digitalWrite(YELLOW_LED_OUTPUT_PIN, HIGH );
digitalWrite(RED_LED_OUTPUT_PIN, HIGH );
unsigned long referenceTime = millis(); //Sample the current time
//Blink the LEDs for some duration
while(millis() - referenceTime < (UNLOCK_TIMEOUT))
{
boolean LEDState = digitalRead(GREEN_LED_OUTPUT_PIN);
//Toggle all LEDs to the opposite state
digitalWrite(GREEN_LED_OUTPUT_PIN, !LEDState );
digitalWrite(YELLOW_LED_OUTPUT_PIN, !LEDState );
digitalWrite(RED_LED_OUTPUT_PIN, !LEDState );
delay(WAITING_ON_INPUT_LED_HOLD_TIME);
}
digitalWrite(GREEN_LED_OUTPUT_PIN, HIGH );
digitalWrite(YELLOW_LED_OUTPUT_PIN, HIGH );
digitalWrite(RED_LED_OUTPUT_PIN, HIGH );
return;
}
}
//Sets the flag telling the system to write the default keymap
void Doorman::CheckForKeychainReset()
{
if(RecievingOverrideRequest())
{
bool NowPopulated = false;
EEPROM_writeAnything(KEYCHAIN_EXISTS_IN_EEPROM_FLAG_ADDRESS, NowPopulated);
Keys = &Keychain();
}
}