Đức Thanh Nguyễn
Đức Thanh Nguyễn

Reputation: 9365

Get title and message of an iOs notification forwarded to esp32 (ANCS)

I'm playing around with this library and reading through the Apple Notification Center Service(ANCS) but still have a hard time getting message and title of an iOs notification forwarded to my esp32 board and output them to the Serial Monitor inside Arduino Studio. With this code, I can pair my iPhone with the esp32 board and when notifications get send over but all I got is the category like this:

20:01:50.225 -> ********************
20:01:50.225 -> **Device connected**
20:01:50.225 -> 30:9a:77:**:**:**
20:01:50.225 -> ********************
20:01:53.567 -> New notification!
20:01:53.567 ->   Category: News

Could someone tell me what am I do wrong with this code? (Arduino_ESP32_ANCS.ino, the Task.h and Task.cpp files are available on github)

P.S: I use the default "Shortcuts" app on iPhone to trigger fake notification to test if anyone is interested in how you can have notifications to test.

// Original: https://github.com/S-March/esp32_ANCS
// fixed for Arduino15/packages/esp32/hardware/esp32/1.0.3

#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEClient.h"
#include "BLEUtils.h"
#include "BLE2902.h"
#include <esp_log.h>
#include <esp_bt_main.h>
#include <string>
#include "Task.h"
#include <sys/time.h>
#include <time.h>
#include "sdkconfig.h"

static char LOG_TAG[] = "SampleServer";

static BLEUUID ancsServiceUUID("7905F431-B5CE-4E99-A40F-4B1E122D00D0");
static BLEUUID notificationSourceCharacteristicUUID("9FBF120D-6301-42D9-8C58-25E699A21DBD");
static BLEUUID controlPointCharacteristicUUID("69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9");
static BLEUUID dataSourceCharacteristicUUID("22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB");

BLERemoteCharacteristic* pControlPointCharacteristic;

class MySecurity : public BLESecurityCallbacks {
    
    uint32_t onPassKeyRequest(){
        ESP_LOGI(LOG_TAG, "PassKeyRequest");
        return 123456;  // Returning a default passkey
    }

    void onPassKeyNotify(uint32_t pass_key){
        ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key);
    }

    bool onSecurityRequest(){
        ESP_LOGI(LOG_TAG, "On Security Request");
        return true;
    }
    
    bool onConfirmPIN(uint32_t pin){
        ESP_LOGI(LOG_TAG, "On Confirm Pin Request with pin: %d", pin);
        return true;  // You should return true to confirm the pin
    }

    void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){
        ESP_LOGI(LOG_TAG, "Starting BLE work!");
        if(cmpl.success){
            uint16_t length;
            esp_ble_gap_get_whitelist_size(&length);
            ESP_LOGD(LOG_TAG, "size: %d", length);
        }
    }
};


static void dataSourceNotifyCallback(
  BLERemoteCharacteristic* pDataSourceCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
    Serial.print(pDataSourceCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);

    // Parse the response
    size_t index = 0;
    uint8_t commandID = pData[index++];
    uint32_t notificationUID = pData[index++] | (pData[index++] << 8) | (pData[index++] << 16) | (pData[index++] << 24);

    while (index < length) {
        uint8_t attributeID = pData[index++];
        uint16_t attributeLength = pData[index++] | (pData[index++] << 8);

        if (attributeID == 1) {  // Title
            Serial.print("Title: ");
        } else if (attributeID == 2) {  // Message
            Serial.print("Message: ");
        } else if (attributeID == 3) {  // App Identifier
            Serial.print("App Identifier: ");
        }

        for (uint16_t i = 0; i < attributeLength; i++) {
            Serial.print((char)pData[index + i]);
        }
        Serial.println();
        index += attributeLength;
    }
}

