Mi Band 3 pairing and fetching data (Heart Rate, Steps etc.) issue - android

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

Related

Reading NFC UID from android phone with PN532 and ArduinoMega

I am trying to use the PN532 to read my NFC UID from phone(Samsung Galaxy S10), but i receive just 08 and another 3 digits of random values.I read that a value that start with 08 is a RID(Random ID). Is there any possible way to read just a unique value, or use the PN532 to read something that is unique from my phones NFC?
I want to use that value to compare it with a constant in my code and send an impulse to a relay to open a door.
This code is from da Adafruit_PN532 library.
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>
#define PN532_IRQ (2)
#define PN532_RESET (3) // Not connected by default on the NFC Shield
// Or use this line for a breakout or shield with an I2C connection:
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);
void setup(void) {
Serial.begin(115200);
Serial.println("Hello!");
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (! versiondata) {
Serial.print("Didn't find PN53x board");
while (1); // halt
}
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();
Serial.println("Waiting for an ISO14443A Card ...");
}
void loop(void) {
uint8_t success;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
// Wait for an ISO14443A type cards (Mifare, etc.). When one is found
// 'uid' will be populated with the UID, and uidLength will indicate
// if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight)
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
if (success) {
// Display some basic information about the card
Serial.println("Found an ISO14443A card");
Serial.print(" UID Length: "); Serial.print(uidLength, DEC); Serial.println(" bytes");
Serial.print(" UID Value: ");
nfc.PrintHex(uid, uidLength);
if (uidLength == 4) {
// We probably have a Mifare Classic card ...
uint32_t cardid = uid[0];
cardid <<= 8;
cardid |= uid[1];
cardid <<= 8;
cardid |= uid[2];
cardid <<= 8;
cardid |= uid[3];
Serial.print("Seems to be a Mifare Classic card #");
Serial.println(cardid);
}
delay(2000);
}
}
Do not use the NFC UID for any security purpose, as you can see a phone does give a random one for privacy purposes.
The NFC UID is designed only to help reading hardware deal with handling sending data to the right card when multiple different Tags are in range. There is no guarantee that the UID is actually Unique and that it cannot duplicated (even with Tag that are supposed to have it programmed at the factory you can buy clones from China where they can be programmed by the end user).
It is better to use cryptographic methods with data stored on a tag or emulated Tag if using a phone to provide uniqueness to use a Tag for anything with security implications.

Broadcast not triggered when using bluetoothLEScanner().startScan

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

How to send more than 20 bytes data over ble in android?

I am trying to send more than 33 bytes using simple loops, Is anybody has idea how to send more than 20 bytes data over android ble.
if(!mConnected) return;
for (int i = 0; i<str.length;i++) {
if(str[i] == str[str.length -1]){
val = str[i]+"\n";
}else {
val = str[i] + "_";
}
System.out.println(val);
mBluetoothLeService.WriteValue(val);
}
Sending more than 20 bytes via BLE is easily achievable by splitting your data into 20 byte packets and implementing a short delay (i.e. using sleep()) between sending each packet.
Here's a short snippet of code from a project I'm working on that takes data in the form of byte[] and splits it into an array of the same, ( byte[][] ), in 20 byte chunks, and then sends it to another method that transmits each packet one by one.
int chunksize = 20;
byte[][] packets = new byte[packetsToSend][chunksize];
int packetsToSend = (int) Math.ceil( byteCount / chunksize);
for(int i = 0; i < packets.length; i++) {
packets[i] = Arrays.copyOfRange(source,start, start + chunksize);
start += chunksize;
}
sendSplitPackets(packets);
Here are two other very good explanations of how to achieve this:
(Stackoverflow) Android: Sending data >20 bytes by BLE
(Nordic Semi) Dealing Large Data Packets Through BLE
You can send more than 20 bytes of data without breaking into chunks and including a delay. Every characteristics you are trying to write has an MTU value assigned. It's number of bytes you can write in one time.
During the connection MTU values are exchanged and you can write those many bytes at a time. You can increase the mtu value on the server side (Max 512 bytes) and send that much bytes in one go.
For Android, you might want to request mtu manually after connecting with the server using
requestMtu(int mtu)
This is return true or false based on the mtu value you send. It will give a callback to onMtuChanged where Android device and server negotiate the maximum possible MTU value.
onMtuChanged (BluetoothGatt gatt, int mtu, int status)
and you can set MTU value in this function and can send more than 20 bytes in one go.
Some embedded bluetooth LE implementations limit the size of a characteristic to be 20 bytes. I know that the Laird BL600 series does this. This is limitation of the Laird module, even though the BLE spec calls for the max length to be longer. Other embedded BLE solutions have similar limits. I suspect this is the limitation that you are encountering.
Instead of using sleep for every chunk, i just found a better and efficient way for my application to send more than 20 bit data.
The packets will be send after onCharacteristicWrite() triggered. i just found out this method will be triggered automatically after peripheral device (BluetoothGattServer) sends a sendResponse() method.
firstly we have to transform the packet data into chunk with this function:
public void sendData(byte [] data){
int chunksize = 20; //20 byte chunk
packetSize = (int) Math.ceil( data.length / (double)chunksize); //make this variable public so we can access it on the other function
//this is use as header, so peripheral device know ho much packet will be received.
characteristicData.setValue(packetSize.toString().getBytes());
mGatt.writeCharacteristic(characteristicData);
mGatt.executeReliableWrite();
packets = new byte[packetSize][chunksize];
packetInteration =0;
Integer start = 0;
for(int i = 0; i < packets.length; i++) {
int end = start+chunksize;
if(end>data.length){end = data.length;}
packets[i] = Arrays.copyOfRange(data,start, end);
start += chunksize;
}
after our data ready, so i put my iteration on this function:
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if(packetInteration<packetSize){
characteristicData.setValue(packets[packetInteration]);
mGatt.writeCharacteristic(characteristicData);
packetInteration++;
}
}

