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

Revision:
6:4a12e0f03381
Parent:
5:2682353a8c32
--- a/main.cpp	Mon Dec 14 22:58:31 2015 +0000
+++ b/main.cpp	Tue Mar 01 13:03:32 2016 +0000
@@ -4,188 +4,136 @@
 
 #include "mbed.h"
 #include "BLE.h"
-#include "ButtonService.h"
+#include "LedDisplay.h"
 #include "WatchTimeService.h"
 #include "nrf51_rtc.h"
+#include "DFUService.h"
 
 // BLE platform
 BLE ble;
 
-// Hour LEDs
-DigitalOut hourPins[5] = { p0, p1, p2, p3, p4 };
-
-// Minute LEDs
-DigitalOut minPins[6] = { p23, p24, p25, p28, p29, p30 };
-
 // Button press to show time
 InterruptIn button(p5);
 
+// Vibration motor
+DigitalOut vibrationMotor(p9);
+
 // Device name - this is the visible name of device on BLE
 const static char DEVICE_NAME[] = "JoesAlarm";
 
 // UUIDs of services offered
-static const uint16_t uuid16_list[] = {ButtonService::BUTTON_SERVICE_UUID, WatchTimeService::WATCHTIME_SERVICE_UUID};
+static const uint16_t uuid16_list[] = {WatchTimeService::WATCHTIME_SERVICE_UUID};
 
 // Service offering to read and set the time on the watch
 WatchTimeService *pWatchTimeService;
 
-// Time display state variables
-const int timeDisplayState_None = 0;
-const int timeDisplayState_ShowTime = 1;
-const int timeDisplayState_ShowAlarm = 2;
-int timeDisplayState = timeDisplayState_None;
-uint8_t curBinaryLedDisplayVal[WatchTimeService::WatchTime_BlockSize];
-int timeDisplayLastButtonTime = 0;
-const int timeDisplayShowTimeSecs = 10;
-const int timeDisplayMoveToShowAlarmSecs = 5;
+// Time watch button last pressed
+time_t watchButtonLastPressed = 0;
 
-// Cur time disp info
-int curTimeLEDBitPos = 0;
+// Time between periodic callbacks used to keep RTC functioning
+const int PERIODIC_CALLBACK_SECS = 2;
+
+// Time for double press detection (2 presses within this number of secs == double press)
+const int DOUBLE_PRESS_SECS = 2;
 
-// TEST CODE
-int callbackCount = 0;
-uint8_t testbuf[WatchTimeService::WatchTime_BlockSize];
-time_t retval = 0;
-int servcode = 0;
-int buflen = 0;
-int mycode = 0;
-int offs = 0;
-int butcode = 0;
-ButtonService *buttonServicePtr;
-Ticker periodicCallbackToKeepTimerGoing;
-Timeout timerForLEDMuxing;
+// Time to display LEDs
+const int DISPLAY_ON_TIME = 5;
+
+// Alarm on time
+const int ALARM_ON_TIME = 120;
+
+// Control whether time characteristic is updated when BLE is connected
+// This is currently set false because it is probably not needed - really just a debug function
+// and, since the code runs in an interrupt, it may be brittle although it seems to work ok
+int UPDATE_BLE_TIME_WHEN_CONNECTED = false;
+
+// Time alarm set to
+time_t alarmSetTime = 0;
 
-time_t watchTimeToUnix(const uint8_t* pWatchTime)
+// Ticker needed to keep the RTC from losing time on counter rollover
+Ticker periodicCallbackToKeepTimerGoing;
+
+// Timeout to avoid bounce on button
+Timeout buttonDebounceTimeout;
+bool buttonAlreadyPressed = false;
+
+// Alarm state
+enum AlarmState
 {
-    struct tm tminfo;
-    tminfo.tm_year = int(pWatchTime[0])*256 + pWatchTime[1] - 1900;
-    tminfo.tm_mon = pWatchTime[2] - 1;
-    tminfo.tm_mday = pWatchTime[3];
-    tminfo.tm_hour = pWatchTime[4];
-    tminfo.tm_min = pWatchTime[5];
-    tminfo.tm_sec = pWatchTime[6];
-    tminfo.tm_isdst = -1;
-    time_t timest = mktime(&tminfo);
-    return timest;
-}
+    AlarmState_off,
+    AlarmState_on_sounding,
+    AlarmState_on_resting
+};
+AlarmState alarmState = AlarmState_off;
 
