sandeepmistry / arduino-BLEPeripheral

An Arduino library for creating custom BLE peripherals with Nordic Semiconductor's nRF8001 or nR51822.
MIT License
464 stars 180 forks source link

Trying to write to ANCS Control Point #142

Closed jacobmh1177 closed 7 years ago

jacobmh1177 commented 7 years ago

Hi everyone, I'm trying to extend the ANCS example to get notification information (app info, message content, etc.). After reading Apple's documentation it looks like you have to send the ANCS Control Point a specially formatted command buffer to ask for notification information, and if the command buffer is correctly formatted then ANCS will respond using the Data Source characteristic. When I write to the control point, ancsControlPointCharacteristic.write(buffer, buffer length) returns true, but I'm not getting any response on the Data Source characteristic. Any advice on where I'm going wrong? My code for this is pasted below:


#include <BLEPeripheral.h>
#include <BLEUtil.h>
#include "pack_lib.h"
#include "utilities.h"
#include "linked_list.h"

//custom boards may override default pin definitions with BLEPeripheral(PIN_REQ, PIN_RDY, PIN_RST)
BLEPeripheral                    blePeripheral                            = BLEPeripheral();
BLEBondStore                     bleBondStore;

// remote services
BLERemoteService                 ancsService                              = BLERemoteService("7905f431b5ce4e99a40f4b1e122d00d0");

// remote characteristics
BLERemoteCharacteristic          ancsDataSourceCharacteristic             = BLERemoteCharacteristic("22eac6e924d64bb5be44b36ace7c7bfb", BLENotify);
BLERemoteCharacteristic          ancsNotificationSourceCharacteristic     = BLERemoteCharacteristic("9fbf120d630142d98c5825e699a21dbd", BLENotify);
BLERemoteCharacteristic          ancsControlPointCharacteristic           = BLERemoteCharacteristic("69d1d8f345e149a898219bbdfdaad9d9", BLEWrite|BLENotify);

byte ANCS_COMMAND_GET_NOTIF_ATTRIBUTES = 0;
byte ANCS_NOTIFICATION_ATTRIBUTE_TITLE = 1;
byte NotificationAttributeIDSubtitle = 2;
byte ANCS_NOTIFICATION_ATTRIBUTE_MESSAGE = 3;
byte ANCS_NOTIFICATION_ATTRIBUTE_MESSAGE_SIZE = 4;
byte ANCS_NOTIFICATION_ATTRIBUTE_DATE = 5;
byte ANCS_NOTIFICATION_ATTRIBUTE_DATA_SIZE = 16;
long MESSAGE_SIZE = 100;
byte ANCS_NOTIFICATION_ATTRIBUTE_APP_IDENTIFIER = 0;
boolean command_send_enable = true;
unsigned long last_command_send = 0;

static linked_list_t* buffer_commands;
static bool ancs_updated = true;
void setup() {
  Serial.begin(9600);
#if defined (__AVR_ATmega32U4__)
  while(!Serial);
#endif

  // clears bond data on every boot
  bleBondStore.clearData();

  blePeripheral.setBondStore(bleBondStore);

  blePeripheral.setServiceSolicitationUuid(ancsService.uuid());
  blePeripheral.setLocalName("ANCS");

  // set device name and appearance
  blePeripheral.setDeviceName("Smartwatch");
  blePeripheral.setAppearance(0x0080);

  blePeripheral.addRemoteAttribute(ancsService);
  blePeripheral.addRemoteAttribute(ancsDataSourceCharacteristic);
  blePeripheral.addRemoteAttribute(ancsNotificationSourceCharacteristic);
  blePeripheral.addRemoteAttribute(ancsControlPointCharacteristic);

  // assign event handlers for connected, disconnected to peripheral
  blePeripheral.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  blePeripheral.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);
  blePeripheral.setEventHandler(BLEBonded, blePeripheralBondedHandler);
  blePeripheral.setEventHandler(BLERemoteServicesDiscovered, blePeripheralRemoteServicesDiscoveredHandler);

  // assign event handlers for characteristic
  ancsDataSourceCharacteristic.setEventHandler(BLEValueUpdated, ancsDataSourceCharacteristicValueUpdated);
  ancsNotificationSourceCharacteristic.setEventHandler(BLEValueUpdated, ancsNotificationSourceCharacteristicValueUpdated);

  // begin initialization
  blePeripheral.begin();

  Serial.println(F("BLE Peripheral - ANCS"));
  //buffer_commands = fifo_create();
}