Read advertisement packet in Android

I'm working on a BLE sensor that is advertising manufacturer specific data. Is there any sample code that demonstrates how to receive an advertisement packet in Android and parse its payload?
This is what I was looking for:
The BLE scan API BluetoothAdapter.startLeScan(ScanCallback) requires a call back function for the scan results. the method needs to look like the following:
private BluetoothAdapter.LeScanCallback ScanCallback =
new BluetoothAdapter.LeScanCallback()onLeScan(final BluetoothDevice device,
int rssi,
final byte[] scanRecord)
{...}
And the scanRecord variable is a byte array which contains the Advertisement packet payload.
Per the BLE specification the structure of the payload is very simple as follows:
The packets can be up to 47 bytes in length and consist of:
1 byte preamble
4 byte access address
2-39 bytes advertising channelPDU
3 bytes CRC
For advertisement communication channels, the access address is always 0x8E89BED6.
The PDU in turn has its own header (2 bytes: size of the payload and its type – whether the device supports connections, etc.) and the actual payload (up to 37 bytes).
Finally, the first 6 bytes of the payload are the MAC address of the device, and the actual information can have up to 31 bytes.
the format of the actual information is as follows:
first byte is length of the data and second byte is type followed by the data.
This is a clever way to allow any application to skip entire data records if they don't care about the contents.
Here is the sample code to determine the contents of the Advertisement packet:
parseAdvertisementPacket(final byte[] scanRecord) {
byte[] advertisedData = Arrays.copyOf(scanRecord, scanRecord.length);
int offset = 0;
while (offset < (advertisedData.length - 2)) {
int len = advertisedData[offset++];
if (len == 0)
break;
int type = advertisedData[offset++];
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
while (len > 1) {
int uuid16 = advertisedData[offset++] & 0xFF;
uuid16 |= (advertisedData[offset++] << 8);
len -= 2;
uuids.add(UUID.fromString(String.format(
"%08x-0000-1000-8000-00805f9b34fb", uuid16)));
}
break;
case 0x06:// Partial list of 128-bit UUIDs
case 0x07:// Complete list of 128-bit UUIDs
// Loop through the advertised 128-bit UUID's.
while (len >= 16) {
try {
// Wrap the advertised bits and order them.
ByteBuffer buffer = ByteBuffer.wrap(advertisedData,
offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
long mostSignificantBit = buffer.getLong();
long leastSignificantBit = buffer.getLong();
uuids.add(new UUID(leastSignificantBit,
mostSignificantBit));
} catch (IndexOutOfBoundsException e) {
// Defensive programming.
Log.e("BlueToothDeviceFilter.parseUUID", e.toString());
continue;
} finally {
// Move the offset to read the next uuid.
offset += 15;
len -= 16;
}
}
break;
case 0xFF: // Manufacturer Specific Data
Log.d(TAG, "Manufacturer Specific Data size:" + len +" bytes" );
while (len > 1) {
if(i < 32) {
MfgData[i++] = advertisedData[offset++];
}
len -= 1;
}
Log.d(TAG, "Manufacturer Specific Data saved." + MfgData.toString());
break;
default:
offset += (len - 1);
break;
}
}
thanks to
how-ibeacons-work
bluetooth org specs
mass for putting me on the right direction!
ADPayloadParser in nv-bluetooth parses the payload of an advertising packet and returns a list of AD structures. The AD structure format is described in "11 ADVERTISING AND SCAN RESPONSE DATA FORMAT" of "Bluetooth Core Specification 4.2".
The following code snippet is an implementation example of onLeScan method.
public void onLeScan(
BluetoothDevice device, int rssi, byte[] scanRecord)
{
// Parse the payload of the advertising packet.
List<ADStructure> structures =
ADPayloadParser.getInstance().parse(scanRecord);
// For each AD structure contained in the advertising packet.
for (ADStructure structure : structures)
{
if (structure instanceof IBeacon)
{
// iBeacon packet was found.
handleIBeacon((IBeacon)structure);
}
......
}
}
You can register a parser of your own for your manufacturer-specific format into ADPayloadParser. Refer to the following links for more information.
Blog: http://darutk-oboegaki.blogspot.jp/2015/03/ibeacon-as-kind-of-ad-structures.html
GitHub: https://github.com/TakahikoKawasaki/nv-bluetooth
JavaDoc: http://takahikokawasaki.github.io/nv-bluetooth/
Maven: http://search.maven.org/#search|ga|1|a%3A%22nv-bluetooth%22
edit 21.02.2016
The library i linked below seems to have been moved;
see https://github.com/AltBeacon/android-beacon-library
You can use Android iBeacon Library for a start.
There is a reference application which you can use for the basics and with simulated data.
~https://github.com/RadiusNetworks/ibeacon-reference-android~
Once you get it up and running you may wish to import the library and use it with your real device, there is also some example code on the site:
http://developer.radiusnetworks.com/ibeacon/android/samples.html

How to send the text data to CC2541 keyfob via Android 4.3?

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.

Categories

Resources