Geo beacon for VF.

Dependencies:   MMA8452 aconno_bsp adc52832_common

main.cpp

Committer:
jurica238814
Date:
2017-11-24
Revision:
32:61d2328ec1a7
Parent:
31:caef580f5943
Child:
33:449fcc43b287

File content as of revision 32:61d2328ec1a7:

/*
 *
 *  Made by Jurica Resetar @ aconno
 *  aconno.de
 *  All rights reserved.
 *
 */

#include "mbed.h"
#include "ble/BLE.h"
#include "GapAdvertisingData.h"
#include "acd52832_bsp.h"
#include "mma8452.h"
#include "AckService.h"
#include "nrf52_uart.h"
#include "nrf52_digital.h"
#include "acn_nrf52_pwm.h"

#define DEBUG               (0)
#define DEBUG_ACC           (0)
#define DEBUG_PRINT_UART    (0)
#define DEBUG_MAC           (0)
#define DEBUG_CONNECTION    (0)
#define DEBUG_WAKEUP_BUZZER (0)

#define USE_ACC             (0)

#define SLEEP_TIME_S              (3.00)           /* Sleep time (in s)          */
#define ADV_TIMER_TIME_S          (1.00)           /* Advertising time (in s)    */
#define SCAN_TIMER_TIME_S         (0.25)           /* Scanning time (in s)       */
#define FREE_TIME_S               (0.1)            /* Time between end of a scanning and sleep mode */
#define AWAKE_TIME_S              (ADV_TIMER_TIME_S+SCAN_TIMER_TIME_S+FREE_TIME_S)      /* Was 0.15 */
#define SHORT_SLEEP_TIME_S        (0.5)            /* Shorter sleep time (s) */
#define SHORT_SLEEP_TIME_PERIOD_S (10)             /* Time after a last scanned advertisment. In the period, sleep time is SHORT_SLEEP_TIME */
#define BUZZER_FREQUENCY_Hz       (3000)
#define MAC_SIZE_B                (6)

/* Static constants for the BLE example */
#define MAX_BLE_PACKET_SIZE (31)
#define MSD_SIZE            (18)
#define MSD_ID              (0xFF)

#define BUZZ_TIME_S         (1)     /* Buzz time in s */
#define ADV_INTERVAL        (100)   /* Advertising interval (in ms) */
#define SCAN_INTERVAL       (SCAN_TIMER_TIME_S)   /* Scan interval (in ms) */
#define SCAN_WINDOW         (SCAN_TIMER_TIME_S)

/* Static constants for the accelerometer */
#define WHO_AM_I            0x0D           /* Type 'read' : This should return the device id of 0x2A */
#define OUT_Z_MSB           0x05           /* Type 'read' : z axis - 8 most significatn bit of a 12 bit sample */
#define I2C_DATA            (p29)
#define I2C_CLK             (p2)
#define INT2_PIN            (p4)
#define BUZZER              (p31)
#define USE_IBEACON_ONLY    (1)

typedef struct __attribute__((packed, aligned(1))){
  uint16_t appleID;
  uint8_t  secondID;
  uint8_t  DataSize;
  uint8_t  UUID[18];
  uint8_t  major;
  uint8_t  minor;
  int8_t   RSSI;
}IBeaconMSD;

IBeaconMSD ibeaconMSD;

#if DEBUG_PRINT_UART
    #include "nrf52_uart.h"
    NRF52_UART uart(p25, p26, Baud9600);
    char buffer[255];
    #define SEND(...) {uint8_t len = sprintf(buffer, __VA_ARGS__); uart.send(buffer, len);}
#else
    #define SEND(...)
#endif

bool beaconStateActive = 1;

bool shushShush = false;

uint8_t txPower = 4;
uint8_t sleepFlag = true;
uint8_t tempSleepTime = SLEEP_TIME_S;
uint8_t msd[MSD_SIZE] = {0x59, 0x00, 0xE1, 0x61, 0x35, 0xBA, 0xC0, 0xEC, 0x47, 0x2A, 0x98, 0x00, 0xAF, 0x18, 0x43, 0xFF, 0x05, 0x00};
uint8_t startBuzz[2] = {0xBA, 0xBE};
uint8_t stopBuzz[2] = {0xDE, 0xAD};
uint8_t myMacAddress[6] = {};   
uint8_t buzzer_flag = 0;