void loop() {
  blePeripheral.poll();
}

void blePeripheralConnectHandler(BLECentral& central) {
  // central connected event handler
  Serial.print(F("Connected event, central: "));
  Serial.println(central.address());
}

void blePeripheralDisconnectHandler(BLECentral& central) {
  // central disconnected event handler
  Serial.print(F("Disconnected event, central: "));
  Serial.println(central.address());
}

void blePeripheralBondedHandler(BLECentral& central) {
  // central bonded event handler
  Serial.print(F("Remote bonded event, central: "));
  Serial.println(central.address());

  if (ancsNotificationSourceCharacteristic.canSubscribe()) {
    ancsNotificationSourceCharacteristic.subscribe();
  } 
  if (ancsDataSourceCharacteristic.canSubscribe()) {
    ancsDataSourceCharacteristic.subscribe();
  } 
}

void blePeripheralRemoteServicesDiscoveredHandler(BLECentral& central) {
  // central remote services discovered event handler
  Serial.print(F("Remote services discovered event, central: "));
  Serial.println(central.address());

  if (ancsNotificationSourceCharacteristic.canSubscribe()) {
    if (ancsNotificationSourceCharacteristic.subscribe()) Serial.println("Subscribed to Notification Source");
  }
  if (ancsDataSourceCharacteristic.canSubscribe()) {
    if (ancsDataSourceCharacteristic.subscribe()) Serial.println("Subscribed to Data Source");
  } 
  if (ancsControlPointCharacteristic.canWrite()) {
    Serial.println("I can write to Control Point");
  } 
}

enum AncsNotificationEventId {
  AncsNotificationEventIdAdded    = 0,
  AncsNotificationEventIdModified = 1,
  AncsNotificationEventIdRemoved  = 2
};

enum AncsNotificationEventFlags {
  AncsNotificationEventFlagsSilent         = 1,
  AncsNotificationEventFlagsImportant      = 2,
  AncsNotificationEventFlagsPositiveAction = 4,
  AncsNotificationEventFlagsNegativeAction = 8
};

enum AncsNotificationCategoryId {
  AncsNotificationCategoryIdOther              = 0,
  AncsNotificationCategoryIdIncomingCall       = 1,
  AncsNotificationCategoryIdMissedCall         = 2,
  AncsNotificationCategoryIdVoicemail          = 3,
  AncsNotificationCategoryIdSocial             = 4,
  AncsNotificationCategoryIdSchedule           = 5,
  AncsNotificationCategoryIdEmail              = 6,
  AncsNotificationCategoryIdNews               = 7,
  AncsNotificationCategoryIdHealthAndFitness   = 8,
  AncsNotificationCategoryIdBusinessAndFinance = 9,
  AncsNotificationCategoryIdLocation           = 10,
  AncsNotificationCategoryIdEntertainment      = 11
};

struct AncsNotification {
  unsigned char eventId;
  unsigned char eventFlags;
  unsigned char catergoryId;
  unsigned char catergoryCount;
  unsigned long notificationUid;
};

void ancsNotificationSourceCharacteristicValueUpdated(BLECentral& central, BLERemoteCharacteristic& characteristic) {
  Serial.println(F("ANCS Notification Source Value Updated:"));
  struct AncsNotification notification;

  memcpy(&notification, characteristic.value(), sizeof(notification));

  Serial.print("\tEvent ID: ");
  Serial.println(notification.eventId);
  Serial.print("\tEvent Flags: 0x");
  Serial.println(notification.eventFlags, HEX);
  Serial.print("\tCategory ID: ");
  Serial.println(notification.catergoryId);
  Serial.print("\tCategory Count: ");
  Serial.println(notification.catergoryCount);
  Serial.print("\tNotification UID: ");
  Serial.println(notification.notificationUid);
  BLEUtil::printBuffer(characteristic.value(), characteristic.valueLength());
  byte buffer[8];
  long uid = notification.notificationUid;
  int m_size = (int)MESSAGE_SIZE;
  buffer[0] = 0x00;//ANCS_COMMAND_GET_NOTIF_ATTRIBUTES;
  buffer[1] = (uid >> 24) & 0xFF;
  buffer[2] = (uid >> 16) & 0xFF;
  buffer[3] = (uid >> 8) & 0xFF;
  buffer[4] = uid & 0xFF;
  buffer[5] = 0x00; //ANCS_NOTIFICATION_ATTRIBUTE_MESSAGE;
  Serial.println("Sending this buffer over!: ");
  BLEUtil::printBuffer(buffer, 6);
  Serial.println();
  Serial.println(ancsControlPointCharacteristic.write((const unsigned char*)buffer, 9)); 
}

