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

main.cpp

Committer:
Bobty
Date:
2016-03-01
Revision:
6:4a12e0f03381
Parent:
5:2682353a8c32

File content as of revision 6:4a12e0f03381:

// BLE Alarm Watch
// Based on BLE examples on MBED
// Rob Dobson, (c) 2015

#include "mbed.h"
#include "BLE.h"
#include "LedDisplay.h"
#include "WatchTimeService.h"
#include "nrf51_rtc.h"
#include "DFUService.h"

// BLE platform
BLE ble;

// 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[] = {WatchTimeService::WATCHTIME_SERVICE_UUID};

// Service offering to read and set the time on the watch
WatchTimeService *pWatchTimeService;

// Time watch button last pressed
time_t watchButtonLastPressed = 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;

// 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;

// 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
{
    AlarmState_off,
    AlarmState_on_sounding,
    AlarmState_on_resting
};
AlarmState alarmState = AlarmState_off;

// Time alarm was activated
time_t alarmInitiatedTime = 0;

// Set alarm on
void AlarmOn()
{
    alarmState = AlarmState_on_sounding;
    vibrationMotor = 1;
    alarmInitiatedTime = rtc.time();
}

// Set alarm on
void AlarmOff()
{
    alarmState = AlarmState_off;
    vibrationMotor = 0;
}

// Button debounce callback
void buttonDebounceCallback()
{
    buttonAlreadyPressed = false;
}
    
bool datesAreTheSame(time_t dateTime1, time_t dateTime2)
{
    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 curTime = rtc.time();
    
    // Check if we should display current time or alarm time
    if (curTime - watchButtonLastPressed > DOUBLE_PRESS_SECS)
    {
        ledDisplay.start(rtc.time(), LedDisplay::DispType_CurTime, DISPLAY_ON_TIME, false);
    }
    else
    {
        bool alarmIsLaterToday = (alarmSetTime > curTime) && datesAreTheSame(alarmSetTime, curTime);
        ledDisplay.start(alarmSetTime, LedDisplay::DispType_AlarmTime, DISPLAY_ON_TIME, alarmIsLaterToday);
    }
    
    // Remember when button last pressed
    watchButtonLastPressed = curTime;
}

// Button released
void buttonReleasedCallback(void)
{
}

// Handle BLE disconnection - restart advertising
void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
    ble.gap().startAdvertising();
}

// 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
    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 = WatchTimeService::watchTimeToUnixTime(pWatchTime);
    if ((int)timest != -1)
    {
        rtc.set_time(timest);
    }
}

void onDataWrittenCallback(const GattWriteCallbackParams *params) 
{
    // Check if this is time setting
    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;
        }
    }
}
    
// DEBUG CODE
void print_time() 
{    
    time_t rawtime=rtc.time();
    
    // massage the time into a human-friendly format for printing
    struct tm * timeinfo;
    timeinfo = localtime(&rawtime);
    char date[24];
    strftime(date,sizeof(date),"%H:%M:%S on %m/%d/%G",timeinfo);
    printf("The current time is %s.\r\n",date);
}

int main(void)
{
    printf("AlarmWatch\r\n");
    
    // 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
    ledDisplay.clear();
    
    // BLE init
    ble.init();
    ble.gap().onDisconnection(disconnectionCallback);
    ble.onDataWritten(onDataWrittenCallback);
    
    // Watch Time Service
    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));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().setAdvertisingInterval(1000); /* 1000ms. */
    ble.gap().startAdvertising();

    // Turn off any alarm
    AlarmOff();

    while (true) 
    {
        ble.waitForEvent();
    }
}