In this tutorial we will be creating a basic Android application
for sending the date and time to the CLUE board/DIY smartwatch. The
app will consist of scan button, which when pressed scans for
nearby BLE devices that are advertising the existence. A list of
devices will appear on the screen, which the user can scroll through.
When the CLUE board is on the "CWatch" name should appear in the list of
devices. The user then taps the name to synchronize the date/time and
day of week with the CLUE board.
Since Android apps are very complex and require quite a bit of
boilerplate code, we will focus on the core functionality for writing
to the BLE characteristics. To view the full implementation, see
the MainActivity.java file.
public class MainActivity extends AppCompatActivity implements MyRecyclerViewAdapter.ItemClickListener {
// ...
private final UUID rawTimeServiceUuid = UUID.fromString("00001805-0000-1000-8000-00805f9b34fb");
private final UUID dayDateTimeChrUuid = UUID.fromString("00002A0A-0000-1000-8000-00805f9b34fb");
private final UUID dayOfWeekChrUuid = UUID.fromString("00002A09-0000-1000-8000-00805f9b34fb");
private BluetoothGatt remoteGatt;
private void writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] payload) {
characteristic.setValue(payload);
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == BluetoothGattCharacteristic.PROPERTY_WRITE) {
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
} else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) {
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
}
remoteGatt.writeCharacteristic(characteristic);
}
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
if (characteristic.getUuid().equals(dayDateTimeChrUuid)) {
// We have succeeded in writing the day/date/time characteristic, now
// write the day of week
ByteBuffer dayOfWeek = ByteBuffer.allocate(1);
Calendar calendar = Calendar.getInstance();
dayOfWeek.put((byte) (calendar.get(Calendar.DAY_OF_WEEK) - 1));
BluetoothGattCharacteristic dayOfWeekChr = gatt.getService(rawTimeServiceUuid).getCharacteristic(dayOfWeekChrUuid);
writeCharacteristic(dayOfWeekChr, dayOfWeek.array());
}
Log.i("BluetoothGattCallback", "Wrote to characteristic $uuid | value: ${value.toHexString()}");
} else if (status == BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH) {
Log.e("BluetoothGattCallback", "Write exceeded connection ATT MTU!");
} else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
Log.e("BluetoothGattCallback", "Write not permitted for $uuid!");
} else {
Log.e("BluetoothGattCallback", "Characteristic write failed for $uuid, error: $status");
}
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
remoteGatt = gatt;
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
gatt.close();
}
} else {
gatt.close();
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
// First write the day date time
// Once this has succeeded, we then write the day of the week
// in the onCharacteristicWrite callback.
// Android BLE is not good at queueing multiple writes, so
// we have to do the next write only after the first write has
// succeeded
BluetoothGattCharacteristic dayDateTimeChr = gatt.getService(rawTimeServiceUuid).getCharacteristic(dayDateTimeChrUuid);
ByteBuffer dayDateTime = ByteBuffer.allocate(9);
Calendar calendar = Calendar.getInstance();
dayDateTime.put((byte) (calendar.get(Calendar.MONTH)));
dayDateTime.put((byte) (calendar.get(Calendar.DAY_OF_MONTH)));
dayDateTime.putInt((int) (calendar.get(Calendar.YEAR)));
dayDateTime.put((byte) (calendar.get(Calendar.HOUR_OF_DAY)));
dayDateTime.put((byte) (calendar.get(Calendar.MINUTE)));
dayDateTime.put((byte) (calendar.get(Calendar.SECOND)));
writeCharacteristic(dayDateTimeChr, dayDateTime.array());
}
};
@Override
public void onItemClick(View view, int position) {
BluetoothDevice device = mLeDevices.get(position);
if (device.getName().equals("CWatch")) {
device.connectGatt(getApplicationContext(), false, gattCallback);
}
}
// ...
}
The core logic is quite simple and consists of basic binary writes. When
the user taps on a BLE device in the list, the onItemClick
method is called. We check if this name is "CWatch" and if so connect to it and attach
our callback handler. When this handler has its onServicesDiscovered
callback called, we write the day/date/time to the correct characteristic.
At some point this write completes, and the onCharacteristicWrite
handler is called. When this is called we start the second characteristic write for
the day of the week. The order and size of the buffers that we write to
the characteristics are identical to what is given in the packed struct in the
Juniper program. The UUIDs are those given in the BLE specification and are
identical to what is used on the CLUE board.