-void unixTimeToWatchTime(time_t unixTime, uint8_t* pWatchTime)
+// Time alarm was activated
+time_t alarmInitiatedTime = 0;
+
+// Set alarm on
+void AlarmOn()
 {
-    // Convert to localtime
-    struct tm * timeinfo;     
-    timeinfo = localtime (&unixTime);
-    pWatchTime[0] = (timeinfo->tm_year + 1900) / 256;
-    pWatchTime[1] = (timeinfo->tm_year + 1900) % 256;
-    pWatchTime[2] = timeinfo->tm_mon + 1;
-    pWatchTime[3] = timeinfo->tm_mday;
-    pWatchTime[4] = timeinfo->tm_hour;
-    pWatchTime[5] = timeinfo->tm_min;
-    pWatchTime[6] = timeinfo->tm_sec;
+    alarmState = AlarmState_on_sounding;
+    vibrationMotor = 1;
+    alarmInitiatedTime = rtc.time();
 }
 
-int watchTimeToBCD(const uint8_t* pWatchTime)
+// Set alarm on
+void AlarmOff()
 {
-    // Simply combine the hour and minute values in a 4 digit BCD number
-    int bcdHourMin = pWatchTime[4] / 10;
-    bcdHourMin = (bcdHourMin << 4) + pWatchTime[4] % 10;
-    bcdHourMin = (bcdHourMin << 4) + pWatchTime[5] / 10;
-    bcdHourMin = (bcdHourMin << 4) + pWatchTime[5] % 10;
-    return bcdHourMin;
-}
-
-void callbackForLEDMuxing();
-
-void clearTimeLEDs()
-{
-    for (int i = 0; i < 5; i++)
-        hourPins[i] = 0;
-    for (int i = 0; i < 6; i++)
-        minPins[i] = 0;
+    alarmState = AlarmState_off;
+    vibrationMotor = 0;
 }
 
-//uint8_t* GetCurTimeAsWatchTime()
-//{
-//    // Get current time and convert to displayable time
-//    time_t rawtime=rtc.time();
-//    unixTimeToWatchTime(rawtime, curBinaryLedDisplayVal);
-//}
-    
-void nextShowingTimeLEDs()
+// Button debounce callback
+void buttonDebounceCallback()
 {
-    // Get current time and convert to displayable time
-    time_t rawtime=rtc.time();
-    unixTimeToWatchTime(rawtime, curBinaryLedDisplayVal);
-    
-    // Clear LEDs
-    clearTimeLEDs();
-    
-    // Stop displaying time after a certain number of seconds
-    if (rawtime - timeDisplayLastButtonTime >= timeDisplayShowTimeSecs)
-        return;
-
-    // Display binary time
-    int hours = curBinaryLedDisplayVal[4];
-    int mins = curBinaryLedDisplayVal[5];
-    int mask = 1 << curTimeLEDBitPos;
-    if ((curTimeLEDBitPos < 5) && ((hours & mask) != 0))
-        hourPins[curTimeLEDBitPos] = 1;
-    if ((mins & mask) != 0)
-        minPins[curTimeLEDBitPos] = 1;
-    curTimeLEDBitPos++;
-    if (curTimeLEDBitPos > 5)
-        curTimeLEDBitPos = 0;
-
-    // Set for another callback
-    timerForLEDMuxing.attach(callbackForLEDMuxing, 0.001);
-}
-
-void callbackForLEDMuxing()
-{
-    nextShowingTimeLEDs();
+    buttonAlreadyPressed = false;
 }
     
-void startShowingTimeLEDs()
+bool datesAreTheSame(time_t dateTime1, time_t dateTime2)
 {
-    curTimeLEDBitPos = 0;
-    timerForLEDMuxing.attach(callbackForLEDMuxing, 0.001);
-}
-
-void stopShowingTimeLEDs()
-{
-    clearTimeLEDs();
+    struct tm * timeinfo;
+    timeinfo = localtime (&dateTime1);
+    int alarmMDay = timeinfo->tm_mday;
+    int alarmMonth = timeinfo->tm_mon;
+    int alarmYear = timeinfo->tm_year;
+    timeinfo = localtime (&dateTime2);
+    return (alarmMDay == timeinfo->tm_mday) && (alarmMonth == timeinfo->tm_mon) && (alarmYear == timeinfo->tm_year);
 }
 
 // Handle button press to read watch
 void buttonPressedCallback(void)
 {
+    // Handle debounce
+    if (buttonAlreadyPressed)
+        return;
+    buttonAlreadyPressed = true;
+    buttonDebounceTimeout.attach(buttonDebounceCallback, 0.2);
+    
+    // Stop alarm
+    AlarmOff();
+    
     // Get the time to display
-    time_t rawtime=rtc.time();
+    time_t curTime = rtc.time();
     
     // Check if we should display current time or alarm time
-    if (rawtime - timeDisplayLastButtonTime > timeDisplayMoveToShowAlarmSecs)
+    if (curTime - watchButtonLastPressed > DOUBLE_PRESS_SECS)
     {
-        timeDisplayState = timeDisplayState_ShowTime;
+        ledDisplay.start(rtc.time(), LedDisplay::DispType_CurTime, DISPLAY_ON_TIME, false);
     }
     else
     {
-        // TO BE DONE 
-        // - move alarm time value to the curBinaryLedDisplayVal
-        timeDisplayState = timeDisplayState_ShowAlarm;
+        bool alarmIsLaterToday = (alarmSetTime > curTime) && datesAreTheSame(alarmSetTime, curTime);
+        ledDisplay.start(alarmSetTime, LedDisplay::DispType_AlarmTime, DISPLAY_ON_TIME, alarmIsLaterToday);
     }
-    startShowingTimeLEDs();
     
     // Remember when button last pressed
-    timeDisplayLastButtonTime = rawtime;
-
-    // Update the button-state service
-    buttonServicePtr->updateButtonState(true);
+    watchButtonLastPressed = curTime;
 }
 