static void NotificationSourceNotifyCallback(
    BLERemoteCharacteristic* pNotificationSourceCharacteristic,
    uint8_t* pData,
    size_t length,
    bool isNotify) {
    if (pData[0] == 0) {
        Serial.println("New notification!");
        Serial.print("  ");
        switch (pData[2]) {
            case 0: Serial.println("Category: Other"); break;
            case 1: Serial.println("Category: Incoming call"); break;
            case 2: Serial.println("Category: Missed call"); break;
            case 3: Serial.println("Category: Voicemail"); break;
            case 4: Serial.println("Category: Social"); break;
            case 5: Serial.println("Category: Schedule"); break;
            case 6: Serial.println("Category: Email"); break;
            case 7: Serial.println("Category: News"); break;
            case 8: Serial.println("Category: Health"); break;
            case 9: Serial.println("Category: Business"); break;
            case 10: Serial.println("Category: Location"); break;
            case 11: Serial.println("Category: Entertainment"); break;
            default: break;
        }

        // Call the function to get notification attributes
        getNotificationAttributes(pControlPointCharacteristic, *(uint32_t*)(pData + 4));
    }
}


/**
 * Become a BLE client to a remote BLE server.  We are passed in the address of the BLE server
 * as the input parameter when the task is created.
 */
class MyClient : public Task {
    void run(void* data) {
        BLEAddress* pAddress = (BLEAddress*)data;
        BLEClient* pClient = BLEDevice::createClient();
        BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
        BLEDevice::setSecurityCallbacks(new MySecurity());

        BLESecurity* pSecurity = new BLESecurity();
        pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
        pSecurity->setCapability(ESP_IO_CAP_IO);
        pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);

        // Connect to the remote BLE Server.
        pClient->connect(*pAddress);

        /** BEGIN ANCS SERVICE **/
        // Obtain a reference to the service we are after in the remote BLE server.
        BLERemoteService* pAncsService = pClient->getService(ancsServiceUUID);
        if (pAncsService == nullptr) {
            ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", ancsServiceUUID.toString().c_str());
            return;
        }

        // Obtain a reference to the characteristics in the service of the remote BLE server.
        BLERemoteCharacteristic* pNotificationSourceCharacteristic = pAncsService->getCharacteristic(notificationSourceCharacteristicUUID);
        if (pNotificationSourceCharacteristic == nullptr) {
            ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", notificationSourceCharacteristicUUID.toString().c_str());
            return;
        }

        pControlPointCharacteristic = pAncsService->getCharacteristic(controlPointCharacteristicUUID);
        if (pControlPointCharacteristic == nullptr) {
            ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", controlPointCharacteristicUUID.toString().c_str());
            return;
        }

        BLERemoteCharacteristic* pDataSourceCharacteristic = pAncsService->getCharacteristic(dataSourceCharacteristicUUID);
        if (pDataSourceCharacteristic == nullptr) {
            ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", dataSourceCharacteristicUUID.toString().c_str());
            return;
        }

        const uint8_t v[] = {0x1, 0x0};
        pDataSourceCharacteristic->registerForNotify(dataSourceNotifyCallback);
        pDataSourceCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)v, 2, true);
        pNotificationSourceCharacteristic->registerForNotify(NotificationSourceNotifyCallback);
        pNotificationSourceCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)v, 2, true);
        /** END ANCS SERVICE **/
    } // run
}; // MyClient


class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) {
        Serial.println(" ");
        Serial.println("********************");
        Serial.println("**Device connected**");
        Serial.println(BLEAddress(param->connect.remote_bda).toString().c_str());
        Serial.println("********************");
        MyClient* pMyClient = new MyClient();
        pMyClient->setStackSize(18000);
        pMyClient->start(new BLEAddress(param->connect.remote_bda));
    };

    void onDisconnect(BLEServer* pServer) {
        Serial.println(" ");
        Serial.println("************************");
        Serial.println("**Device  disconnected**");
        Serial.println("************************");
    }
};

