nkolban / esp32-snippets

Sample ESP32 snippets and code fragments
https://leanpub.com/kolban-ESP32
Apache License 2.0
2.34k stars 711 forks source link

ANCS library #430

Closed S-March closed 6 years ago

S-March commented 6 years ago

Hi Neil,

I decided to start putting together an ANCS library to use the ESP32 as a smart watch. Right now I'm a little stuck and was wondering if I could pick your brain.

Normally, for ANCS to work, the device must first advertise while soliciting service from the ANCS UUID.

I added the following function to BLEAdvertising so that you can include a service solicitation in the advert packet (this allows the device to show up in the settings panel of an iOS device).

void setServiceSolicitation(BLEUUID uuid);

/**
* @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 BLEAdvertisementData::setServiceSolicitation(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
            addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2));
            break;
        }

        case 128: {
            // [Len] [0x15] [UUID128] data
            cdata[0] = 17;
            cdata[1] = ESP_BLE_AD_TYPE_128SOL_SRV_UUID;  // 0x15
            addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16));
            break;
        }

        default:
            return;
    }
} // setServiceSolicitationData

Once you find the device, you can connect in the settings panel. I added the ability to bond in my code, which it does just fine. After bonded, the device shows as connected - perfect.

Here is where I'm stuck. After the device is bonded and connected, the ESP needs to switch out of a server role and into a client role to then subscribe to the ANCS service on the iOS device and read/write to the three characteristics exposed.

I may just be in way over my head, but I can't seem to find a way to force a connection to a white-listed device. Or if I'm already connected, to maintain that connection while I dismantle the server and spin up the client. Any ideas?

Here is the code that I have so far (gets as far as bonding/connecting to device after the advertising stage)

/**
 * Create a new BLE server.
 */
#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEUtils.h"
#include "BLE2902.h"
#include <esp_log.h>
#include <string>
#include <Task.h>

#include "sdkconfig.h"

static char LOG_TAG[] = "SampleServer";

class MySecurity : public BLESecurityCallbacks {

    uint32_t onPassKeyRequest(){
        ESP_LOGI(LOG_TAG, "PassKeyRequest");
        return 123456;
    }

    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(unsigned int){
        ESP_LOGI(LOG_TAG, "On Confrimed Pin Request");
        return true;
    }

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

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("Watch");
        BLEServer* pServer = BLEDevice::createServer();
        BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
        BLEDevice::setSecurityCallbacks(new MySecurity());

        // Advertising parameters:
        // Soliciting ANCS
        BLEAdvertising *pAdvertising = pServer->getAdvertising();
        BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
        oAdvertisementData.setFlags(0x01);
        oAdvertisementData.setServiceSolicitation(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);
    }
};

void SampleSecureServer(void)
{
    //esp_log_level_set("*", ESP_LOG_DEBUG);
    MainBLEServer* pMainBleServer = new MainBLEServer();
    pMainBleServer->setStackSize(20000);
    pMainBleServer->start();
} // app_main

void setup()
{
    SampleSecureServer();

}
void loop()
{
    delay(100);
}

Thanks a ton!

m-hertig commented 5 years ago

Great project! Thanks alot for the efforts and for sharing @S-March ! I'm trying to compile the code from the repository for a lolin d32 but get the following error message:

/Users/mh/Documents/Arduino/libraries/ANCS/BLEServer.cpp:21:22: fatal error: gatt_api.h: No such file or directory

I copied the files from the Library Folder from your repo to a subfolder in the Arduino libraries folder. Googling "gatt_api.h" points to a file in the espressif repository which does not exist anymore (to here). I have installed vesion 1.0.0 of espressifs esp32 board definitions. Any idea how to resolve this?

chegewara commented 5 years ago

@m-hertig There is old ble library included in project. You can download files from this repository and replace in ANCS project.

m-hertig commented 5 years ago

Thank you for the reply @chegewara ! Well I use the files from the Library folder in the ANCS project but still get that error. Might there be a version conflict with the rest my esp32 library?

chegewara commented 5 years ago

I know what files you are using. The thing is that since then there was change in esp-idf and file name has changed from gatt_api.h to esp_gatt_api.h (i think).

CoretechR commented 5 years ago

The file is now named esp_gatts_api.h but even with that there are still errors. I just tried the latest esp32 core and library versions and nothing works. @S-March , do you have a working ANCS example with the latest software versions?

m-hertig commented 5 years ago

I got it working now using the current version of the ESP32_BLE_Arduino library and then manually doing the modifications that @S-March described in the posts above. Here is the code that works for me: ANCS_ESP32.zip Thanks alot for the good work!

Petros144 commented 5 years ago

first, thanks @m-hertig for the Update! I tried out your Code and the Libs, but it won´t run correctly.

Does someone know whats going on here?

(I can see the ESP within the iOS BT Settings & can connect, but after this I get a reboot, see code)

