Import from github.com/google/uribeacon/beacons/mbed

Dependencies:   BLE_API mbed nRF51822

main.cpp

Committer:
schilit
Date:
2015-02-25
Revision:
0:9033b372f4fe

File content as of revision 0:9033b372f4fe:

/*
 * Copyright 2014-2015 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdint.h>
#include <stddef.h>
#include <nrf_error.h>
#include "mbed.h"
#include "BLEDevice.h"
#include "URIBeaconConfigService.h"
#include "DFUService.h"
#include "pstorage.h"
#include "DeviceInformationService.h"

// Struct to hold persistent data across power cycles
struct PersistentData_t {
    uint32_t magic;
    URIBeaconConfigService::Params_t params;
    uint8_t pad[4];
} __attribute__ ((aligned (4)));

static const int PERSISTENT_DATA_ALIGNED_SIZE = sizeof(PersistentData_t);
// Seconds after power-on that config service is available.
static const int ADVERTISING_TIMEOUT_SECONDS = 60;
// Advertising interval for config service.
static const int ADVERTISING_INTERVAL_MSEC = 1000;
// Maximum size of service data in ADV packets
static const int SERVICE_DATA_MAX = 31;
// Magic that identifies persistent storage
static const uint32_t MAGIC = 0x1BEAC000;
// Values for ADV packets related to firmware levels
static URIBeaconConfigService::PowerLevels_t  defaultAdvPowerLevels = {-20, -4, 0, 10};
// Values for setTxPower() indexed by power mode.
static const int8_t firmwarePowerLevels[] = {-20, -4, 0, 10};

BLEDevice ble;
URIBeaconConfigService *uriBeaconConfig;
pstorage_handle_t pstorageHandle;
PersistentData_t  persistentData;

/* LEDs for indication */
DigitalOut  connectionStateLed(LED1);
DigitalOut  advertisingStateLed(LED2);

void blink(int count) {
    for (int i = 0; i <= count; i++) {
        advertisingStateLed = !advertisingStateLed;
        wait(0.2);
        advertisingStateLed = !advertisingStateLed;
        wait(0.2);
    }
}

/* Dummy callback handler needed by Nordic's pstorage module. */
void pstorageNotificationCallback(pstorage_handle_t *p_handle,
                                  uint8_t            op_code,
                                  uint32_t           result,
                                  uint8_t *          p_data,
                                  uint32_t           data_len) {
    /* APP_ERROR_CHECK(result); */
}

void pstorageLoad() {
    pstorage_init();
    pstorage_module_param_t pstorageParams = {
        .cb          = pstorageNotificationCallback,
        .block_size  = PERSISTENT_DATA_ALIGNED_SIZE,
        .block_count = 1
    };
    pstorage_register(&pstorageParams, &pstorageHandle);
    if (pstorage_load(reinterpret_cast<uint8_t *>(&persistentData),
                      &pstorageHandle, PERSISTENT_DATA_ALIGNED_SIZE, 0) != NRF_SUCCESS) {
        // On failure zero out and let the service reset to defaults
        memset(&persistentData, 0, sizeof(PersistentData_t));
    }
}


void pstorageSave() {
    if (persistentData.magic != MAGIC) {
        persistentData.magic = MAGIC;
        pstorage_store(&pstorageHandle,
                       reinterpret_cast<uint8_t *>(&persistentData),
                       sizeof(PersistentData_t),
                       0 /* offset */);
    } else {
        pstorage_update(&pstorageHandle,
                        reinterpret_cast<uint8_t *>(&persistentData),
                        sizeof(PersistentData_t),
                        0 /* offset */);
    }
}

void startAdvertisingUriBeaconConfig() {
    char  DEVICE_NAME[] = "mUriBeacon Config";

    ble.clearAdvertisingPayload();

    // Stops advertising the UriBeacon Config Service after a delay
    ble.setAdvertisingTimeout(ADVERTISING_TIMEOUT_SECONDS);

    ble.accumulateAdvertisingPayload(
        GapAdvertisingData::BREDR_NOT_SUPPORTED |
        GapAdvertisingData::LE_GENERAL_DISCOVERABLE);

    // UUID is in different order in the ADV frame (!)
    uint8_t reversedServiceUUID[sizeof(UUID_URI_BEACON_SERVICE)];
    for (unsigned int i = 0; i < sizeof(UUID_URI_BEACON_SERVICE); i++) {
        reversedServiceUUID[i] =
            UUID_URI_BEACON_SERVICE[sizeof(UUID_URI_BEACON_SERVICE) - i - 1];
    }
    ble.accumulateAdvertisingPayload(
        GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
        reversedServiceUUID,
        sizeof(reversedServiceUUID));

    ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG);
    ble.accumulateScanResponse(
        GapAdvertisingData::COMPLETE_LOCAL_NAME,
        reinterpret_cast<uint8_t *>(&DEVICE_NAME),
        sizeof(DEVICE_NAME));
    ble.accumulateScanResponse(
        GapAdvertisingData::TX_POWER_LEVEL,
        reinterpret_cast<uint8_t *>(
            &defaultAdvPowerLevels[URIBeaconConfigService::TX_POWER_MODE_LOW]),
        sizeof(uint8_t));

    ble.setTxPower(
        firmwarePowerLevels[URIBeaconConfigService::TX_POWER_MODE_LOW]);

    ble.setDeviceName(reinterpret_cast<uint8_t *>(&DEVICE_NAME));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.setAdvertisingInterval(
        Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(ADVERTISING_INTERVAL_MSEC));
    ble.startAdvertising();
}