class MainBLEServer: public Task {
    void run(void *data) {
        ESP_LOGD(LOG_TAG, "Starting BLE work!");
        esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG));
        esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG));

        // Initialize device
        BLEDevice::init("ANCS");
        BLEServer* pServer = BLEDevice::createServer();
        pServer->setCallbacks(new MyServerCallbacks());
        BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
        BLEDevice::setSecurityCallbacks(new MySecurity());

        // Advertising parameters:
        // Soliciting ANCS
        BLEAdvertising *pAdvertising = pServer->getAdvertising();
        BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
        oAdvertisementData.setFlags(0x01);
        _setServiceSolicitation(&oAdvertisementData, BLEUUID("7905F431-B5CE-4E99-A40F-4B1E122D00D0"));
        pAdvertising->setAdvertisementData(oAdvertisementData);        

        // Set security
        BLESecurity *pSecurity = new BLESecurity();
        pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
        pSecurity->setCapability(ESP_IO_CAP_OUT);
        pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);

        //Start advertising
        pAdvertising->start();
        
        ESP_LOGD(LOG_TAG, "Advertising started!");
        delay(portMAX_DELAY);
    }

    
    /**
     * @brief Set the service solicitation (UUID)
     * @param [in] uuid The UUID to set with the service solicitation data.  Size of UUID will be used.
     */
    void _setServiceSolicitation(BLEAdvertisementData *a, BLEUUID uuid)
    {
        char cdata[2];
        switch(uuid.bitSize()) {
            case 16: {
                // [Len] [0x14] [UUID16] data
                cdata[0] = 3;
                cdata[1] = ESP_BLE_AD_TYPE_SOL_SRV_UUID;  // 0x14
                String uuidString = String((char *)&uuid.getNative()->uuid.uuid16, 2);
                a->addData(String(cdata[0], DEC) + String(cdata[1], DEC) + uuidString);
                break;
            }
        
            case 128: {
                // [Len] [0x15] [UUID128] data
                cdata[0] = 17;
                cdata[1] = ESP_BLE_AD_TYPE_128SOL_SRV_UUID;  // 0x15
                String uuidString = String((char *)uuid.getNative()->uuid.uuid128, 16);
                a->addData(String(cdata[0], DEC) + String(cdata[1], DEC) + uuidString);
                break;
            }
        
            default:
                return;
        }
    }

};

// New from 19:44

void getNotificationAttributes(BLERemoteCharacteristic* pControlPointCharacteristic, uint32_t notificationUID) {
    // Command ID for Get Notification Attributes
    const uint8_t commandID = 0;
    // Attributes we want to retrieve (Title, Message, AppIdentifier)
    const uint8_t attributes[] = {1, 2, 3};

    // Create the command to send
    std::vector<uint8_t> command;
    command.push_back(commandID);

    // Add the notification UID
    command.push_back(notificationUID & 0xFF);
    command.push_back((notificationUID >> 8) & 0xFF);
    command.push_back((notificationUID >> 16) & 0xFF);
    command.push_back((notificationUID >> 24) & 0xFF);

    // Add the attributes and their max lengths (e.g., 100 bytes)
    for (uint8_t attr : attributes) {
        command.push_back(attr);
        command.push_back(100);
        command.push_back(0);
    }

    // Send the command
    pControlPointCharacteristic->writeValue(command.data(), command.size(), true);
}


void SampleSecureServer(void)
{
    MainBLEServer* pMainBleServer = new MainBLEServer();
    pMainBleServer->setStackSize(20000);
    pMainBleServer->start();
    Serial.print("");
    Serial.print("BLEServer started.");    
    digitalWrite(LED_BUILTIN, HIGH);  // turn the LED on (HIGH is the voltage level)
}

void setup()
{
    Serial.print("ANCS started.");    
    Serial.begin(115200);
    SampleSecureServer();
    // initialize digital pin LED_BUILTIN as an output.
    pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{   
}

Upvotes: 0

Views: 56

Answers (0)

Related Questions