don / cordova-plugin-ble-central

Bluetooth Low Energy (BLE) Central plugin for Apache Cordova (aka PhoneGap)
Apache License 2.0
942 stars 603 forks source link

how to read characteristic value #668

Closed nickcom closed 4 years ago

nickcom commented 5 years ago

I see quite a few questions posted about this, but no clear answer for me.

I am running the Battery sample from ble-central on one iPhone (iPhoneC), and the Flashlight sample for ble-peripheral on another iPhone (iPhoneP).

I can locate iPhoneP and connect to it from iPhoneC. I can read the battery level fine.

But when I change the serviceID and characteristicID on iPhoneC to the Flashlight values, the read response back is empty.

Note: If I do a 'write' from iPhoneC to the Flashlight service I register a response on iPhoneP as expected.

iPhoneC (ble-central):

ble.read(deviceId, serviceId, characteristicId, onSuccess, onFailure);

iPhoneP (ble-peripheral):

var flashlightService = {
uuid: SERVICE_UUID,
characteristics: [
                  {
                  uuid: SWITCH_UUID,
                  properties: property.WRITE | property.READ,
                  permissions: permission.WRITEABLE | permission.READABLE,
                  descriptors: [
                                {
                                uuid: '2901',
                                value: 'Switch'
                                }
                                ]
                  },
                  {
                  uuid: DIMMER_UUID,
                  properties: property.WRITE | property.READ,
                  permissions: permission.WRITEABLE | permission.READABLE,
                  descriptors: [
                                {
                                uuid: '2901',
                                value: 'Dimmer'
                                }
                                ]
                  }
                  ]
};

Console from ble-peripheral:

2019-03-06 23:27:24.707992+0800 helloworld[7575:2886399] Received read request for <CBMutableCharacteristic: 0x2817a2bc0 UUID = FF11, Value = (null), Properties = 0xA, Permissions = 0x3, Descriptors = (
"<CBMutableDescriptor: 0x282922480 UUID = Characteristic User Description, Value = Switch>"
), SubscribedCentrals = (
)>
2019-03-06 23:27:24.708354+0800 helloworld[7575:2886399] Looking for FF11

Console from ble-central (nothing):

2019-03-06 23:27:24.726775+0800 helloworld[478:37299] {}

I assume this might have something to do with the format of the reply?

droghio commented 5 years ago

Do you have another ble device to test with, or another phone? The first step is probably to isolate whether the problem is in the peripheral or central.

nickcom commented 5 years ago

thanks for your reply. I will try to source a different phone to test with.

In the mean time, for the characteristic defined above, what reply back would I expect to receive from the ble.read request?

ble.read(deviceId, SERVICE_UUID, SWITCH_UUID, onSuccess, onFailure);

Would I expect to receive simply 'Switch', or an array of everything contained in the characteristics declaration?

droghio commented 5 years ago

You should get an array buffer corresponding to that one characteristic that you can parse any way you want. Make sure you are using the correct service and switch uuids, both will need to change.

https://github.com/don/cordova-plugin-ble-central#read

nickcom commented 5 years ago

so I only get back an empty object. I must be missing something.

Im my example above, I want to read the switch characteristic. I'm expecting to receive the value of 'switch', but all I receive back is an empty object. Am I meant to define the contents I want to read elsewhere?

ble.read(deviceId, SERVICE_UUID, SWITCH_UUID, onSuccess, onFailure);

Result is an empty object. If changing to a write request I receive the data written on the peripheral device as expected. The issue is getting the central device to read from the peripheral.

If relevant, the ble.scan method returns the following (I notice the value = null as well)

helloworld[604:182229] Characteristic <CBCharacteristic: 0x281424b40, UUID = SWITCH_UUID, properties = 0x12, value = (null), notifying = NO>
droghio commented 5 years ago

Can you include the uuids in their full form, as well are your callback functions?

nickcom commented 5 years ago

Sure. Really appreciate your help here. I'm using the same code for the battery level for ble-central from here, and the flashlight sample from peripheral from here with minimal alterations.

The flashlight peripheral app uses a service UUID=FF10 and a switch characteristic UUID of FF11, so I have changed the battery sample to use those UUID's. I receive back an empty data object rather than the value of the switch characteristic I am wanting.