-// TEST CODE
+// Button released
 void buttonReleasedCallback(void)
 {
-    // Update the button-state service
-    buttonServicePtr->updateButtonState(false);
 }
 
 // Handle BLE disconnection - restart advertising
@@ -194,47 +142,107 @@
     ble.gap().startAdvertising();
 }
 
-// TEST CODE
+// Callback required for RTC operation
 void periodicCallback(void)
 {
     // Update the rtc library time (it says in the notes on the rtc lib that this needs to happen
     // more than once every few hundred seconds to avoid a rollover
-    rtc.time();
+    time_t curTime = rtc.time();
+    
+    // Update the time characteristic if we are connected
+    if (UPDATE_BLE_TIME_WHEN_CONNECTED)
+    {
+        Gap::GapState_t gapState = ble.gap().getState();
+        if (gapState.connected)
+        {
+            time_t curTime = rtc.time();
+            pWatchTimeService->writeWatchTime(curTime);
+        }
+    }
+    
+    // Check if alarm went off recently
+    if (alarmInitiatedTime + PERIODIC_CALLBACK_SECS*2 + ALARM_ON_TIME > curTime)
+    {
+        // Stop the alarm after it has been on for the allotted time
+        if (alarmInitiatedTime + ALARM_ON_TIME < curTime)
+            AlarmOff();
+    }
+    else
+    {
+        // Check if time for alarm
+        if (alarmSetTime > 0)
+        {
+            if ((curTime >= alarmSetTime) && (curTime < alarmSetTime + PERIODIC_CALLBACK_SECS*2))
+            {
+                AlarmOn();
+            }
+        }
+    }
+    
+    // Pulse the alarm
+    if (alarmState == AlarmState_on_sounding)
+    {
+        alarmState = AlarmState_on_resting;
+        vibrationMotor = 0;
+    }
+    else if (alarmState == AlarmState_on_resting)
+    {
+        alarmState = AlarmState_on_sounding;
+        vibrationMotor = 1;
+    }
+    
+//    // TEST TEST TEST
+//    int testMin = 0;
+//    int testHr = 0;
+//    if (testNum < 6)
+//        testMin = 1 << testNum;
+//    else
+//        testHr = 1 << (testNum - 6);
+//    testNum++;
+//    if (testNum >= 11)
+//        testNum = 0;
+//    
+//    uint8_t testTime[] = { uint8_t(2015/256), uint8_t(2015%256), 12, 18, testHr, testMin, 0 };
+//    time_t testTimeT = WatchTimeService::watchTimeToUnixTime(testTime);
+//
+//    ledDisplay.start(testTimeT, LedDisplay::DispType_CurTime, DISPLAY_ON_TIME);
+
 }
 