enum RadioState{
    OFF,
    ADVERTISING,
    SCANNING,
};
enum RadioState radioState = OFF;

void GoToSleep();
void StartAdvertising();
void startScanning();
void WakeMeUp();

Ticker WakeSleepT;
Ticker sleepChanger;
NRF52_PWM buzzer(NRF_PWM2);

enum advPayloadType{
    iBEACON = 0,
    BEACON
};

#if USE_ACC
    DigitalOut accPower(p7);
    DigitalOut i2cPower(p5);
    InterruptIn accPulse(INT2_PIN);
    Acc_MMA8452 acc(I2C_DATA, I2C_CLK, MMA8452_ADDRESS);

#endif
BLE &ble = BLE::Instance();
ACKService<4> *ackServicePtr;

#if DEBUG || DEBUG_MAC || DEBUG_CONNECTION
    NRF52_DigitalOut advLED(p22);         // Red
    NRF52_DigitalOut scanLED(p23);        // Blue
    NRF52_DigitalOut connectedLED(p24);   // Green
#endif

#if DEBUG_ACC
    NRF52_DigitalOut int_led(p22);
    NRF52_DigitalOut act_led(p22);
#endif


void buzzerStart(){
    buzzer.enable(BUZZER_FREQUENCY_Hz);
    //buzzer.enable(1);
    buzzer.enableChannel(0, BUZZER);
    buzzer.setDuty(0,0.5f);
}
void buzzerStop(){
    buzzer.enable(0);
    buzzer.setDuty(0, 0);
    buzzer.disable();
}

void onConnectionCallback(const Gap::ConnectionCallbackParams_t *params){   
    #if DEBUG_CONNECTION
        scanLED = !scanLED;       // Blue
        wait_ms(100);
        scanLED = !scanLED;       // Blue
        wait_ms(100);
        scanLED = !scanLED;       // Blue
        wait_ms(100);
        scanLED = !scanLED;       // Blue
        wait_ms(100);
        scanLED = !scanLED;       // Blue
        wait_ms(100);
        scanLED = !scanLED;       // Blue
        wait_ms(100);
        scanLED = 1;              // Blue
    #endif
    WakeSleepT.detach();
    sleepFlag = false;
}


/* Restart Advertising on disconnection*/
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params){
    buzzerStop();
    #if DEBUG_CONNECTION
        advLED = !advLED;       // RED
        wait_ms(100);
        advLED = !advLED;
        wait_ms(100);
        advLED = !advLED;
        wait_ms(100);
        advLED = !advLED;
        wait_ms(100);
        advLED = 1;
        wait_ms(100);
        advLED = 1;
    #endif
    WakeSleepT.attach(WakeMeUp, FREE_TIME_S);
    sleepFlag = true;
    
}

void onDataWrittenCallback(const GattWriteCallbackParams *params) {
    if(params->handle == ackServicePtr->getACKCharacteristicHandle()){
        // Something is written into AckCharacteristic
        if(params->data[0] == startBuzz[0]){
            if(params->data[1] == startBuzz[1]){
                #if DEBUG_CONNECTION
                    connectedLED = !connectedLED;       // BLUE
                    wait_ms(100);
                    connectedLED = !connectedLED;
                    wait_ms(100);
                    connectedLED = !connectedLED;
                    wait_ms(100);
                    connectedLED = !connectedLED;
                    wait_ms(100);
                    connectedLED = !connectedLED;
                    wait_ms(100);
                    connectedLED = 1;
                    wait_ms(100);
                #endif    
                buzzerStart();
                return;
            }
        }
        else if(params->data[0] == stopBuzz[0]){
            if(params->data[1] == stopBuzz[1]){
                buzzerStop();
                WakeSleepT.detach();
                WakeSleepT.attach(WakeMeUp, FREE_TIME_S);
                ble.disconnect(Gap::LOCAL_HOST_TERMINATED_CONNECTION);
            }
        }
        else if(params->data[0] == 0x55){
            /* This is used after pairing */
            radioState = SCANNING;
            WakeSleepT.detach();
            WakeSleepT.attach(WakeMeUp, FREE_TIME_S);
            shushShush = true;
            ble.disconnect(Gap::LOCAL_HOST_TERMINATED_CONNECTION);
        }
        
    }
    else{
        // Execute this for wrong data written into characteristic
        return;
    }
}

/**
 * This function is called when the ble initialization process has failed
 */