The purpose of my code is to work out how to read a value from the peripheral device from the central device. The write from central to peripheral works as expected.

Full js code for both the peripheral and the central devices is Central (changes from the stock example are indicated)

'use strict';

var app = {
initialize: function() {
    this.bindEvents();
    detailPage.hidden = true;
},
bindEvents: function() {
    document.addEventListener('deviceready', this.onDeviceReady, false);
    refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
    batteryStateButton.addEventListener('touchstart', this.readCharacteristic, false); //**new
    disconnectButton.addEventListener('touchstart', this.disconnect, false);
    deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
},
onDeviceReady: function() {
    app.refreshDeviceList();
},
refreshDeviceList: function() {
    deviceList.innerHTML = ''; // empties the list
    // scan for all devices
    ble.scan(['FF10'], 5, app.onDiscoverDevice, app.onError); //**new
},
onDiscoverDevice: function(device) {

    console.log(JSON.stringify(device));
    var listItem = document.createElement('li'),
    html = '<b>' + device.name + '</b><br/>' +
    'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
    device.id;

    listItem.dataset.deviceId = device.id;  // TODO
    listItem.innerHTML = html;
    deviceList.appendChild(listItem);

},
connect: function(e) {
    var deviceId = e.target.dataset.deviceId,

    onConnect = function() {
        batteryStateButton.dataset.deviceId = deviceId;
        disconnectButton.dataset.deviceId = deviceId;
        app.showDetailPage();
        app.readCharacteristic(); //**new
    };

    ble.connect(deviceId, onConnect, app.onError);
},
readCharacteristic: function(event) {
   console.log("readCharacteristic");
    var deviceId = event.target.dataset.deviceId;
    ble.read(deviceId, 'FF10', 'FF11', app.onReadCharacteristsic, app.onError); //**new
},
onReadCharacteristsic: function(data) {
    console.log(data);

    //find length of data result //**new
    Object.size = function(obj) {
        var size = 0, key;
        for (key in obj) {
           if (obj.hasOwnProperty(key)) size++;
        }
        return size;
    };

    // Get the size of an object
    var size = Object.size(data);
    console.log(size);
    ////////////////////////////

    var a = new Uint8Array(data);
    batteryState.innerHTML = a[0];
},
disconnect: function(event) {
    var deviceId = event.target.dataset.deviceId;
    ble.disconnect(deviceId, app.showMainPage, app.onError);
},
showMainPage: function() {
    mainPage.hidden = false;
    detailPage.hidden = true;
},
showDetailPage: function() {
    mainPage.hidden = true;
    detailPage.hidden = false;
},
onError: function(reason) {
    alert("ERROR: " + reason); // real apps should use notification.alert
}
};

Peripheral (no changes from sample example)

   // Smartbotic Service
var SERVICE_UUID = 'FF10';
var SWITCH_UUID = 'FF11';
var DIMMER_UUID = 'FF12';