void startAdvertisingUriBeacon() {
    uint8_t serviceData[SERVICE_DATA_MAX];
    int serviceDataLen = 0;

    advertisingStateLed = 1;
    connectionStateLed = 1;

    ble.shutdown();
    ble.init();

    // Fields from the Service
    int beaconPeriod = persistentData.params.beaconPeriod;
    int txPowerMode = persistentData.params.txPowerMode;
    int uriDataLength = persistentData.params.uriDataLength;
    URIBeaconConfigService::UriData_t &uriData = persistentData.params.uriData;
    URIBeaconConfigService::PowerLevels_t &advPowerLevels =
        persistentData.params.advPowerLevels;
    uint8_t flags = persistentData.params.flags;

    pstorageSave();

    delete uriBeaconConfig;
    uriBeaconConfig = NULL;

    ble.clearAdvertisingPayload();
    ble.setTxPower(firmwarePowerLevels[txPowerMode]);

    ble.setAdvertisingType(
        GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);

    ble.setAdvertisingInterval(
        Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(beaconPeriod));

    ble.accumulateAdvertisingPayload(
        GapAdvertisingData::BREDR_NOT_SUPPORTED |
        GapAdvertisingData::LE_GENERAL_DISCOVERABLE);

    ble.accumulateAdvertisingPayload(
        GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, BEACON_UUID,
        sizeof(BEACON_UUID));

    serviceData[serviceDataLen++] = BEACON_UUID[0];
    serviceData[serviceDataLen++] = BEACON_UUID[1];
    serviceData[serviceDataLen++] = flags;
    serviceData[serviceDataLen++] = advPowerLevels[txPowerMode];
    for (int j=0; j < uriDataLength; j++) {
        serviceData[serviceDataLen++] = uriData[j];
    }

    ble.accumulateAdvertisingPayload(
        GapAdvertisingData::SERVICE_DATA,
        serviceData, serviceDataLen);

    ble.startAdvertising();
}

// After advertising timeout, stop config and switch to UriBeacon
void timeout(void) {
    Gap::GapState_t state;
    state = ble.getGapState();
    if (!state.connected) {
        startAdvertisingUriBeacon();
    }
}

// When connected to config service, change the LEDs
void connectionCallback(Gap::Handle_t handle,
                        Gap::addr_type_t peerAddrType,
                        const Gap::address_t peerAddr,
                        const Gap::ConnectionParams_t *params) {
    advertisingStateLed = 1;
    connectionStateLed = 0;
}

// When disconnected from config service, start advertising UriBeacon
void disconnectionCallback(Gap::Handle_t handle,
                           Gap::DisconnectionReason_t reason) {
    advertisingStateLed = 0;    // on
    connectionStateLed = 1;     // off
    startAdvertisingUriBeacon();
}

int main(void) {
    URIBeaconConfigService::UriData_t uriData  = {
        // http://uribeacon.org
        0x02, 'u', 'r', 'i', 'b', 'e', 'a', 'c', 'o', 'n', 0x08
    };
    int uriDataLength = 11;

    advertisingStateLed = 0;    // on
    connectionStateLed = 1;     // off

    ble.init();
    ble.onDisconnection(disconnectionCallback);
    ble.onConnection(connectionCallback);
    // Advertising timeout
    ble.onTimeout(timeout);

    pstorageLoad();
    bool resetToDefaults = persistentData.magic != MAGIC;
    uriBeaconConfig = new URIBeaconConfigService(
        ble, persistentData.params, resetToDefaults,
        uriData, uriDataLength, defaultAdvPowerLevels);
    if (!uriBeaconConfig->configuredSuccessfully()) {
        error("failed to accommodate URI");
    }

    // Setup auxiliary services to allow over-the-air firmware updates, etc
    DFUService dfu(ble);
    DeviceInformationService deviceInfo(
        ble, "ARM", "UriBeacon", "SN1", "hw-rev1", "fw-rev1", "soft-rev1");


    startAdvertisingUriBeaconConfig();

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