#include // Controls whether red/green LED is turned on. const uint8_t DISPLAY_LOCK_PIN = 12; // Pins used to interact with SIPO controlling four 7-segment dislays. const uint8_t DISPLAY_CLOCK_PIN = 11; const uint8_t DISPLAY_DATA_PIN = 10; const uint8_t DISPLAY_STROBE_PIN = 9; // Pin controls weather or not the alarm clock is ringing. const uint8_t NOISE_ENABLE_PIN = 8; // Pins used to interact with weight circuit. const uint8_t WEIGHT_DATA_PIN = 7; // Reads data from HX711 (DT) const uint8_t WEIGHT_CLOCK_PIN = 6; // Drives HX711 output. (SCK) HX711 scale; int WEIGHT_SAMPLE_AMOUNT = 5; // amount of samples of each observation. int AWARE_TIME = 2000; //amount of miliseconds in the aware state. // Pins used to interact with rotary dial circuit. const uint8_t DIAL_DATA_PIN = 5; // Reads data from PISO (4021). const uint8_t DIAL_CLOCK_PIN = 4; // Drives PISO output. const uint8_t DIAL_READY_PIN = 3; // Output from FLIP-FLOP. const uint8_t DIAL_RESET_PIN = 2; // Reset pin on FLIP-FLOP. // The program is centered around a state machine. // TODO: Describe states. enum { STATE_UNARMED, STATE_ARMED, STATE_AWARE, STATE_RINGING, } state; unsigned long awareStartTime; const double MAX_DEVIATION = 200; double savedWeight; typedef uint8_t Passcode[4]; Passcode savedPass; Passcode currentPass; int currentPassIndex = 0; //All the setup functions. void unarmedSetup() { Serial.println("Entering UNARMED state"); state = STATE_UNARMED; digitalWrite(DISPLAY_LOCK_PIN, LOW); } void armedSetup() { Serial.println("Entering ARMED state"); digitalWrite(DISPLAY_LOCK_PIN, HIGH); // The weight isn't saved as part of armedSetup() because that would allow an attack where the weight is gradually // lowered by alternating between the armed and aware states. If the weight were to be updated every time, an // atacker could lower the weight graduadally by staying within the margin of error on every step. if (state == STATE_UNARMED) { scale.begin(WEIGHT_DATA_PIN, WEIGHT_CLOCK_PIN); scale.tare(); savedWeight = scale.get_value(WEIGHT_SAMPLE_AMOUNT); } state = STATE_ARMED; } void awareSetup() { Serial.println("Entering AWARE state"); state = STATE_AWARE; awareStartTime = millis(); } void ringingSetup() { Serial.println("Entering RINGING state"); state = STATE_RINGING; digitalWrite(NOISE_ENABLE_PIN, HIGH); } //Initial setup function. void setup() { Serial.begin(9600); pinMode(DISPLAY_LOCK_PIN, OUTPUT); pinMode(DISPLAY_CLOCK_PIN, OUTPUT); pinMode(DISPLAY_STROBE_PIN, OUTPUT); pinMode(DISPLAY_DATA_PIN, OUTPUT); pinMode(DIAL_DATA_PIN, INPUT); pinMode(DIAL_CLOCK_PIN, OUTPUT); pinMode(DIAL_READY_PIN, INPUT); pinMode(DIAL_RESET_PIN, OUTPUT); pinMode(WEIGHT_DATA_PIN, INPUT); pinMode(WEIGHT_CLOCK_PIN, OUTPUT); pinMode(NOISE_ENABLE_PIN, OUTPUT); // Clear out display ibn case there's old data left in the encoders. clearDisplay(); // Enter initial state; unarmedSetup(); } //other functions. uint8_t dialRead() { Serial.println("Data ready from rotary dial circuit!!"); digitalWrite(DIAL_CLOCK_PIN, LOW); uint8_t data = 0; for (int i = 0; i < 8; ++i) { int bit = digitalRead(DIAL_DATA_PIN); data = (data << 1) | bit; digitalWrite(DIAL_CLOCK_PIN, HIGH); digitalWrite(DIAL_CLOCK_PIN, LOW); } // 0 is encoded as 10 pulses. if (data == 10) { data = 0; } //uint8_t data = shiftIn(PISO_DATA_PIN, PISO_CLOCK_PIN, MSBFIRST); Serial.print("Number: "); Serial.print(data, DEC); Serial.print(", "); Serial.println(data, BIN); digitalWrite(DIAL_RESET_PIN, LOW); digitalWrite(DIAL_RESET_PIN, HIGH); digitalWrite(DIAL_RESET_PIN, LOW); return data; } double weightDeviation() { double currentWeight = scale.get_value(WEIGHT_SAMPLE_AMOUNT); double deviation = fabs(currentWeight - savedWeight); Serial.print("Deviation: "); Serial.println(deviation, 1); return deviation; } //All loop functions. void unarmedLoop() { if (digitalRead(DIAL_READY_PIN)) { currentPass[currentPassIndex] = dialRead(); displayDigitAt(currentPass[currentPassIndex], currentPassIndex); if (currentPassIndex == 3) { memcpy(savedPass, currentPass, sizeof(savedPass)); delay(1000); clearDisplay(); armedSetup(); currentPassIndex = 0; } else { currentPassIndex += 1; } } } void displayDigitAt(uint8_t digit, uint8_t i) { const uint8_t ALL_LATCHED = 0xF0; uint8_t bits = ALL_LATCHED - (16 << i) + digit; digitalWrite(DISPLAY_STROBE_PIN, LOW); shiftOut(DISPLAY_DATA_PIN, DISPLAY_CLOCK_PIN, MSBFIRST, bits); digitalWrite(DISPLAY_STROBE_PIN, HIGH); bits = ALL_LATCHED + digit; digitalWrite(DISPLAY_STROBE_PIN, LOW); shiftOut(DISPLAY_DATA_PIN, DISPLAY_CLOCK_PIN, MSBFIRST, bits); digitalWrite(DISPLAY_STROBE_PIN, HIGH); } void displayPasscode(Passcode p) { for (int i = 0; i <= 3; ++i) { displayDigitAt(p[i], i); } } void displayNumber(int value) { if (value > 9999) { value = 9999; } else if (value < 0) { value = 0; } for (int i = 3; i >= 0; --i) { uint8_t digit = value % 10; value /= 10; displayDigitAt(digit, i); } } void clearDisplay() { for (int i = 0; i <= 3; ++i) { displayDigitAt(10, i); } } bool handlePasscode() { if (digitalRead(DIAL_READY_PIN)) { currentPass[currentPassIndex] = dialRead(); displayDigitAt(currentPass[currentPassIndex], currentPassIndex); if (currentPassIndex == 3) { currentPassIndex = 0; bool validPass = memcmp(savedPass, currentPass, sizeof(savedPass)) == 0; if (validPass) { // Very cool victory animation. delay(300); for (int i = 0; i < 2; ++i) { clearDisplay(); delay(200); for (int j = 0; j <= 3; ++j) { delay(100); displayDigitAt(currentPass[j], j); } delay(300); } clearDisplay(); return true; } else { // blink passcode const unsigned long wait = 100; for (int i = 0; i < 4; ++i) { delay(wait); clearDisplay(); delay(wait); displayPasscode(currentPass); } delay(wait); clearDisplay(); return false; } } else { currentPassIndex += 1; return false; } } return false; } void armedLoop() { if (handlePasscode()) { unarmedSetup(); } double deviation = weightDeviation(); if (deviation > MAX_DEVIATION) { awareSetup(); } } void awareLoop() { if (handlePasscode()) { unarmedSetup(); } double deviation = weightDeviation(); if (deviation < MAX_DEVIATION) { armedSetup(); return; } int remainingTime = AWARE_TIME - (millis() - awareStartTime); Serial.print("Remaining time:"); Serial.println(remainingTime); if (remainingTime <= 0) { ringingSetup(); } } // FIXME: What happens if user starts entering password while in aware state and ringing state only sees last two digits? void ringingLoop() { if (handlePasscode()) { digitalWrite(NOISE_ENABLE_PIN, LOW); unarmedSetup(); } // FIXME: SHouldn't be able to do this for non-testing purposes. double deviation = weightDeviation(); Serial.println(deviation, 1); if (deviation < MAX_DEVIATION) { digitalWrite(NOISE_ENABLE_PIN, LOW); armedSetup(); } } //initial loop function, wich contains the switch (state machine). void loop() { switch (state) { case STATE_UNARMED: unarmedLoop(); break; case STATE_ARMED: armedLoop(); break; case STATE_AWARE: awareLoop(); break; case STATE_RINGING: ringingLoop(); break; default: Serial.println("warning: unhandled state"); break; } }