28

Goal

I am developping a simple device running Linux. It is BLE capable, and I am currently using bluez 5.8.

I want to trigger an action on this device using an iPhone.

What already works:

  • I can make the iPhone "see" the device.
  • The iPhone also connects to the device.

I setup the bluetooth device like this on linux (thanks to this question):

# activate bluetooth
hciconfig hci0 up                                             
# set advertise data: "hello world"
hcitool -i hci0 cmd 0x08 0x0008 48 45 4c 4c 4f 57 4f 52 4c 44
# start advertising as connectable
hciconfig hci0 leadv 0

The iOS code is straightforward:

- (int) scanForPeripherals
{
    if (self->centralManager.state != CBCentralManagerStatePoweredOn) {
        return -1;
    }
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
    [self.centralManager scanForPeripheralsWithServices:nil options:options];
    return 0;
}

- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    if (central.state == CBCentralManagerStatePoweredOn) {
        NSLog(@"Starting scan");
        [self scanForPeripherals];
    }
}

- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    NSLog(@"didDiscoverPeripheral");
    /* 
     * Retain the peripheral to avoid the error:
     *  CoreBluetooth[WARNING]: state = connecting> is being dealloc'ed while connecting
     */
    self.activePeripheral = peripheral;
    [centralManager connectPeripheral:peripheral options:nil];
}

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"Connected to peripheral");

    /* discover all services */
    [peripheral discoverServices:nil];
}

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    NSLog(@"Discovered services");
    for (CBService *service in peripheral.services) {
        NSLog(@"Discovered service %@", service);
    }
}

When running this code on the iPhone, I get this log:

2013-12-19 12:53:22.609 Test2[18518:60b] Starting scan
2013-12-19 12:53:29.945 Test2[18518:60b] didDiscoverPeripheral
2013-12-19 12:53:31.230 Test2[18518:60b] Connected to peripheral

So it seems that the iPhone connects fine, but does not see any service.

What I am missing

  • I need to advertise a simple BLE service, but I can't find any documentation on how to do this in bluez .
  • I think I need something like a gatt-server to receive read/write characteristics for the service I would advertise. I saw the plugins/gatt-example.c file in bluez, but I have absolutely no idea how to use it: there is no documentation.

I should probably mention that I saw this question: Creating a gatt server, but the answers raise too much questions (for example, where is the GATT api for bluez? how to set the GATT database? How to register for read/write events?)

EDIT: The commands I use only set-up the BLE device to advertise some data, but iOS reports that the connection is accepted. What part of bluez is accepting incoming connections?

Community
  • 1
  • 1
Gilles Gregoire
  • 1,636
  • 1
  • 12
  • 13
  • you're right, you need a GATT server. The commands you ran just set up the hardware to broadcast an advertising packet, but doesn't start anything up for things to connect to. Unfortunately I've yet to figure out how to set up a GATT server myself so I can't help you there... – Tim Tisdall Dec 20 '13 at 14:05
  • @TimTisdall I know that these command don't set up a server. And this raises the question: who is accepting the bluetooth connection? I edited my post to make this question clear. – Gilles Gregoire Dec 21 '13 at 07:42
  • My guess is there's something in the kernel making the connection, but it's only a guess. – Tim Tisdall Dec 21 '13 at 22:52

2 Answers2

18

Eventually, I discovered the answers to all the questions I had.

I will start by answering the last question:

The commands I use only set-up the BLE device to advertise some data, but iOS reports that the connection is accepted. What part of bluez is accepting incoming connections?

This one was answered on the bluez mailing-list, in response to me.

Summary: the BLE connection is accepted at the HCI level by the kernel. If you want to use that connection from user space you need to use an l2cap socket with the ATT channel ID (which is 4).

Bleno has a good example of using an L2CAP socket.

How an L2CAP socket works is basically like this:

/* create L2CAP socket, and bind it to the local adapter */
l2cap_socket = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);

hci_device_id = hci_get_route(NULL);
hci_socket = hci_open_dev(hci_device_id);
memset(&l2cap_address, sizeof(l2cap_address));
l2cap_address.l2_family = AF_BLUETOOTH;
l2cap_address.l2_bdaddr = hci_device_address;
l2cap_address.l2_cid = htobs(ATT_CID);

bind(l2cap_socket, (struct sockaddr*)&l2cap_address, sizeof(l2cap_address));
listen(l2cap_socket, 1);