+// Helper
 void setRTCfromWatchTime(const uint8_t* pWatchTime)
 {
-    time_t timest = watchTimeToUnix(pWatchTime);
-    retval = timest;
+    time_t timest = WatchTimeService::watchTimeToUnixTime(pWatchTime);
     if ((int)timest != -1)
     {
         rtc.set_time(timest);
-        minPins[5] = !minPins[5];
     }
 }
 
 void onDataWrittenCallback(const GattWriteCallbackParams *params) 
 {
-    // TEST code
-    callbackCount++;
-    memcpy(testbuf, params->data, WatchTimeService::WatchTime_BlockSize);
-    servcode = params->handle;
-    buflen = params->len;
-    mycode = pWatchTimeService->getValueHandle();
-    butcode = buttonServicePtr->getValueHandle();
-    offs = params->offset;
-
     // Check if this is time setting
-    if (pWatchTimeService->getValueHandle() == params->handle) 
+    if (pWatchTimeService->getWatchTimeValueHandle() == params->handle) 
     {
         if (params->len == WatchTimeService::WatchTime_BlockSize)
         {
             setRTCfromWatchTime(params->data);
         }
     }
+    else if (pWatchTimeService->getAlarmTimeValueHandle() == params->handle) 
+    {
+        if (params->len == WatchTimeService::WatchTime_BlockSize)
+        {
+            time_t timest = WatchTimeService::watchTimeToUnixTime(params->data);
+            if (((int)timest != -1) && ((int)timest != 0))
+                alarmSetTime = timest;
+            else
+                alarmSetTime = 0;
+        }
+    }
 }
     