var app = {
initialize: function() {
    this.bindEvents();
},
bindEvents: function() {
    document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function() {

    blePeripheral.onWriteRequest(app.didReceiveWriteRequest);
    blePeripheral.onBluetoothStateChange(app.onBluetoothStateChange);

    app.createServiceJSON();
},
createServiceJSON: function() {

    var property = blePeripheral.properties;
    var permission = blePeripheral.permissions;

    var flashlightService = {
    uuid: SERVICE_UUID,
    characteristics: [
                      {
                      uuid: SWITCH_UUID,
                      properties: property.WRITE | property.READ,
                      permissions: permission.WRITEABLE | permission.READABLE,
                      descriptors: [
                                    {
                                    uuid: '2901',
                                    value: 'Switch'
                                    }
                                    ]
                      },
                      {
                      uuid: DIMMER_UUID,
                      properties: property.WRITE | property.READ,
                      permissions: permission.WRITEABLE | permission.READABLE,
                      descriptors: [
                                    {
                                    uuid: '2901',
                                    value: 'Dimmer'
                                    }
                                    ]
                      }
                      ]
    };

    Promise.all([
                 blePeripheral.createServiceFromJSON(flashlightService),
                 blePeripheral.startAdvertising(flashlightService.uuid, 'Flashlight')
                 ]).then(
                         function() { console.log ('Created Flashlight Service'); },
                         app.onError
                         );
},
didReceiveWriteRequest: function(request) {
    console.log(request);

    // Android sends long versions of the UUID
    if (request.characteristic === SWITCH_UUID || request.characteristic === '0000ff11-0000-1000-8000-00805f9b34fb') {
        var data = new Uint8Array(request.value);
        if (data[0] === 0) {
            window.plugins.flashlight.switchOff();
        } else {
            window.plugins.flashlight.switchOn();
        }
    }

    // brightness only works on iOS as of Flashlight 3.2.0
    if (request.characteristic === DIMMER_UUID || request.characteristic === '0000ff12-0000-1000-8000-00805f9b34fb') {
        var data = new Uint8Array(request.value);
        var brightnessByte = data[0];              // 1 byte value 0x00 to 0xFF
        var brightness = brightnessByte / 255.0    // convert to value between 0 and 1.0
        window.plugins.flashlight.switchOn(
                                           function() { console.log('Set brightness to', brightness) },
                                           function() { console.log('Set brightness failed')},
                                           { intensity: brightness }
                                           );
    }
},
onBluetoothStateChange: function(state) {
    console.log('Bluetooth State is', state);
   outputDiv.innerHTML += 'Bluetooth  is ' +  state + '<br/>';
}
};

app.initialize();
nickcom commented 5 years ago

I’m sure I’m overlooking something trivial, but with others encountering similar issues judging by previous issues, could someone post the solution here? Much appreciated!

droghio commented 5 years ago

I don't see anything particularly problematic at first glance, but I haven't used the ble periperhal library. If you have a set of headphones or an actual ble device kicking around I would first try reading from that.

The issue is it is hard to tell if there is a bug in the ble central or ble peripheral libraries or how you configured either. If you have a known good peripheral it'll be easier to isolate where the problem is.

luchomtvn commented 4 years ago

Hello, it seems this issue is a bit old, but i'm having the same problem. I can connect to a peripheral and ble.write to it, but applying the ble.read function returns only an empty object. I tried connecting to the peripheral with my smartphone (iOS) using nrfConnect and i can read the characteristic value from there, so it seems to be a central issue.

nickcom commented 4 years ago

I gave up, but the problem still persists for me. I hope someone with more knowledge on this than me can work it out as I’d love to utilize this

luchomtvn commented 4 years ago

@nickcom i made it work. In the success callback function, instead of using JSON.stringify to read the incoming ArrayBuffer, i used String.fromCharCode.apply instead. Try using this a success callback to see if it works:

function (data) { console.log(String.fromCharCode.apply(null, new Uint8Array(data)); }

I found it weird that when i got the incoming ArrayBuffer, JSON.stringify read an empty objet but the byteLength attribute was right, according to the message i stored in the characteristic. For some reason, String.fromCharCode.apply actually finds the content instead of an empty obj.

Got it from here: https://www.npmjs.com/package/cordova-plugin-ble-central/v/1.1.9#typed-arrays

nickcom commented 4 years ago

Thanks for the tip! I’ll give it a try (I probably won’t have the chance for a few weeks though). Thanks!

don commented 4 years ago

JSON.stringify doesn't work on ArrayBuffers, you need to create a specific buffer based on the type of data you're expecting e.g UInt8Array(buffer) or Float32Array(buffer) and then get the data from the buffer.

nickcom commented 4 years ago

Just getting back to this now after stepping away from this project for a while. I'm still getting an empty reply.

I'm using the ble-peripheral to create a ble device to connect to, and the characteristic I'm trying to read are defined in that plugin as

characteristics: [
                      {
                      uuid: SWITCH_UUID,
                      properties: property.WRITE | property.READ,
                      permissions: permission.WRITEABLE | permission.READABLE,
                      descriptors: [
                                    {
                                    uuid: '2901',
                                    value: 'Switch'
                                    }
                                    ]
                      },
                      {
                      uuid: DIMMER_UUID,
                      properties: property.WRITE | property.READ,
                      permissions: permission.WRITEABLE | permission.READABLE,
                      descriptors: [
                                    {
                                    uuid: '2901',
                                    value: 'Dimmer'
                                    }
                                    ]
                      }
                      ]

Knowing this, is String.fromCharCode.apply(null, new Uint8Array(data)) the correct approach?