while (1) {
  /* now select and accept() client connections. */
  select(l2cap_socket + 1, &afds, NULL, NULL, &tv);
  client_socket = accept(l2cap_socket, (struct sockaddr *)&l2cap_address, &len);

  /* you can now read() what the client sends you */
  int ret = read(client_socket, buffer, sizeof(buffer));
  printf("data len: %d\n", ret);
  for (i = 0; i < ret; i++) {
    printf("%02x", ((int)buffer[i]) & 0xff);
  }
  printf("\n");
  close(client_socket);
}

How to advertise a service?

I realized I needed an answer to the previous question to answer that one.

Once you can read the data over L2CAP socket, everything makes more sense, for example, if your Android phone does gatt.discoverServices(), then the little program above will read (i.e. receive):

10 0100 ffff 0028

Which basically means:

10: READ_BY_GROUP
0100: from handle 0001
ffff: to handle ffff
0028: with UUID 2800

This request is the way any BLE peripheral will request the list of services.

Then, you can answer this request with the list of services your device provides, formatted according to the GATT protocol.

Again, see the implementation of this in Bleno.

Chnossos
  • 8,332
  • 2
  • 22
  • 34
Gilles Gregoire
  • 1,636
  • 1
  • 12
  • 13
  • Did you ever manage to get your GATT services working with Bluez? I'm trying to accomplish the same. – Michael May 07 '17 at 06:18
4

You're really close.

To see the services on a device using your iOS code, try adding

peripheral.delegate = self;

to your didConnectPeripheral, before the discoverServices call. I got that from the Apple documentation and it fixed things for me (just don't forget to add CBPeripheralDelegate to the interface declaration in the header file). Without it, didDiscoverServices will never be called.

I was able to get the gatt-example service plugin to run by compiling BlueZ from source with the ./configure --enable-maintainer-mode. Then if you launch bluetoothd -nd you'll see something like

src/plugin.c:add_plugin() Loading gatt_example plugin

near the top of the output, and then

attrib/gatt-service.c:gatt_service_add() New service: handle 0x0009, UUID a002, 4 attributes
src/attrib-server.c:attrib_db_add_new() handle=0x0009
attrib/gatt-service.c:gatt_service_add() New characteristic: handle 0x000a
src/attrib-server.c:attrib_db_add_new() handle=0x000a

At that point, my iOS app was able to see the BlueZ peripheral, connect, and discover its services (after a hciconfig hci0 leadv).

Gabriel
  • 1,423
  • 14
  • 23
  • thanks for sharing. It appears that I did _not_ compile bluez using `--enable-maintainer-mode`, _but_ the connection is accepted. After (too much) reading the bluez code, I think the connection is accepted by the service discovery part of bluez... (see src/sdpd-server.c). – Gilles Gregoire Jan 21 '14 at 08:46
  • Yes I saw the same behavior before enabling the `gatt-example` plugin. This is because connections are accepted first, then service discovery occurs. Without the `gatt-example` plugin included in `bluetoothd`, your iPhone will still see your advertising packets, and be able to connect, but won't find any services. `--enable-maintainer-mode` is a shortcut to including the `gatt-example` plugin in the build without modifying `Makefile.plugins`. – Gabriel Jan 22 '14 at 13:45
  • I have two quick, related questions: a) where can I configure the Services and Attributes in bluez? b) in case an attribute is writeable, where does bluez "store" this informaiton (or is ther some kind of Callback) in order to do something with this value written by the ios app. – geri-m Feb 04 '14 at 18:36
  • A Reboot fixed (a). Regarding (b). I just had a look at the attrib-server. is this the place where bluez stores to updates? should a third party app work with this data-structure? – geri-m Feb 04 '14 at 21:17
  • @geri-m If I'm understanding your question correctly, I believe that you'll find your answer by looking at the source code of `gatt-example.` Everything is callbacks. – Gabriel Feb 05 '14 at 20:15
  • Hi guys - I am facing exactly the same issue where iOS can find and connect to my BlueZ 4.101 bluetooth instance with the GATT Plugins enabled, but cannot see any services at all. I've detailed everything I've done in my post here: https://stackoverflow.com/questions/46284902/linux-bluez-4-101-allows-gatt-connection-but-does-not-expose-gatt-services-on-ub. I would really appreciate any insight as to what I did wrong or what I could try. Thank you. – PhilBot Sep 19 '17 at 00:49