/************** Tick Tock Tock **************/ /* Arduino code for the Watch portion of the Tick Tock Tock project written by Steven Lehrburger 25 June 2008 for the Sociable Objects class at ITP taught by Rob Faludi */ #define s1Pin 2 // red notes to self re wire color #define s2Pin 3 // blue #define s3Pin 4 // yellow #define lightPin 5 #define resetPin 6 #define buttonPin 8 #define statusPin 13 #define deviceID '1' // Allows the radios in broadcast mode to identify each other #define WATCH '0' #define LCD '1' #define COMP '2' #define STARTBYTE '*' // a unifrom startbyte #define CONFBYTE 'R' // and confirmation byte #define NUM_TIME_UNITS 7 // centruy, year, month, day, hours, minutes, seconds #define ON_DELAY 45 #define BETWEEN_DELAY 50 int stressCounter = 0; // stress level starts at zero - see loop and CalculateOffset below unsigned long initialMillis; int initialDateTimeData[7]; // dateTimeData in ccyymmddHHMMSS format int currentDateTimeData[7]; // dateTimeData in ccyymmddHHMMSS format // Setup gets the initial time and sends a confirmation void setup() { Serial.begin(9600); configureXBee(); pinMode(s1Pin, OUTPUT); pinMode(s2Pin, OUTPUT); pinMode(s3Pin, OUTPUT); pinMode(lightPin, OUTPUT); pinMode(resetPin, OUTPUT); pinMode(statusPin, OUTPUT); digitalWrite(s1Pin, LOW); digitalWrite(s2Pin, LOW); digitalWrite(s3Pin, LOW); digitalWrite(lightPin, LOW); digitalWrite(resetPin, LOW); PressReset(); GetInitialTime(); initialMillis = millis(); SetTime(60); SendConfirmation(); } // Loop checks to see if the button (which simulates a sensor or series of sensors gauging the wearers stress level) has been pressed void loop() { if (digitalRead(buttonPin) == HIGH) { stressCounter++; blink(statusPin, stressCounter); SetTime(CalculateOffset()); // if it has been pressed, it sets it's own time, with an updated offset BroadcastTime(); // and them broadcasts the new time to the other devices BroadcastTime(); BroadcastTime(); } delay(20); } // Sets the time on the watch as an offset in seconds of the current time - the offset of one to start is an estimate of send/receive time delays void SetTime(int offset) { long msToDelay = (60 - currentDateTimeData[6]) * 1000; // first, figure out how long until the end of the current inute long timeForSetting = TimeToSetTime(currentDateTimeData[4], currentDateTimeData[5], currentDateTimeData[3], currentDateTimeData[2]/*, wkday*/); // then estimate how long it will take to set the time - this is nonnegligible, since buttons can only be pressed so fast and must be pressed many times if (timeForSetting > msToDelay) { // watch out for negative delays!!! if so, we need to wait another minute msToDelay = msToDelay + 60000; } delay(msToDelay - timeForSetting); RecursiveDateTimeUpdate(6, ((millis() - initialMillis) / 1000) + offset); // first, update the internal time - note the Arduino has to keep track and not rely on the watch, because the watch changes PressReset(); // first, reset the time to 1am on 1/1 PressS3(); // then change to set mode PressS3(); PressS3(); PressS1(); // seconds goes last so skip it for now int i; for (i = 1; i < (currentDateTimeData[5] + 1); i++) { PressS2(); }// set the miniutes - PressS1(); for (i = 1; i < (currentDateTimeData[4]); i++) { PressS2(); } // hours PressS1(); for (i = 1; i < (currentDateTimeData[3]); i++) { PressS2(); } // date PressS1(); for (i = 1; i < (currentDateTimeData[2]); i++) { PressS2(); } // months PressS1(); //for (i = 1; i < (wkday + 1); i++) { PressS2(); } // day of week was not worth dealing with PressS1(); PressS2(); // seconds to zero PressS3(); // exit set mode int temp = (((millis() - initialMillis) / 1000) + offset) / 60; // get an integer number of seconds RecursiveDateTimeUpdate(6, temp * 60); // and reset the internal time count because it might be off after setting, and should be correct for sending to LCD } // Just like the above, but is used to estimate how much time it will take to set the clock long TimeToSetTime(int hrs, int mins, int date, int month/*, int wkday*/) { // sorry about not using +=, they gave me trouble once and I forgot to try them again here long timeSum = 4000; int totalDelay = ON_DELAY + BETWEEN_DELAY; timeSum = timeSum + totalDelay; // PressReset(); timeSum = timeSum + totalDelay; // PressS3(); timeSum = timeSum + totalDelay; // PressS3(); timeSum = timeSum + totalDelay; // PressS3(); timeSum = timeSum + totalDelay; // PressS1(); int i; for (i = 1; i < (mins + 1); i++) { timeSum = timeSum + totalDelay; }//PressS2(); } timeSum = timeSum + totalDelay; // PressS1(); for (i = 1; i < (hrs); i++) { timeSum = timeSum + totalDelay; } // PressS2(); timeSum = timeSum + totalDelay; // PressS1(); for (i = 1; i < (date); i++) { timeSum = timeSum + totalDelay; } // PressS2(); timeSum = timeSum + totalDelay; // PressS1(); for (i = 1; i < (month); i++) {timeSum = timeSum + totalDelay; } // PressS2(); timeSum = timeSum + totalDelay; // PressS1(); //for (i = 1; i < (wkday + 1); i++) { PressS2(); } timeSum = timeSum + totalDelay; // PressS1(); timeSum = timeSum + totalDelay; // PressS3(); return(timeSum); } // Sends a confirmation ten times that includes a startbyte, a device ID, and a confirmation byte void SendConfirmation() { for (int i = 0; i < 10; i++) { SendByte(STARTBYTE); SendByte(deviceID); SendByte(CONFBYTE); delay(50); } } // Waits until it receives an initial time from the computer and stores it. // After that the watch does not need to receive the time again. void GetInitialTime() { Serial.flush(); while (true) { delay(50); int inByte = Serial.read(); if (inByte == STARTBYTE) { int senderID = Serial.read(); if(senderID == COMP) { for (int i = 0; i < NUM_TIME_UNITS; i++) { initialDateTimeData[i] = currentDateTimeData[i] = Serial.read(); } return; } } } } // Increases the time offset (in seconds) depending on how many times the button has been pressed int CalculateOffset() { if (stressCounter == 0) return(0 * 60); if (stressCounter == 1) return(5 * 60); if (stressCounter == 2) return(10 * 60); if (stressCounter == 3) return(15 * 60); if (stressCounter == 4) return(20 * 60); if (stressCounter >= 5) return(25 * 60); blink(statusPin, 100); return(-1); } // Broadcasts a single series of bytes with the current time to the other devices void BroadcastTime() { blink(statusPin, 2); SendByte(STARTBYTE); SendByte(deviceID); for(int i = 0; i < NUM_TIME_UNITS; i++) { SendByte(currentDateTimeData[i]); } delay(100); } /************** XBee configuration **************/ // Configure's the XBee with the proper addresses void configureXBee() { Serial.flush(); delay(1010); Serial.print("+++"); delay(1010); WaitForCR(); // everyone is broadcasting! Serial.print("ATRE, DH0, DLFFFF\r"); WaitForCR(); // but deviceID's determine sender and behavior Serial.print("ATMY"); Serial.print(deviceID); Serial.print("\r"); WaitForCR(); Serial.print("ATIDBCDE\r"); WaitForCR(); Serial.print("ATCN\r"); delay(1000); Serial.flush(); } // Waits for the radio to respond to an AT command with "Ok/r" void WaitForCR() { int thisByte = 0; while (thisByte != '\r') { if (Serial.available() > 0) { thisByte = Serial.read(); } } } // Blink the specified LED the specified number of times void blink(int pinNum, int howManyTimes) { for (int i=0; i< howManyTimes; i++) { digitalWrite(pinNum, HIGH); delay(200); digitalWrite(pinNum, LOW); delay(200); } } //Easier to read than the Serial.print, and it used to have a delay void SendByte(int byteToSend) { Serial.print(byteToSend, BYTE); } /************** Recursive function for keeping track of time **************/ // (same as for Watch) // The LCD will need to keep track of time on its own. To do this it must constantly // convert its own internal millis() count to a standard time, based off of the millis() // when the currently set time was received. // This is a relatively complex task, and I wrote a recursive function to handle it. (An example is below.) void RecursiveDateTimeUpdate(int index, int totalToCarry) { // the first time it is called, it will be passed 6 (the index of the seconds) // and the difference in millis over 1000, which is the total number of seconds to handle if (index == 0) { currentDateTimeData[index] = initialDateTimeData[index] + totalToCarry; // we assume we won't get beyond the year 9999.. if we do, we can have a Y10k bug scare (and a big party) } else { // the recursive case int totalToPass = (initialDateTimeData[index] + totalToCarry) / NumSubdivisionsForIndex(index); // first, figure out how many of the current unit we are going to have left over to pass to the next larger unit // note the decimal will be truncated! currentDateTimeData[index] = initialDateTimeData[index] + totalToCarry - (NumSubdivisionsForIndex(index) * totalToPass); // then we know the current value for the current unit is the initial plus the total to carry minues the number of // the current units that we are going to pass recursively as the next units RecursiveDateTimeUpdate(index - 1, totalToPass); // and then we can make the recursive call } } /* Partial Example So let's say we have 5000 seconds in our first call to the function that looks like RecursiveDateTimeUpdate(6, 50000) First, we need to determine how many of those seconds are minutes. So take however many seconds we currently have from initialTimeDateData, say, 34 and 5000, divide by 60 to get 83.9, and truncate the decimal to get the 83 minutes that will be recursively passed. Then get that decimal back by taking the initial number of seconds, adding the 5000, and subtracting 83*60 = 4980 to get 54 seconds left over. And then recurse to the next level. */ // Each unit of time divides differently into the next larger unit, and this handles those differences int NumSubdivisionsForIndex(int index) { if (index == 6) return(60); if (index == 5) return(60); if (index == 4) return(24); if (index == 3) return(DaysInCurrentMonth()); if (index == 2) return(12); if (index == 1) return(100); } // And each month has a different number of days int DaysInCurrentMonth () { // if ((currentDateTimeData[2] == 3) || // April (currentDateTimeData[2] == 4) || // June (currentDateTimeData[2] == 8) || // September (currentDateTimeData[2] == 10)) { // November return(30); } else if ((currentDateTimeData[2] == 1) && ((currentDateTimeData[1] % 4) == 0)) { return(29); // February (leap year) } else if ((currentDateTimeData[2] == 1) && ((currentDateTimeData[1] % 4) != 0)) { return(28); // February (normal year) } else { return(31); // all others } } /************** Watch controls **************/ // Send high voltage pulses to the corresponding buttons on the watch void PressS1() { digitalWrite(s1Pin, HIGH); delay(ON_DELAY); digitalWrite(s1Pin, LOW); delay(BETWEEN_DELAY); } void PressS2() { digitalWrite(s2Pin, HIGH); delay(ON_DELAY); digitalWrite(s2Pin, LOW); delay(BETWEEN_DELAY); } void PressS3() { digitalWrite(s3Pin, HIGH); delay(ON_DELAY); digitalWrite(s3Pin, LOW); delay(BETWEEN_DELAY); } /*void PressLight() { //this connection came undone and was nonessential, so I did not risk fixing it digitalWrite(lightPin, HIGH); delay(ON_DELAY); digitalWrite(lightPin, LOW); delay(BETWEEN_DELAY); }*/ void PressReset() { digitalWrite(resetPin, HIGH); delay(ON_DELAY); digitalWrite(resetPin, LOW); delay(BETWEEN_DELAY); delay(BETWEEN_DELAY); }