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:
2015-12-14
Revision:
5:2682353a8c32
Parent:
4:f0b030a3223f
Child:
6:4a12e0f03381

File content as of revision 5:2682353a8c32:

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

#include "mbed.h"
#include "BLE.h"
#include "ButtonService.h"
#include "WatchTimeService.h"
#include "nrf51_rtc.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);

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

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

// Cur time disp info
int curTimeLEDBitPos = 0;

// 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_t watchTimeToUnix(const uint8_t* pWatchTime)
{
    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;
}

void unixTimeToWatchTime(time_t unixTime, uint8_t* pWatchTime)
{
    // 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;
}

int watchTimeToBCD(const uint8_t* pWatchTime)
{
    // 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;
}

//uint8_t* GetCurTimeAsWatchTime()
//{
//    // Get current time and convert to displayable time
//    time_t rawtime=rtc.time();
//    unixTimeToWatchTime(rawtime, curBinaryLedDisplayVal);
//}
    
void nextShowingTimeLEDs()
{
    // 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();
}
    
void startShowingTimeLEDs()
{
    curTimeLEDBitPos = 0;
    timerForLEDMuxing.attach(callbackForLEDMuxing, 0.001);
}

void stopShowingTimeLEDs()
{
    clearTimeLEDs();
}

// Handle button press to read watch
void buttonPressedCallback(void)
{
    // Get the time to display
    time_t rawtime=rtc.time();
    
    // Check if we should display current time or alarm time
    if (rawtime - timeDisplayLastButtonTime > timeDisplayMoveToShowAlarmSecs)
    {
        timeDisplayState = timeDisplayState_ShowTime;
    }
    else
    {
        // TO BE DONE 
        // - move alarm time value to the curBinaryLedDisplayVal
        timeDisplayState = timeDisplayState_ShowAlarm;
    }
    startShowingTimeLEDs();
    
    // Remember when button last pressed
    timeDisplayLastButtonTime = rawtime;

    // Update the button-state service
    buttonServicePtr->updateButtonState(true);
}

// TEST CODE
void buttonReleasedCallback(void)
{
    // Update the button-state service
    buttonServicePtr->updateButtonState(false);
}

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

// TEST CODE
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();
}

void setRTCfromWatchTime(const uint8_t* pWatchTime)
{
    time_t timest = watchTimeToUnix(pWatchTime);
    retval = timest;
    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 (params->len == WatchTimeService::WatchTime_BlockSize)
        {
            setRTCfromWatchTime(params->data);
        }
    }
}
    
// TEST 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");
    
    // TEST CODE
    memset(testbuf, 0, WatchTimeService::WatchTime_BlockSize);
    int loopCount = 0;
    periodicCallbackToKeepTimerGoing.attach(periodicCallback, 1);
    button.fall(buttonPressedCallback);
    button.rise(buttonReleasedCallback);

    // Clear display
    clearTimeLEDs();
    
    // 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);
    setRTCfromWatchTime(initialTime);
    pWatchTimeService = &watchTimeService;
    
    // 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();

    uint8_t curWatchTime[WatchTimeService::WatchTime_BlockSize];
    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;
        }
    }    
}
*/