void ancsDataSourceCharacteristicValueUpdated(BLECentral& central, BLERemoteCharacteristic& characteristic) {
  Serial.print(F("ANCS Data Source Value Updated: "));
  BLEUtil::printBuffer(characteristic.value(), characteristic.valueLength());
}```
jacobmh1177 commented 7 years ago

Update I changed the following code to make it little-endian but it still doesn't work:

  byte buffer[8];
  long uid = notification.notificationUid;
  int m_size = (int)MESSAGE_SIZE;
  buffer[0] = 0x00;//ANCS_COMMAND_GET_NOTIF_ATTRIBUTES;
  buffer[4] = (uid >> 24) & 0xFF;
  buffer[3] = (uid >> 16) & 0xFF;
  buffer[2] = (uid >> 8) & 0xFF;
  buffer[1] = uid & 0xFF;
  buffer[5] = 0x00; //ANCS_NOTIFICATION_ATTRIBUTE_MESSAGE;
sandeepmistry commented 7 years ago

Take a look at: https://github.com/sandeepmistry/node-ancs/blob/master/lib/ancs.js#L108-L119

jacobmh1177 commented 7 years ago

Thanks for the quick response! I'm pretty new to working with BLE, but I couldn't find buffer.writeUint8/32 in the arduino library. Could you help me understand how to apply the node code to arduino?

claudioarduino commented 7 years ago

@jacobmh1177 Hi jacob, did you solve the problem with the ANCS Control Point? I'm interested to write to ANCS Control Point too. Any hint on how to do that? Thanks

jacobmh1177 commented 7 years ago

@claudioarduino I haven't gotten it to work yet and I'm stuck with the code I have above. I think it should theoretically work. I've also checked to make sure I'm subscribed to the ANCS data source characteristic. Maybe it has to do with the order I subscribe to characteristics? I'm hoping @sandeepmistry can give me some more insight into this bug

sandeepmistry commented 7 years ago

I'm hoping @sandeepmistry can give me some more insight into this bug

I don't think it's a bug in the library ...

Serial.println(ancsControlPointCharacteristic.write((const unsigned char*)buffer, 9));

Why is the 9 here? The buffer size should be either 6 or 8.

Closing this for now, we can always re-open if this was not the problem.

jacobmh1177 commented 7 years ago

Yea I noticed this when I was going back through my code and changed that to an 6 or 8 and it still didn't work.

claudioarduino commented 7 years ago

@jacobmh1177 Hi jacob, how do you update the values on the ANCS Data Source, after you send the command to the ANCS Control Point? Did you solve the problem?

jacobmh1177 commented 7 years ago

@claudioarduino I have not solved the issue of using ANCS using this library but would love to collaborate. The way ANCS works is that you subscribe to the Notification Source characteristic that is updated whenever your apple device receives a notification. The Notification Source gives you meta data about the notification, but the most important thing is the Notification UID. If you want to see more information about the notification (i.e. The body of the text message) you need to send back a specifically formatted command buffer to the Control Point characteristic. If the buffer is correctly formatted, the requested information will be written to the Data Source. https://developer.apple.com/library/content/documentation/CoreBluetooth/Reference/AppleNotificationCenterServiceSpecification/Specification/Specification.html

claudioarduino commented 7 years ago

@jacobmh1177 Hi jacob, I tried to send CommandIDPerformNotificationAction through the Control point, in this case it works, instead when I try to send ANCS_COMMAND_ID_GET_NOTIF_ATTRIBUTES the ancsControlPointCharacteristic.write(buffer, buffer length) returns true, but I have not response from the IOS device. It seems that there's something wrong on the ANCS_COMMAND_ID_GET_NOTIF_ATTRIBUTES or something must be enabled on the IOS device? Thanks for your help.

jacobmh1177 commented 7 years ago

@claudioarduino that's awesome! Could you send me the code you got working for CommandIDPerformNotificationAction? I'll look into configuring the iPhone

claudioarduino commented 7 years ago

@jacobmh1177 Hi jacob, the code for notification action is:

/ PERFORM_NOTIFICATION_ACTION / byte buffer[6]; buffer[0] = ANCS_COMMAND_ID_PERFORM_NOTIF_ACTION; //(0x02) buffer[4] = (uid >> 24) & 0xFF; buffer[3] = (uid >> 16) & 0xFF; buffer[2] = (uid >> 8) & 0xFF; buffer[1] = uid & 0xFF; buffer[5] = ANCS_ACTION_ID_POSITIVE; //(0x00)

Serial.println("Perform action buffer "); BLEUtil::printBuffer(buffer, 6); Serial.println(); Serial.println(ancsControlPointCharacteristic.write((const unsigned char*)buffer, 6));

This is the buffer I used to perform a notification action. This code works, when i try to call my Iphone it answers, but i'm not still able to see the notification attributes. I tried with your code for the notification attribute, the control point is ok but the data source doesn't works. The cose id:

/ GET_NOTIFICATION_ATTRIBUTE / byte buffer[8]; buffer[0] = ANCS_COMMAND_ID_GET_NOTIF_ATTRIBUTES; buffer[4] = (uid >> 24) & 0xFF; buffer[3] = (uid >> 16) & 0xFF; buffer[2] = (uid >> 8) & 0xFF; buffer[1] = uid & 0xFF; buffer[5] = ANCS_NOTIFICATION_ATTRIBUTE_MESSAGE; buffer[6] = 20; //ANCS_NOTIFICATION_ATTRIBUTE_MESSAGE; buffer[7] = 0; //ANCS_NOTIFICATION_ATTRIBUTE_MESSAGE; BLEUtil::printBuffer(buffer, 8); Serial.println("Sending notification attribute buffer "); Serial.println(); Serial.println(ancsControlPointCharacteristic.write((const unsigned char*)buffer, 8));

Let me know if you make any progress. Thanks

claudioarduino commented 7 years ago

@jacobmh1177 Hi jacob, I found what's the problem. You have to add

if (ancsDataSourceCharacteristic.canSubscribe()) { ancsDataSourceCharacteristic.subscribe(); Serial.println("Subscribed to Data Source"); } before to define the buffer structure, then put the buffer structure for the command and then Serial.println(ancsControlPointCharacteristic.write((const unsigned char)buffer, 8)); ` , it works.