void onBleInitError(BLE &ble, ble_error_t error){
    /* Avoid compiler warnings */
    (void) ble;
    (void) error;
    /* Initialization error handling should go here */
}

/**
 * Callback triggered when the ble initialization process has finished
 */
void bleInitComplete(BLE::InitializationCompleteCallbackContext *params){
    BLE&        ble   = params->ble;
    ble_error_t error = params->error;

    if (error != BLE_ERROR_NONE) {
        /* In case of error, forward the error handling to onBleInitError */
        onBleInitError(ble, error);
        return;
    }

    /* Ensure that it is the default instance of BLE */
    if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
        return;
    }
    
    uint8_t init_values[4] = {0,0,0,0};
    /* Get my MAC address */
    BLEProtocol::AddressType_t temp_address_type;
    ble.gap().getAddress(&temp_address_type, myMacAddress);
    ackServicePtr = new ACKService<4>(ble, init_values);
    ackServicePtr->updateMacAddress(myMacAddress);    // Update MAC address
    
    ble.gap().onDisconnection(disconnectionCallback);
    ble.gap().onConnection(onConnectionCallback);         // -->> Uncomment these two lines for shush-shush 
    ble.gattServer().onDataWritten(onDataWrittenCallback);
    
    //ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, (uint8_t *)msd, MSD_SIZE);
    
    /*
     *  iBeacon
     */
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::LE_GENERAL_DISCOVERABLE | GapAdvertisingData::BREDR_NOT_SUPPORTED);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, (uint8_t*)&ibeaconMSD, sizeof(ibeaconMSD));
    
    ble.gap().setAdvertisingInterval(ADV_INTERVAL);  // --> Has to be at least 100ms!
}

void changeAdvPaylod(advPayloadType newState){
    if(newState == iBEACON){
        /*
         *  iBeacon
         */
        ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::LE_GENERAL_DISCOVERABLE | GapAdvertisingData::BREDR_NOT_SUPPORTED);
        ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, (uint8_t*)&ibeaconMSD, sizeof(ibeaconMSD));
    }
    else{
        ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, (uint8_t *)msd, MSD_SIZE);
    }
}

uint8_t findMSDIndex(const Gap::AdvertisementCallbackParams_t *params){
    uint8_t i=0;
    uint8_t advLen = params->advertisingDataLen;
    uint8_t dataLen;
    
    if((advLen < (MAC_SIZE_B + 2)) || advLen == 0){
        // Empty advertisement or not long enough for MAX
        // +2 for SIZE and MSD ID
        return 0;
    }
    
    do{
        dataLen = params->advertisingData[i];
        i++;
        if(params->advertisingData[i] == MSD_ID) return i;
        else i += (dataLen);
    }while(i<advLen);
    
    return 0;
}

uint8_t CheckMac(const Gap::AdvertisementCallbackParams_t *params, uint8_t *myMacAddress, uint8_t msdOffset){
    int i=0;
    
    /* Get my MAC address */
    BLEProtocol::AddressType_t temp_address_type;
    ble.gap().getAddress(&temp_address_type, myMacAddress);
    
    if(!msdOffset){
        #if DEBUG_MAC
        for(i=0; i<10; i++){
            scanLED = !scanLED; // BLUE
            wait_ms(100);
        }
        #endif
        return 0;     // There's no MSD in BLE advertisement data
    }
    for(i=0; i<6; i++){
        if(params->advertisingData[msdOffset + 3 + i] != myMacAddress[5-i]){      // myMacAddress[0] == 0x91
            #if DEBUG_MAC
                for(i=0; i<10; i++){
                    connectedLED = !connectedLED; // Green
                    wait_ms(100);
                }
            #endif
            return 0;
        }
    }       
    #if DEBUG_MAC
        for(i=0; i<10; i++){
            advLED = !advLED; // RED
            wait_ms(100);
        }
        advLED = 1;
    #endif
    return 1;
}

/**
 *  Function is called when BLE radio discovers any kind of advertisment 
 */
