Auto updating alarm watch - accepts alarm settings from a BLE device like a Raspberry Pi and buzzes at the appropriate time - also displays binary time

Dependencies:   BLE_API mbed-src nRF51822 nrf51_rtc

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 // BLE Alarm Watch
00002 // Based on BLE examples on MBED
00003 // Rob Dobson, (c) 2015
00004 
00005 #include "mbed.h"
00006 #include "BLE.h"
00007 #include "LedDisplay.h"
00008 #include "WatchTimeService.h"
00009 #include "nrf51_rtc.h"
00010 #include "DFUService.h"
00011 
00012 // BLE platform
00013 BLE ble;
00014 
00015 // Button press to show time
00016 InterruptIn button(p5);
00017 
00018 // Vibration motor
00019 DigitalOut vibrationMotor(p9);
00020 
00021 // Device name - this is the visible name of device on BLE
00022 const static char DEVICE_NAME[] = "JoesAlarm";
00023 
00024 // UUIDs of services offered
00025 static const uint16_t uuid16_list[] = {WatchTimeService::WATCHTIME_SERVICE_UUID};
00026 
00027 // Service offering to read and set the time on the watch
00028 WatchTimeService *pWatchTimeService;
00029 
00030 // Time watch button last pressed
00031 time_t watchButtonLastPressed = 0;
00032 
00033 // Time between periodic callbacks used to keep RTC functioning
00034 const int PERIODIC_CALLBACK_SECS = 2;
00035 
00036 // Time for double press detection (2 presses within this number of secs == double press)
00037 const int DOUBLE_PRESS_SECS = 2;
00038 
00039 // Time to display LEDs
00040 const int DISPLAY_ON_TIME = 5;
00041 
00042 // Alarm on time
00043 const int ALARM_ON_TIME = 120;
00044 
00045 // Control whether time characteristic is updated when BLE is connected
00046 // This is currently set false because it is probably not needed - really just a debug function
00047 // and, since the code runs in an interrupt, it may be brittle although it seems to work ok
00048 int UPDATE_BLE_TIME_WHEN_CONNECTED = false;
00049 
00050 // Time alarm set to
00051 time_t alarmSetTime = 0;
00052 
00053 // Ticker needed to keep the RTC from losing time on counter rollover
00054 Ticker periodicCallbackToKeepTimerGoing;
00055 
00056 // Timeout to avoid bounce on button
00057 Timeout buttonDebounceTimeout;
00058 bool buttonAlreadyPressed = false;
00059 
00060 // Alarm state
00061 enum AlarmState
00062 {
00063     AlarmState_off,
00064     AlarmState_on_sounding,
00065     AlarmState_on_resting
00066 };
00067 AlarmState alarmState = AlarmState_off;
00068 
00069 // Time alarm was activated
00070 time_t alarmInitiatedTime = 0;
00071 
00072 // Set alarm on
00073 void AlarmOn()
00074 {
00075     alarmState = AlarmState_on_sounding;
00076     vibrationMotor = 1;
00077     alarmInitiatedTime = rtc.time();
00078 }
00079 
00080 // Set alarm on
00081 void AlarmOff()
00082 {
00083     alarmState = AlarmState_off;
00084     vibrationMotor = 0;
00085 }
00086 
00087 // Button debounce callback
00088 void buttonDebounceCallback()
00089 {
00090     buttonAlreadyPressed = false;
00091 }
00092     
00093 bool datesAreTheSame(time_t dateTime1, time_t dateTime2)
00094 {
00095     struct tm * timeinfo;
00096     timeinfo = localtime (&dateTime1);
00097     int alarmMDay = timeinfo->tm_mday;
00098     int alarmMonth = timeinfo->tm_mon;
00099     int alarmYear = timeinfo->tm_year;
00100     timeinfo = localtime (&dateTime2);
00101     return (alarmMDay == timeinfo->tm_mday) && (alarmMonth == timeinfo->tm_mon) && (alarmYear == timeinfo->tm_year);
00102 }
00103 
00104 // Handle button press to read watch
00105 void buttonPressedCallback(void)
00106 {
00107     // Handle debounce
00108     if (buttonAlreadyPressed)
00109         return;
00110     buttonAlreadyPressed = true;
00111     buttonDebounceTimeout.attach(buttonDebounceCallback, 0.2);
00112     
00113     // Stop alarm
00114     AlarmOff();
00115     
00116     // Get the time to display
00117     time_t curTime = rtc.time();
00118     
00119     // Check if we should display current time or alarm time
00120     if (curTime - watchButtonLastPressed > DOUBLE_PRESS_SECS)
00121     {
00122         ledDisplay.start(rtc.time(), LedDisplay::DispType_CurTime, DISPLAY_ON_TIME, false);
00123     }
00124     else
00125     {
00126         bool alarmIsLaterToday = (alarmSetTime > curTime) && datesAreTheSame(alarmSetTime, curTime);
00127         ledDisplay.start(alarmSetTime, LedDisplay::DispType_AlarmTime, DISPLAY_ON_TIME, alarmIsLaterToday);
00128     }
00129     
00130     // Remember when button last pressed
00131     watchButtonLastPressed = curTime;
00132 }
00133 
00134 // Button released
00135 void buttonReleasedCallback(void)
00136 {
00137 }
00138 
00139 // Handle BLE disconnection - restart advertising
00140 void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
00141 {
00142     ble.gap().startAdvertising();
00143 }
00144 
00145 // Callback required for RTC operation
00146 void periodicCallback(void)
00147 {
00148     // Update the rtc library time (it says in the notes on the rtc lib that this needs to happen
00149     // more than once every few hundred seconds to avoid a rollover
00150     time_t curTime = rtc.time();
00151     
00152     // Update the time characteristic if we are connected
00153     if (UPDATE_BLE_TIME_WHEN_CONNECTED)
00154     {
00155         Gap::GapState_t gapState = ble.gap().getState();
00156         if (gapState.connected)
00157         {
00158             time_t curTime = rtc.time();
00159             pWatchTimeService->writeWatchTime(curTime);
00160         }
00161     }
00162     
00163     // Check if alarm went off recently
00164     if (alarmInitiatedTime + PERIODIC_CALLBACK_SECS*2 + ALARM_ON_TIME > curTime)
00165     {
00166         // Stop the alarm after it has been on for the allotted time
00167         if (alarmInitiatedTime + ALARM_ON_TIME < curTime)
00168             AlarmOff();
00169     }
00170     else
00171     {
00172         // Check if time for alarm
00173         if (alarmSetTime > 0)
00174         {
00175             if ((curTime >= alarmSetTime) && (curTime < alarmSetTime + PERIODIC_CALLBACK_SECS*2))
00176             {
00177                 AlarmOn();
00178             }
00179         }
00180     }
00181     
00182     // Pulse the alarm
00183     if (alarmState == AlarmState_on_sounding)
00184     {
00185         alarmState = AlarmState_on_resting;
00186         vibrationMotor = 0;
00187     }
00188     else if (alarmState == AlarmState_on_resting)
00189     {
00190         alarmState = AlarmState_on_sounding;
00191         vibrationMotor = 1;
00192     }
00193     
00194 //    // TEST TEST TEST
00195 //    int testMin = 0;
00196 //    int testHr = 0;
00197 //    if (testNum < 6)
00198 //        testMin = 1 << testNum;
00199 //    else
00200 //        testHr = 1 << (testNum - 6);
00201 //    testNum++;
00202 //    if (testNum >= 11)
00203 //        testNum = 0;
00204 //    
00205 //    uint8_t testTime[] = { uint8_t(2015/256), uint8_t(2015%256), 12, 18, testHr, testMin, 0 };
00206 //    time_t testTimeT = WatchTimeService::watchTimeToUnixTime(testTime);
00207 //
00208 //    ledDisplay.start(testTimeT, LedDisplay::DispType_CurTime, DISPLAY_ON_TIME);
00209 
00210 }
00211 
00212 // Helper
00213 void setRTCfromWatchTime(const uint8_t* pWatchTime)
00214 {
00215     time_t timest = WatchTimeService::watchTimeToUnixTime(pWatchTime);
00216     if ((int)timest != -1)
00217     {
00218         rtc.set_time(timest);
00219     }
00220 }
00221 
00222 void onDataWrittenCallback(const GattWriteCallbackParams *params) 
00223 {
00224     // Check if this is time setting
00225     if (pWatchTimeService->getWatchTimeValueHandle() == params->handle) 
00226     {
00227         if (params->len == WatchTimeService::WatchTime_BlockSize)
00228         {
00229             setRTCfromWatchTime(params->data);
00230         }
00231     }
00232     else if (pWatchTimeService->getAlarmTimeValueHandle() == params->handle) 
00233     {
00234         if (params->len == WatchTimeService::WatchTime_BlockSize)
00235         {
00236             time_t timest = WatchTimeService::watchTimeToUnixTime(params->data);
00237             if (((int)timest != -1) && ((int)timest != 0))
00238                 alarmSetTime = timest;
00239             else
00240                 alarmSetTime = 0;
00241         }
00242     }
00243 }
00244     
00245 // DEBUG CODE
00246 void print_time() 
00247 {    
00248     time_t rawtime=rtc.time();
00249     
00250     // massage the time into a human-friendly format for printing
00251     struct tm * timeinfo;
00252     timeinfo = localtime(&rawtime);
00253     char date[24];
00254     strftime(date,sizeof(date),"%H:%M:%S on %m/%d/%G",timeinfo);
00255     printf("The current time is %s.\r\n",date);
00256 }
00257 
00258 int main(void)
00259 {
00260     printf("AlarmWatch\r\n");
00261     
00262     // This is necessary to keep the clock ticking - doesn't have to be so frequent
00263     // According to this https://developer.mbed.org/users/fxschumacher/code/nRF51_rtc_example/file/c1f06d0a5e11/main.cpp
00264     // any time less than 512 seconds is ok
00265     periodicCallbackToKeepTimerGoing.attach(periodicCallback, PERIODIC_CALLBACK_SECS);
00266     
00267     // Handlers for the button press
00268     button.fall(buttonPressedCallback);
00269     button.rise(buttonReleasedCallback);
00270 
00271     // Clear display
00272     ledDisplay.clear();
00273     
00274     // BLE init
00275     ble.init();
00276     ble.gap().onDisconnection(disconnectionCallback);
00277     ble.onDataWritten(onDataWrittenCallback);
00278     
00279     // Watch Time Service
00280     uint8_t initialTime[] = { uint8_t(2015/256), uint8_t(2015%256), 12, 18, 10, 10, 0 };  
00281     uint8_t alarmTime[] = { 0, 0, 0, 0, 0, 0, 0 };  
00282     WatchTimeService watchTimeService(ble, initialTime, alarmTime);
00283     setRTCfromWatchTime(initialTime);
00284     pWatchTimeService = &watchTimeService;
00285     
00286     /* Enable over-the-air firmware updates. Instantiating DFUSservice introduces a
00287      * control characteristic which can be used to trigger the application to
00288      * handover control to a resident bootloader. */
00289     DFUService dfu(ble);
00290 
00291     // Setup advertising
00292     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
00293     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
00294     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
00295     ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
00296     ble.gap().setAdvertisingInterval(1000); /* 1000ms. */
00297     ble.gap().startAdvertising();
00298 
00299     // Turn off any alarm
00300     AlarmOff();
00301 
00302     while (true) 
00303     {
00304         ble.waitForEvent();
00305     }
00306 }