Now I'm working on the Alert Notification Service to make a sort of ANCS for Android. Any idea on how it works? Thanks

M3sca commented 3 years ago

@jacobmh1177 Hi jacob, I found what's the problem. You have to add

if (ancsDataSourceCharacteristic.canSubscribe()) { ancsDataSourceCharacteristic.subscribe(); Serial.println("Subscribed to Data Source"); } before to define the buffer structure, then put the buffer structure for the command and then Serial.println(ancsControlPointCharacteristic.write((const unsigned char)buffer, 8)); ` , it works.

Now I'm working on the Alert Notification Service to make a sort of ANCS for Android. Any idea on how it works? Thanks

I have the same issue. Where is the problem?

void ancsNotificationSourceCharacteristicValueUpdated(BLECentral& central, BLERemoteCharacteristic& characteristic) {

struct AncsNotification notification;

memcpy(&notification, characteristic.value(), sizeof(notification));

if (ancsDataSourceCharacteristic.canSubscribe()) { ancsDataSourceCharacteristic.subscribe();
}

byte buffer[8]; unsigned long uid = notification.notificationUid;
buffer[0] = 0; buffer[4] = (uid >> 24) & 0xFF; buffer[3] = (uid >> 16) & 0xFF; buffer[2] = (uid >> 8) & 0xFF; buffer[1] = uid & 0xFF; buffer[5] = 1; buffer[6] = 19; buffer[7] = 0;

ancsControlPointCharacteristic.write((const unsigned char*)buffer, 8);
}