Im trying to wake my application when the device enters a region of beacons with a specific UUID.
So far I tried using the native
BluetoothAdapter.getBluetoothLeScanner, providing a PendingIntent to launch a Broadcast reciever.
I also tried RxBleClient which is an amazing library which does work when doing a regular discovery.
I tried AltBeacon lib which is also a very thorough and brilliant lib.
I also tried play services Nearby Apis.
But all failed. They can all scan for beacons regularly but when trying to basically, register and let the device scan while in the bg. The broadcast never gets triggered.
I also tried all 4 while using a foreground service. That did not help either.
Ill also mention this is all done on Android 8.
I went over the code in both libs and they both eventually use the same function call from the native bluetoothadapter. So Im assuming if that fails, everything else will fail too.
Ill also mention that when supplying the startScan function with a CallBack instead of a PendingIntent, the callback is constantly triggered for all scanned BT devices. If I add a ScanFilter to filter for my UUID only, it fails.
What am I missing?
Btw, when I implemented this in Ios, it worked flawlessly. The app is awoken as if I used a Geofence enter/exit trigger.
Why wont this work in Android??
Any thoughts?
EDIT:
This is how I used the code from the AltBeacon library. It is basically a copy paste from the DOCS tutorial. Only difference is I encapsulated it in a seperate class:
public void init(AppCompatActivity context) {
if (isRegistered) {
return;
}
BluetoothManager bluetoothManager = (BluetoothManager) context.getApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) {
bluetoothAdapter = bluetoothManager.getAdapter();
if (!bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
context.startActivityForResult(enableBtIntent, 1002);
} else {
register(context);
}
}
}
public void register(AppCompatActivity context) {
if (isRegistered) {
return;
}
executorService.execute(new Runnable() {
#Override
public void run() {
Region region = new Region("com.sample.WakeOnDiscovery", Identifier.parse("E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"), Identifier.fromInt(11180), null);
bootstrapRegion = new RegionBootstrap(new BootstrapNotifier() {
#Override
public Context getApplicationContext() {
return Application.getContextFromApplicationClass();
}
#Override
public void didEnterRegion(Region region) {
Logger.log(TAG, "didEnterRegion: " + region.toString());
}
#Override
public void didExitRegion(Region region) {
Logger.log(TAG, "didExitRegion: " + region.toString());
}
#Override
public void didDetermineStateForRegion(int i, Region region) {
Logger.log(TAG, "didDetermineStateForRegion: " + region.toString() + "|" + i);
}
}, region);
}
});
isRegistered = true;
}
This is when I try to scan for this specific UUID and major int (and without the major int). Only the didDetermineStateForRegion is triggered once I think. With int i = 0 or 1.
Regarding the UUID I tried 2 different UUIDs. The first is one that I generate in a sample app I wrote in IOS. The ios app advertises itself as a beacon. I can see it in a different app that uses a regular discovery function.
But the onEnterRegion does not trigger.
I also tried a UUID from an iBeacon device that I use regularly for years now.
Still no dice.
When I try using the native android ble scanner I use this:
ScanSettings settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)).setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES).build();
List<ScanFilter> filters = new ArrayList<>(); // Make a scan filter matching the beacons I care about
filters.add(new ScanFilter.Builder()/*.setServiceUuid(ParcelUuid.fromString("E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"))*/.build());
bluetoothAdapter.getBluetoothLeScanner().startScan(filters, settings, getPendingIntent(context));
and:
private PendingIntent getPendingIntent(Context context) {
return PendingIntent.getBroadcast(context, 0, new Intent(context, BTDiscoveryBroadcast.class), PendingIntent.FLAG_UPDATE_CURRENT);
}
I tried passing null as filters, I tried an empty array list and I tried an actual filter with the same UUID.
Still nothing works.
what I did see by chance in logcat is this error message:
E/NearbyDirect: Could not start scan. [CONTEXT service_id=49 ]
java.lang.IllegalStateException: Receiver com.google.location.nearby.direct.bluetooth.state.FastPairScanner$4#535d6ea registered with differing handler (was Handler (adxv) {8fab16a} now Handler (android.app.ActivityThread$H) {e4e0078})
I think this occurs when using the AltBeacon lib as well. I can only assume this is related to my problem.
EDIT 2:
I tried this:
ScanSettings settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)).setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES).build();
List<ScanFilter> filters = new ArrayList<>(); // Make a scan filter matching the beacons I care about
byte[] manufacturerData = new byte[] {
// iBeacon identifier prefix
0x02, 0x15,
// Your Proximity UUID
(byte) 0xE2, (byte) 0xC5, 0x6D, (byte) 0xB5, (byte) 0xDF, (byte) 0xFB, 0x48, (byte) 0xD2, (byte) 0xB0, 0x60, (byte) 0xD0, (byte) 0xF5, (byte) 0xA7, 0x10, (byte) 0x96, (byte) 0xE0,
0x3, 0x20, 0x3, 0x20, (byte) 0xC5
};
int manufacturerId = 0x004c; // Apple
filters.add(new ScanFilter.Builder().setManufacturerData(manufacturerId, manufacturerData).build());
bluetoothAdapter.getBluetoothLeScanner().startScan(filters, settings, getPendingIntent(context));
The bytes data I retrieved after doing a regular .startDiscovery scan, locating my bt device and extracting from ScanResult the data. Just to make sure its precisely the same.
Still does not trigger broadcast :(
If you want to set up a scan filter to look for a beacon UUID, code like this won't work:
filters.add(new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("E2C56DB5-DFFB-48D2-B060-D0F5A71096E0")).build());
The above code will set a filter to look for a BLE device advertising a GATT Service UUID matching E2C56DB5-DFFB-48D2-B060-D0F5A71096E0. A GATT Service UUID has absolutely nothing to do with a Bluetooth Beacon Proximity UUID, even though both UUIDs may look superficially the same. In other words, you are telling the filter to look for a connectable bluetooth device that advertises a custom service that just happens to be the same as your beacon identifier. This will never find anything, because such a device is almost guaranteed not to exist.
What you want to do instead is more complicated -- you want to set up a scan filter that looks for a bluetooth LE manufacturer advertisement (which iBeacon and AltBeacon advertisements are), that have a specific manufacturer code and start with a certain sequence of bytes.
Getting the exact byte sequence for the filter is tricky because it depends on both the manufacturer ID the beacon layout (iBeacon or AltBeacon). This is one of the many complexities the Android Beacon Library handles for you.
If you really need to do it yourself you would do something like this for iBeacon (WARNING: untested code):
byte[] manufacturerData = new byte[] {
// iBeacon identifier prefix
0x02, 0x15,
// Your Proximity UUID
0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0
};
int manufacturerId = 0x004c; // Apple
ScanFilter filter =
filters.add(new ScanFilter.Buider().setManufacturerData(manufacturerId, manufacturerData).build());
Related
Is there a possibility how to "overcome" BLE MAC address randomization and detect presence of my own Android phone(s)?
I'm looking for a solution how to detect presence of my phone in close-range to ESP32 without installing something like iBeacon app which would drain my battery.
I've started with example for BLE sniffer which works nice but with MAC randomization on Android it is useless.
Then I moved to the solution using emulation of a HID keyboard. After pairing it, it is nicely reconnecting when the phone comes into the range. Once it is connected I can trigger needed action and then I can turn ESP32 Bluetooth off not to be connected whole time. When I need check the phone again I just can turn the server back on. It would be neat solution but... I need to check more (at least two) phones. I've tried to duplicate BLEServers to swith between or run two simoutinasly but without success - either it is not possible or it is exceeding my knowladge about this BLE advertising/pairing/connecting magic.
Third solution would be to have separate ESP for each phone - doable but only as a last resort
Has somebody solved such task somehow already?
For the keyboard solution I'm using this code found online:
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"
#include <driver/adc.h>
BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;
BLEAdvertising *pAdvertising;
BLEServer *pServer;
bool connected = false;
bool restart = false;
class MyCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer){
connected = true;
Serial.println("Connected");
BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
desc->setNotifications(true);
// NEEDED ACTIONS
}
void onDisconnect(BLEServer* pServer){
connected = false;
Serial.println("DisConnected");
BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
desc->setNotifications(false);
restart = true;
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
BLEDevice::init("Backpack-MeowMeow");
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyCallbacks());
pServer->getPeerDevices(true);
hid = new BLEHIDDevice(pServer);
input = hid->inputReport(1); // <-- input REPORTID from report map
output = hid->outputReport(1); // <-- output REPORTID from report map
std::string name = "ElectronicCats";
hid->manufacturer()->setValue(name);
hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
hid->hidInfo(0x00,0x02);
BLESecurity *pSecurity = new BLESecurity();
// pSecurity->setKeySize();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);
hid->startServices();
pAdvertising = pServer->getAdvertising();
pAdvertising->setAppearance(HID_BARCODE);
pAdvertising->addServiceUUID(hid->hidService()->getUUID());
pAdvertising->start();
hid->setBatteryLevel(7);
//ESP_LOGD(LOG_TAG, "Advertising started!");
//delay(portMAX_DELAY);
}
void loop() {
if(connected){
delay(10);
}
if (restart) {
restart = false;
pAdvertising->start();
}
delay(50);
}
Tried pairing Mi Band with GATT services and characteristics, no response from band. Issue same for both android and iOS. Only works with Mi Fit official app.
Neither updating nor notifying the updates for any of the characteristics except battery,Date, other info.
Note:- Tried services/characteristics -
FF0F
2A37
2A39
0000180d-0000–1000–8000–00805f9b34fb
0000fee0–0000–1000–8000–00805f9b34fb
0000009–0000–1000–8000–00805f9b34fb
You will need to authenticate the app before start fetch data. The steps are:
Enable notification for auth characteristics "0009"
Send 18 bytes to the auth characteristics
Once notified {0x10, 0x01, 0x01}, write another request that sends the first two bytes in (2)
Once notified {0x10, 0x02, 0x01}, write another request that sends bytes with AES encrypted
One notified {0x10, 0x03, 0x01}, you can proceed with other actions like fetching data.
I have noticed that in Mi Band 3 even without pairing(Authentication) you can access Heart Rate data and Details for heart rate measurement:
let BLE_Heart_Rate_Service_CBUUID = CBUUID(string: "0x180D")
let Heart_rate_UUID = CBUUID(string: "2A37")
Now put below code in didUpdateValueFor
if characteristic.uuid == Heart_rate_UUID {
print("HeartRate_UUID reading: ", characteristic.value)
peripheral.readValue(for: characteristic)
print("HEART RATE: ", getHeartRate(heartRateData: characteristic.value!))
}
Use getHeartRate() for reading Heart Rate.
func getHeartRate(heartRateData:Data) -> Int{
print("--- UPDATING Heart Rate..")
var buffer = [UInt8](repeating: 0x00, count: heartRateData.count)
heartRateData.copyBytes(to: &buffer, count: buffer.count)
var bpm:UInt16?
if (buffer.count >= 2){
if (buffer[0] & 0x01 == 0){
bpm = UInt16(buffer[1]);
}else {
bpm = UInt16(buffer[1]) << 8
bpm = bpm! | UInt16(buffer[2])
}
}
if let actualBpm = bpm{
return Int(actualBpm)
}else {
return Int(bpm!)
}
}
I want to only scan BLE beacons with a specific UUID in my Android code. Even though I can add filter for specific MAC addresses, I cannot make it work with UUIDs. onScanResult function is never called. Why could that be? I'm using API 21 and I'm not getting any errors for the project.
final String tagUUID = "01122334-4556-6778-899a-abbccddeeff0";
//does not work
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(UUID.fromString(tagUUID))).build();
//works
ScanFilter filter = new ScanFilter.Builder().setDeviceAddress(tagMAC).build();
I'm the author of the blog post mentioned above. Here's how to fix your issue for Android 21+.
// Empty data
byte[] manData = new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
// Data Mask
byte[] mask = new byte[]{0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0};
// Copy UUID into data array and remove all "-"
System.arraycopy(hexStringToByteArray("YOUR_UUID_TO_FILTER".replace("-","")), 0, manData, 2, 16);
// Add data array to filters
ScanFilter filter = new ScanFilter.Builder().setManufacturerData(76, manData, mask).build());
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
The issue here is that you can add UUID filtering but its not exactly straight forward
It is possible that the filtering looks for the service UUID in the "Service UUID" AD Type Advertising Structure. Which actually makes sense and that's how it should work.
For beacons, the UUID you are trying to find is actually located in the "Manufacturer Specific Data" AD Type structure. And nobody cares about looking for Service UUIDs there.
I believe that the service UUID filtering is only meant to filter for UUIDs of services in the GATT Database; those UUIDs would be located as I explained in the first paragraph.
That UUID in beacons is not a service UUID per se. It is rather a beacon identifier with an UUID format.
I' currently trying to get HCE running with my arduino uno device mounted with a seeed NFC Shield V 2.0
I'm using the example code from https://github.com/Seeed-Studio/PN532/blob/master/PN532/examples/android_hce/android_hce.ino
#include <SPI.h>
#include <PN532_SPI.h>
#include <PN532Interface.h>
#include <PN532.h>
PN532_SPI pn532spi(SPI, 10);
PN532 nfc(pn532spi);
void setup()
{
Serial.begin(115200);
Serial.println("-------Peer to Peer HCE--------");
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (! versiondata) {
Serial.print("Didn't find PN53x board");
while (1); // halt
}
// Got ok data, print it out!
Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC);
Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
// Set the max number of retry attempts to read from a card
// This prevents us from waiting forever for a card, which is
// the default behaviour of the PN532.
//nfc.setPassiveActivationRetries(0xFF);
// configure board to read RFID tags
nfc.SAMConfig();
}
void loop()
{
bool success;
uint8_t responseLength = 32;
Serial.println("Waiting for an ISO14443A card");
// set shield to inListPassiveTarget
success = nfc.inListPassiveTarget();
if(success) {
Serial.println("Found something!");
uint8_t selectApdu[] = { 0x00, /* CLA */
0xA4, /* INS */
0x04, /* P1 */
0x00, /* P2 */
0x07, /* Length of AID */
0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* AID defined on Android App */
0x00 /* Le */ };
uint8_t response[32];
success = nfc.inDataExchange(selectApdu, sizeof(selectApdu), response, &responseLength);
if(success) {
Serial.print("responseLength: "); Serial.println(responseLength);
nfc.PrintHexChar(response, responseLength);
do {
uint8_t apdu[] = "Hello from Arduino";
uint8_t back[32];
uint8_t length = 32;
success = nfc.inDataExchange(apdu, sizeof(apdu), back, &length);
if(success) {
Serial.print("responseLength: "); Serial.println(length);
nfc.PrintHexChar(back, length);
}
else {
Serial.println("Broken connection?");
}
}
while(success);
}
else {
Serial.println("Failed sending SELECT AID");
}
}
else {
Serial.println("Didn't find anything!");
}
delay(1000);
}
void printResponse(uint8_t *response, uint8_t responseLength) {
String respBuffer;
for (int i = 0; i < responseLength; i++) {
if (response[i] < 0x10)
respBuffer = respBuffer + "0"; //Adds leading zeros if hex value is smaller than 0x10
respBuffer = respBuffer + String(response[i], HEX) + " ";
}
Serial.print("response: "); Serial.println(respBuffer);
}
void setupNFC() {
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (! versiondata) {
Serial.print("Didn't find PN53x board");
while (1); // halt
}
// Got ok data, print it out!
Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC);
Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
// configure board to read RFID tags
nfc.SAMConfig();
}
My android code is taken from the HCE development guide and I also tried the code form https://github.com/grundid/host-card-emulation-sample
My problem is, that the arduino code doesn't even recognize a tag. So all I'm getting is:
Waiting for an ISO14443A card
Didn't find anything!
Waiting for an ISO14443A card
Didn't find anything!
Waiting for an ISO14443A card
As a matter of fact, on the Android side, I do not receive any packet, neither is the fancy NFC sound playing, which usually indicates that at least something is happening.
So my question is, has someone tried to use the Galaxy S3 with cyanogenmod 10 along with HCE support. I'm really out of any ideas, I triplechecked my code, read a lots of question here on SO, but all did at least receive something from the NFC Shield.
I know that the common NFC mode works fine, as I have an application using NFC in "normal" mode. When I run this NFC-protocol on my arduino, all applications receive a packet, (although they can't route them correctly, because they are not adpu packets)
Android 4.4 HCE does not run on Samsung Galaxy S3 with NXP chip.
I had tested for both Galaxy Nexus, Nexus 7(2012) and Galaxy S3. All these 3 devices are not able to run HCE on Kitkat.
You may check the device for HCE support:
boolean isHceSupported =
getPackageManager().hasSystemFeature("android.hardware.nfc.hce");
Android HCE (as provided by the Android 4.4 HCE API) is not supported on CyanogenMod 10.*. That API is only available starting with CyanogenMod 11 and even then I'm not sure that the CyanogenMod implementation of the Android HCE API works on devices with NXP chipset (e.g. Galaxy S3).
CyanogenMod 10.* has a different HCE API. See Nikolay's blog for an example on how to use that API.
Basically, for your Arduino program to work, you would have to implement HCE based on the IsoPcdA technology.
Also note that if you ever switch from CyanogenMod HCE to Android HCE, you have to use ISO 7816-4 compliant APDUs. Thus, an "APDU" like you use it in your code above,
apdu[] = "Hello from Arduino";
would not work. CyanogenMod HCE, however, does not require the use of ISO 7816-4 APDUs on top of the ISO 14443-4 transport protocol and will, therefore, happily accept this string instead of an APDU.
I am developing an application where I have to connect to Bluetooth device on Android 4.3.
And I want to change the name of CC2541 Keyfob via the Android application.
My ideas is:
1.There has a Plain Text that I can type the name what I want in my Android application.
2.After I type the name, I push the button to send this text.
3.If the CC2541 receive this text from Android application , it will change the text in the deviceName[] of the following code in keyfobdemo.c:
static uint8 deviceName[] =
{
// complete name
0x0b, // length of first data structure (11 bytes excluding length byte)
0x09, // AD Type = Complete local name
0x4b, // 'K'
0x65, // 'e'
0x79, // 'y'
0x66, // 'f'
0x6f, // 'o'
0x62, // 'b'
0x64, // 'd'
0x65, // 'e'
0x6d, // 'm'
0x6f, // 'o'
};
The question like the following:
1.How to send the text data to CC2541 keyfob in Android application 4.3 ??
2.How to receive the text data on CC2541 side ??
3.Did I need to use any profile ??
Sorry about my English, and these question.
Thanks for your direction.
Edit
I have trying to use 0x2A00 to get the Device Name service , but it seen not working when I call the Device_Name function.
The Name_Service is null.
private static final UUID Device_Name_UUID = UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb");
private static final UUID Write_UUID = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
public void Device_Name(){
BluetoothGattService Name_Service = mBluetoothGatt.getService(Write_UUID );
if(Name_Service == null) {
Log.d(TAG, "Name_Service service not found!");
return;
}
BluetoothGattCharacteristic DeviceName = Name_Service.getCharacteristic(Device_Name_UUID);
if(DeviceName == null) {
Log.d(TAG, "DeviceName charateristic not found!");
return;
}
}
Log.v(TAG, "readCharacteristic(DeviceName) = " + mBluetoothGatt.readCharacteristic(DeviceName));
String i = "123";
DeviceName.setValue(i);
Log.v(TAG, "writeCharacteristic(DeviceName) = " + mBluetoothGatt.writeCharacteristic(DeviceName));
it show the following Log:
V/BluetoothLeService( 3680): readCharacteristic(DeviceName) = true
V/BluetoothLeService( 3680): writeCharacteristic(DeviceName) = false
D/audio_hw_primary( 1752): found out /dev/snd/pcmC0D0p
W/audio_hw_primary( 1752): out_write() limiting sleep time 45351 to 23219
W/audio_hw_primary( 1752): out_write() limiting sleep time 34263 to 23219
W/audio_hw_primary( 1752): out_write() limiting sleep time 33696 to 23219
D/BtGatt.btif( 2646): btif_gattc_upstreams_evt: Event 3
I/BtGatt.btif( 2646): set_read_value unformat.len = 13
D/BtGatt.GattService( 2646): onReadCharacteristic() - address=90:59:AF:0B:8A:AB, status=0, length=13
D/BluetoothGatt( 3680): onCharacteristicRead() - Device=90:59:AF:0B:8A:AB UUID=00002a00-0000-1000-8000-00805f9b34fb Status=0
it read successful,and I can get the name of device.
And I reference the Bluetooth Page-Device Name , the format is UTF-8 String.
But it writeCharacteristic false.
I don't know about the Android BLE API, but I can tell you how this is supposed to work with Bluetooth Low Energy.
The device name is stored in the GATT server (a local database on the cc2541 device). If you connect to the BLE device you should be able to do a discover to figure out the structure of the database and find the ATT handle for the device name.
The GATT server is built up of attributes with a UUID (loosely defining the type of attribute) an attribute handle (the identifier used in this instance of the GATT server) and a value. According to [1] the UUID for the device name is 0x2A00. So you can search by type and find the handle with this UUID.
Once you have the UUID it's just a matter of using the GATT client in the Android API to send a write request to this handle with the new value
Edit: Looking at the API I think you should use getService(0x18, 0x00) [2] to get the primary service (which should contain the device name) and then writeCharacteristic[3] to update the name.
From [4] it looks like the code should look something like this (not tested):
public void writeCharacteristic(byte[] value) {
BluetoothGattService gap_service = mBluetoothGatt.getService(
UUID.fromString("00001800-0000-1000-8000-00805F9B34FB"));
if (gap_service == null) {
System.out.println("gap_service null";);
return;
}
BluetoothGattCharacteristic dev_name = gap_service.getCharacteristic(
UUID.fromString("00002A00-0000-1000-8000-00805F9B34FB"));
if (dev_name == null) {
System.out.println("dev_name null";);
return;
}
dev_name.setValue(value);
boolean status = mBluetoothGatt.writeCharacteristic(dev_name);
System.out.println("Write Status: " + status);
}
[1] bluetooth.org
[2] getService
[3] writeCharacteristic
[4] devzone.nordicsemi.com
This is my solution, adding to Vegar's background information about GATT, profiles, etc.
This is based on the simpleBLEPeripheral application in the CC2541 SDK, and the sensortag Android application. The simpleBLEPeripheral characteristic lets you read a multiple byte characteristic, I modified it to enable writes. The simpleGATTprofile.c simpleProfile_WriteAttrCB() method needs an additional case statement:
case SIMPLEPROFILE_CHAR5_UUID:
//Validate the value
// Make sure it's not too long
if ( len >= SIMPLEPROFILE_CHAR5_LEN )
{
status = ATT_ERR_INVALID_VALUE_SIZE;
}
//Write the value
if ( status == SUCCESS )
{
uint8 *pCurValue = (uint8 *)pAttr->pValue;
osal_memcpy(pCurValue+offset, pValue, len);
notifyApp = SIMPLEPROFILE_CHAR5;
}
break;
On the Android side, the following code is placed in DeviceActivity.java. Please forgive the messy code, it's a quick hack. It takes a string, finds the hex representation, which is then sent as a characteristic update to the CC2541.
void writeString() {
UUID servUuid = SensorTagGatt.UUID_STR_SERV;
UUID configUuid = SensorTagGatt.UUID_STR_DATA;
BluetoothGattService serv = mBtGatt.getService(servUuid);
BluetoothGattCharacteristic config = serv.getCharacteristic(configUuid);
int OAD_BLOCK_SIZE = 18;
int OAD_BUFFER_SIZE = OAD_BLOCK_SIZE + 2;
int GATT_WRITE_TIMEOUT = 300; // Milliseconds
String msg = new String();
byte[] mOadBuffer = hexStringToByteArray("e04fd020ea3a6910a2d808002b30309daabbccdd");
// Send block
config.setValue(mOadBuffer);
boolean success = mBtLeService.writeCharacteristic(config);
if (success) {
// Update stats
if (!mBtLeService.waitIdle(GATT_WRITE_TIMEOUT)) {
success = false;
msg = "GATT write timeout\n";
}
} else {
msg = "GATT writeCharacteristic failed\n";
}
if (!success) {
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
}
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
The key is to make sure your UUIDs match up, otherwise nothing will work.