-// TEST CODE
+// DEBUG CODE
 void print_time() 
 {    
     time_t rawtime=rtc.time();
@@ -251,31 +259,35 @@
 {
     printf("AlarmWatch\r\n");
     
-    // TEST CODE
-    memset(testbuf, 0, WatchTimeService::WatchTime_BlockSize);
-    int loopCount = 0;
-    periodicCallbackToKeepTimerGoing.attach(periodicCallback, 1);
+    // This is necessary to keep the clock ticking - doesn't have to be so frequent
+    // According to this https://developer.mbed.org/users/fxschumacher/code/nRF51_rtc_example/file/c1f06d0a5e11/main.cpp
+    // any time less than 512 seconds is ok
+    periodicCallbackToKeepTimerGoing.attach(periodicCallback, PERIODIC_CALLBACK_SECS);
+    
+    // Handlers for the button press
     button.fall(buttonPressedCallback);
     button.rise(buttonReleasedCallback);
 
     // Clear display
-    clearTimeLEDs();
+    ledDisplay.clear();
     
     // BLE init
     ble.init();
     ble.gap().onDisconnection(disconnectionCallback);
     ble.onDataWritten(onDataWrittenCallback);
     
-    // TEST CODE
-    ButtonService buttonService(ble, false /* initial value for button pressed */);
-    buttonServicePtr = &buttonService;
-    
     // Watch Time Service
-    uint8_t initialTime[] = { uint8_t(2015/256), uint8_t(2015%256), 7, 26, 12, 8, 0 };    
-    WatchTimeService watchTimeService(ble, initialTime);
+    uint8_t initialTime[] = { uint8_t(2015/256), uint8_t(2015%256), 12, 18, 10, 10, 0 };  
+    uint8_t alarmTime[] = { 0, 0, 0, 0, 0, 0, 0 };  
+    WatchTimeService watchTimeService(ble, initialTime, alarmTime);
     setRTCfromWatchTime(initialTime);
     pWatchTimeService = &watchTimeService;
     
+    /* Enable over-the-air firmware updates. Instantiating DFUSservice introduces a
+     * control characteristic which can be used to trigger the application to
+     * handover control to a resident bootloader. */
+    DFUService dfu(ble);
+
     // Setup advertising
     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
@@ -284,175 +296,11 @@
     ble.gap().setAdvertisingInterval(1000); /* 1000ms. */
     ble.gap().startAdvertising();
 
-    uint8_t curWatchTime[WatchTimeService::WatchTime_BlockSize];
+    // Turn off any alarm
+    AlarmOff();
+
     while (true) 
     {
         ble.waitForEvent();
-        
-        // TEST CODE    
-        loopCount++;
-        if (loopCount < 5)
-            continue;
-        loopCount = 0;
-        
-        print_time();
-        // Get current time and convert to watch time
-        time_t rawtime=rtc.time();
-        unixTimeToWatchTime(rawtime, curWatchTime);
-        pWatchTimeService->writeWatchTime(curWatchTime);
-
-        
-/*        printf ("Timest %02x %02x %02x %02x %02x %02x %02x %ld\r\n", testbuf[0],
-                testbuf[1], testbuf[2], testbuf[3], testbuf[4], testbuf[5], testbuf[6], retval); 
-        printf ("serv %d buflen %d mycode %d offs %d butcode %d\r\n", servcode, buflen, mycode, offs, butcode);
-        printf ("val %ld\r\n", watchTimeService.getValueHandle());
-        print_time();
-        
-        if (timeDisplayState != timeDisplayState_None)
-        {
-            int watchLedTime = watchTimeToBCD(curBinaryLedDisplayVal);
-            printf("watchTime %04x = ", watchLedTime);
-            for (int i = 15; i >= 0; i--)
-            {
-                printf("%d", (watchLedTime >> i) % 2);
-            }
-            printf("\r\n");
-        }
-        */
     }
 }
-
-/*
-
-#include "mbed.h"
-#include "BLEDevice.h"
-#include "DeviceInformationService.h"
-
-// BLE Device etc
-BLEDevice  ble;
-DigitalOut ledIndicator(LED1);
-DigitalOut testOut(p1);
-
-// Device name
-const static char     DEVICE_NAME[] = "JOESALARM";
-
-// UUID for CurTimeService & TimeBlockCharacteristic
-const uint16_t UUID_CUR_TIME_SERVICE = 0xA000;
-const uint16_t UUID_TIME_BLOCK_CHARACTERISTIC = 0xA001;
-
-// List of supported service UUIDs
-static const uint16_t uuid16_list[] = {UUID_CUR_TIME_SERVICE};
-
-// Time is packed in an array of bytes
-const int SIZE_OF_TIME_BLOCK = 7;
-uint8_t timeBlockInitValue[SIZE_OF_TIME_BLOCK];
-uint8_t curTimeBlock[SIZE_OF_TIME_BLOCK];
-
-// GATT Characteristic for time block
-GattCharacteristic timeBlockCharacteristic(UUID_TIME_BLOCK_CHARACTERISTIC, timeBlockInitValue, SIZE_OF_TIME_BLOCK, SIZE_OF_TIME_BLOCK,
-        GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
-
-// Callback when connection lost
-void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
-{
-    ble.startAdvertising(); // restart advertising
-}
-
-// Callback for ticker
-int tickCount = 0;
-bool showInfo = false;
-int writeSinceLast = 0;
-void periodicCallback(void)
-{
-    ledIndicator = !ledIndicator; 
-    testOut = !testOut;
-    tickCount++;
-    if (tickCount > 100)
-    {
-        showInfo = true;
-        tickCount = 0;
-    }
-}
-
-// Data written callback
-void onDataWrittenCallback(const GattCharacteristicWriteCBParams *params) 
-{
-    if ((params->charHandle == timeBlockCharacteristic.getValueHandle())) 
-    {
-        // Validate time
-        int year = params->data[0] * 100 + params->data[1];
-        int month = params->data[2];
-        int day = params->data[3];
-//        && (params->len == SIZE_OF_TIME_BLOCK)
-        writeSinceLast = params->len;
-        memcpy(curTimeBlock, params->data, SIZE_OF_TIME_BLOCK);
-    }
-    writeSinceLast = true;
-}
-
-// Main
-int main(void)
-{
-    ledIndicator = 0;
-    testOut = 0;
-
-    // Ticker is interrupt driven
-    Ticker ticker;
-    ticker.attach_us(periodicCallback, 100000);
-
-    // Initial value for the time block characteristic
-    memset(timeBlockInitValue, 0, sizeof(timeBlockInitValue));
-
-    // Init BLE and register callbacks
-    ble.init();
-    ble.onDisconnection(disconnectionCallback);
-    ble.onDataWritten(onDataWrittenCallback);
-
-    // Add the time setting service
-    GattCharacteristic *charTable[] = {&timeBlockCharacteristic};
-    GattService timeBlockService(UUID_CUR_TIME_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
-    ble.addService(timeBlockService);
-    
-    // Setup advertising
-    // BREDR_NOT_SUPPORTED means classic bluetooth not supported;
-    // LE_GENERAL_DISCOVERABLE means that this peripheral can be
-    // discovered by any BLE scanner--i.e. any phone.
-    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
-    
-    // Add services to payload
-    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
-    
-    // This is where we're collecting the device name into the advertisement payload.
-    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
-    
-    // We'd like for this BLE peripheral to be connectable.
-    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
-    
-    // set the interval at which advertisements are sent out; this has
-    // an implication power consumption--radio activity being a
-    // biggest draw on average power. The other software controllable
-    // parameter which influences power is the radio's TX power
-    // level--there's an API to adjust that.
-    ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000));
-    
-
-    ble.startAdvertising();
-    
-    while (true) 
-    {
-        ble.waitForEvent();
-        
-        if (showInfo)
-        {
-            printf("Time info: ");
-            for (int i = 0; i < SIZE_OF_TIME_BLOCK; i++)
-            {
-                printf("%02d", curTimeBlock[i]);
-            }
-            printf(" - writeSinceLast %d\r\n", writeSinceLast);
-            showInfo = false;
-            writeSinceLast = 0;
-        }
-    }    
-}
-*/