void advertisementCallback(const Gap::AdvertisementCallbackParams_t *params){
    uint8_t msdOffset;
                    
    msdOffset = findMSDIndex(params);       // Should be 1 or 4
    if(msdOffset == 0){
        return;     // There's no MSD in BLE advertisement data
    }
    if ((params->advertisingData[msdOffset]) == MSD_ID){
        // Follows Manufacturer Specific Data
        if ((params->advertisingData[msdOffset+1]) == 0x59){
            if ((params->advertisingData[msdOffset+2]) == 0x00){
                if(CheckMac(params, myMacAddress, msdOffset)){
                    //ble.gap().stopScan();
                    WakeSleepT.detach();
                    WakeSleepT.attach(WakeMeUp, FREE_TIME_S);
                }
            }
        }
    }
}


/* Call this function few minutes (TBD) after a last scanned advertisment */
void changeSleepTime(){
    tempSleepTime = SLEEP_TIME_S;    
    sleepChanger.detach();
}

void startAdvertising(){
    static uint8_t cnt = 0;
    advPayloadType nextState;
    if(shushShush){
        // Do not advertise! Go to sleep
        WakeSleepT.detach();
        ble.gap().stopAdvertising();
        WakeMeUp();
    }
    else{
        if(USE_IBEACON_ONLY || !cnt){
            nextState = iBEACON;
            changeAdvPaylod(nextState);
        }
        else if(cnt==1){
            nextState = BEACON;
            changeAdvPaylod(nextState);
        }
        cnt = (cnt+1)%5;    // Every fifth advertising change to iBeacon
        
        ble.gap().startAdvertising();
        #if DEBUG
            advLED = 0;
            scanLED = 1;
        #endif
        WakeSleepT.detach();
        WakeSleepT.attach(WakeMeUp, ADV_TIMER_TIME_S);    // Call the wakeMeUp function
    }
}

void startScanning(){
    ble.gap().stopAdvertising();
    ble.gap().setScanInterval(SCAN_INTERVAL);
    ble.gap().setScanWindow(SCAN_WINDOW);
    ble.gap().setScanParams();
    ble.gap().startScan(advertisementCallback);
    #if DEBUG
        advLED = 1;
        scanLED = 0;
    #endif
    WakeSleepT.detach();
    WakeSleepT.attach(WakeMeUp, SCAN_TIMER_TIME_S);
}

void WakeMeUp(){
    sleepFlag = 0;
    switch(radioState){
        case OFF:{
                radioState = SCANNING;
                startAdvertising();
                break;
            }
        case ADVERTISING:{
                radioState = SCANNING;
                startScanning();
                break;
            }
        case SCANNING:{
                radioState = OFF;
                WakeSleepT.detach();
                //WakeSleepT.attach(GoToSleep, FREE_TIME_S);
                GoToSleep();
                break;
            }
        default: return;
    }
}

void GoToSleep(){
    WakeSleepT.detach();
    WakeSleepT.attach(WakeMeUp, tempSleepTime);
    ble.gap().stopAdvertising();
    ble.gap().stopScan();
    sleepFlag = 1;
    #if DEBUG
        advLED = 1;
        scanLED = 1;
    #endif
}

#if USE_ACC
    void pulse_handler(){
    #if DEBUG_WAKEUP_BUZZER
        buzzerStart();
        wait_ms(50);
        buzzerStop();
    #endif
    #if DEBUG_ACC
        int_led = !int_led;
    #endif
    shushShush = false;
    }
#endif

int main(){
    #if DEBUG || DEBUG_MAC
        advLED = 1;
        scanLED = 1;
        connectedLED = 1;
    #endif
    
    #if USE_ACC
        accPower = 1;
        i2cPower = 1;
    #endif 
    
    //WakeSleepT.attach(GoToSleep, AWAKE_TIME_S);
    GoToSleep();
    ble.init(bleInitComplete);
    ble.gap().setTxPower(txPower);
    /* SpinWait for initialization to complete. This is necessary because the BLE object is used in the main loop below. */
    while (ble.hasInitialized()  == false){ /* spin loop */ }
    __enable_irq();
            
    buzzerStart();
    wait_ms(500);
    buzzerStop();
    
    ibeaconMSD.appleID = 0x004C;
    ibeaconMSD.secondID = 0x02;
    ibeaconMSD.DataSize = 0x15;
    ibeaconMSD.UUID[0] = 0x11;
    ibeaconMSD.UUID[1] = 0x22;
    ibeaconMSD.UUID[2] = 0x33;
    ibeaconMSD.RSSI = -4;
    
    WakeSleepT.attach(GoToSleep, AWAKE_TIME_S);
    
    while(true){   
        ble.waitForEvent();
    }
}