`ets Jun 8 2016 00:22:57

rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0xee clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0018,len:4 load:0x3fff001c,len:952 load:0x40078000,len:6084 load:0x40080000,len:7936 entry 0x40080310


Device connected 42:ac:a7:e0:bc:1a


CORRUPT HEAP: Bad head at 0x3fff5400. Expected 0xabba1234 got 0x3fff5408 E (102133) boot: Assert failed in multi_heap_free, /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/multi_heap_poisoning.c:205 (head != NULL) Guru Meditation Error: Core 0 panic'ed (Interrupt wdt timeout on CPU0) Core 0 register dump: PC : 0x400d94cd PS : 0x00060334 A0 : 0x80090884 A1 : 0x3ffdcce0
A2 : 0x3ffc334c A3 : 0x000000cd A4 : 0x3ffc3410 A5 : 0x3ffc333c
A6 : 0x00060f23 A7 : 0x00000000 A8 : 0x800d94cd A9 : 0x3ffdcc90
A10 : 0x40158110 A11 : 0x3ffc7f58 A12 : 0x3f401dd8 A13 : 0x00000000
A14 : 0x00000001 A15 : 0x00000001 SAR : 0x00000004 EXCCAUSE: 0x00000005
EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xfffffffc

Backtrace: 0x400d94cd:0x3ffdcce0 0x40090881:0x3ffdcd10 0x40084b3e:0x3ffdcd30 0x4008a3f9:0x3ffdcd50 0x4000bec7:0x3ffdcd70 0x4016f3d1:0x3ffdcd90 0x40172a5a:0x3ffdcdb0 0x400d6b72:0x3ffdcdd0 0x400d4304:0x3ffdce10 0x400d4c71:0x3ffdce50 0x400d373d:0x3ffdce70 0x400d4131:0x3ffdcf20 0x400e1894:0x3ffdcf70 0x400e1bb6:0x3ffdcfb0 0x400ddf41:0x3ffdcfd0

Core 1 register dump: PC : 0x4008db3d PS : 0x00060034 A0 : 0x800901be A1 : 0x3fff2ed0
A2 : 0x3ffaffb4 A3 : 0x0000abab A4 : 0xb33fffff A5 : 0x00000001
A6 : 0x00060020 A7 : 0x0000cdcd A8 : 0x0000cdcd A9 : 0x3fff2ed0
A10 : 0x00000003 A11 : 0x00060023 A12 : 0x00060020 A13 : 0x3fff3174
A14 : 0x3fff3138 A15 : 0x3f41346c SAR : 0x00000010 EXCCAUSE: 0x00000005
EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xfffffffe

Backtrace: 0x4008db3d:0x3fff2ed0 0x400901bb:0x3fff2f00 0x40090833:0x3fff2f20 0x40084a44:0x3fff2f40 0x40084a75:0x3fff2f60 0x4008a3ed:0x3fff2f80 0x4000beaf:0x3fff2fa0 0x4016f40f:0x3fff2fc0 0x40172a49:0x3fff2fe0 0x40172c97:0x3fff3000 0x4017053e:0x3fff3030 0x401707ce:0x3fff3070 0x401a05d9:0x3fff3090 0x40170abd:0x3fff30b0 0x40170b5d:0x3fff30d0 0x40170c25:0x3fff3100 0x400d647f:0x3fff3120 0x400d433b:0x3fff3220 0x400d179d:0x3fff3280 0x400d6c86:0x3fff32c0

Rebooting... ets Jun 8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0xee clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0018,len:4 load:0x3fff001c,len:952 load:0x40078000,len:6084 load:0x40080000,len:7936 entry 0x40080310 `

bin2ai commented 5 years ago

First Thank you so much for this ANCS code @S-March. I am having difficulties getting a notification's "appID" using the control point characteristic.
example. "com.apple.facetime"

This is my current understanding of how I can obtain that information.

Once a notification has be received I get the notificationUID and pass it into the control point characteristic's write value function. Based off of the ANCS documentation site, the format should be [commandID-notificationUID-attributeID].

From this threads ANCS code, I declared the pControlPointCharatersitic globally & statically (instead of inside of the myClient class) but when we use the writevalue() function it hangs. Is there another approach to obtain ALL OF the notification's information?

example of how I am currently trying to implement it.

  1. new notification received
  2. enters function "NotificationSourceNotifyCallback"
  3. scrapes uint8_t *pData for the notificationUID (pData's last 4 bytes)
  4. requests more data from control point by sending [1-notificationUID-0] to (NOW Static) pControlPointCharacterstic object function, "writevalue".
  5. hangs, and doesn't exit writevalue function

[D][BLERemoteCharacteristic.cpp:554] writeValue(): >> writeValue(), length: 11 [D][FreeRTOS.cpp:164] take(): Semaphore taking: name: WriteCharEvt (0x3fff5a24), owner: <N/A> for writeValue [D][FreeRTOS.cpp:173] take(): Semaphore taken: name: WriteCharEvt (0x3fff5a24), owner: writeValue

Petros144 commented 5 years ago

@tccox3 I have a working exaple of showing notification with App ID, i can upload this for you.

But can someone tell me how can I put the Nofification etc. into a variable that can be printed out on a TFT etc? String or Char Array?

The default is a byte Stream that gets print out with the Serial.Write function...

jhud commented 5 years ago

I actually got the ANCS code running on my ESP32 smartwatch today. It’s working great, and I’m getting notifications no problem. Thanks for the comments posted here! You can find it in my public repo, but it all needs a big refactor when I have time.

Petros144 commented 5 years ago

@jhud Thank you for Posting your good work!

I have still problems with reconnecting to the device when its reset. are there any ways to make a reconnect possible ( iPhone turn On/Off BT Or ESP Lost Power ).