ArduinoBLEPebbleSwitch
Contents
Creating a switch with pebble, android, arduino and BLE (Blue Tooth Low Energy)
Arduino Code
Library from adafruit available here: Adafruit_nRF8001
/*********************************************************************
description: Switch
hardware: nRF8001 Bluetooth Low Energy Breakout
Setup: PIN 13: Connected to SCK of BLE (Clock)
PIN 12: Connected to MISO of BLE (Master Input, Slave Output, sending data to arduino
from peripherals)
PIN 11: Connected to MOSI of BLE (Master Output, Slave Inut, sending data form peripherals to arduino).
PIN 10: Connected to REQ of BLE (this is basically what the nRF8001 considers the 'SPI Chip Select' pin, its an input)
PIN 09: Connected to RST of BLE ( (reset) - this is the reset pin input)
PIN 02: Connected to RDY of BLE ((ready) - this is the data-ready pin,
an interrupt output from the breakout to the microcontroller
letting it know that data is ready to read)
GND: Connected to GND of BLE
5V: Connected to VIN of BLE
*********************************************************************/
// This version uses the internal data queing so you can treat it like Serial (kinda)!
#include <SPI.h>
#include "Adafruit_BLE_UART.h"
// Connect CLK/MISO/MOSI to hardware SPI
// e.g. On UNO & compatible: CLK = 13, MISO = 12, MOSI = 11
#define ADAFRUITBLE_REQ 10
#define ADAFRUITBLE_RDY 2 // This should be an interrupt pin, on Uno thats #2 or #3
#define ADAFRUITBLE_RST 9
Adafruit_BLE_UART BTLEserial = Adafruit_BLE_UART(ADAFRUITBLE_REQ, ADAFRUITBLE_RDY, ADAFRUITBLE_RST);
int SWITCH_PIN = 7;
void setup(void)
{
Serial.begin(9600);
while(!Serial); // Leonardo/Micro should wait for serial init
Serial.println(F("Adafruit Bluefruit Low Energy nRF8001 Print echo demo"));
pinMode(SWITCH_PIN, OUTPUT);
digitalWrite(SWITCH_PIN, HIGH);
BTLEserial.begin();
}
aci_evt_opcode_t laststatus = ACI_EVT_DISCONNECTED;
void sendDataToBleCient()
{
if (Serial.available()) {
// Read a line from Serial
Serial.setTimeout(100); // 100 millisecond timeout
String s = Serial.readString();
// We need to convert the line to bytes, no more than 20 at this time
uint8_t sendbuffer[20];
s.getBytes(sendbuffer, 20);
char sendbuffersize = min(20, s.length());
Serial.print(F("\n* Sending -> \""));
Serial.print((char *)sendbuffer);
Serial.println("\"");
// write the data
BTLEserial.write(sendbuffer, sendbuffersize);
}
}
void receieveDataFromBleClient()
{
boolean doSwitch = false;
// Lets see if there's any data for us!
if (BTLEserial.available()) {
Serial.print("* ");
Serial.print(BTLEserial.available());
Serial.println(F(" bytes available from BTLE"));
doSwitch = true;
}
if(doSwitch == true)
{
int currentState = digitalRead(SWITCH_PIN);
int newState = LOW;
if(currentState == LOW)
{
newState = HIGH;
}
digitalWrite(SWITCH_PIN, newState);
}
// OK while we still have something to read, get a character and print it out
while (BTLEserial.available()) {
char c = BTLEserial.read();
Serial.print(c);
}
}
aci_evt_opcode_t updateBLEDeviecStatus()
{
// Ask what is our current status
aci_evt_opcode_t status = BTLEserial.getState();
// If the status changed....
if (status != laststatus) {
// print it out!
if (status == ACI_EVT_DEVICE_STARTED) {
Serial.println(F("* Advertising started"));
}
if (status == ACI_EVT_CONNECTED) {
Serial.println(F("* Connected!"));
}
if (status == ACI_EVT_DISCONNECTED) {
Serial.println(F("* Disconnected or advertising timed out"));
}
// OK set the last status change to this one
laststatus = status;
}
return status;
}
void loop()
{
// Tell the nRF8001 to do whatever it should be working on.
BTLEserial.pollACI();
aci_evt_opcode_t status = updateBLEDeviecStatus();
if (status == ACI_EVT_CONNECTED) {
//reveieve Data
receieveDataFromBleClient();
// Next up, see if we have any data to get from the Serial console
sendDataToBleCient();
}
}
Android code
Use Bluetooth Low Energy sample (Android SDK) as a starting point.
public static String UART_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
public static String UART_RX_CHARACTERISTICS = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
public static String UART_TX_CHARACTERISTICS = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
The modified service:
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.bluetoothlegatt;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import com.getpebble.android.kit.PebbleKit;
import com.getpebble.android.kit.util.PebbleDictionary;
import java.util.List;
import java.util.UUID;
/**
* Service for managing connection and data communication with a GATT server hosted on a
* given Bluetooth LE device.
*/
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";
public final static String PEBBLE_CONNECTED = "com.example.bluetooth.le.PEBBLE_CONNECTED";
public final static String PEBBLE_DISCONNECTED = "com.example.bluetooth.le.PEBBLE_DISCONNECTED";
public final static String PEBBLE_BUTTON_MIDDLE = "com.example.bluetooth.le.PEBBLE_MIDDLE";
public final static String PEBBLE_BUTTON_UP = "com.example.bluetooth.le.PEBBLE_UP";
public final static String PEBBLE_BUTTON_DOWN = "com.example.bluetooth.le.PEBBLE_DOWN";
public final static String PEBBLE_BUTTON_UNKNOWN = "com.example.bluetooth.le.PEBBLE_UNKNOWN";
public final static UUID UUID_HEART_RATE_MEASUREMENT =
UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
public final static UUID UUID_UART_RX_CHARACTERISTICS =
UUID.fromString(SampleGattAttributes.UART_RX_CHARACTERISTICS);
public final static UUID UUID_UART_TX_CHARACTERISTICS =
UUID.fromString(SampleGattAttributes.UART_TX_CHARACTERISTICS);
public final static UUID UUID_UART_SERVICE =
UUID.fromString(SampleGattAttributes.UART_SERVICE);
public final static UUID PEBBLE_APP_UUID = UUID.fromString("5cd90c76-2457-4ac0-a869-22c66c9f7321");
private BroadcastReceiver mPebbleReceiever;
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// This is special handling for the Heart Rate Measurement profile. Data parsing is
// carried out as per profile specifications:
// http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
} else {
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
}
}
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// After using a given device, you should make sure that BluetoothGatt.close() is called
// such that resources are cleaned up properly. In this particular example, close() is
// invoked when the UI is disconnected from the Service.
close();
return super.onUnbind(intent);
}
private final IBinder mBinder = new LocalBinder();
/**
* Initializes a reference to the local Bluetooth adapter.
*
* @return Return true if the initialization is successful.
*/
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
public void startPebble()
{
if(mPebbleReceiever != null)
{
unregisterReceiver(mPebbleReceiever);
mPebbleReceiever = null;
}
// Launching my app
PebbleKit.startAppOnPebble(getApplicationContext(), BluetoothLeService.PEBBLE_APP_UUID);
mPebbleReceiever = PebbleKit.registerReceivedDataHandler(this, new PebbleKit.PebbleDataReceiver(BluetoothLeService.PEBBLE_APP_UUID) {
@Override
public void receiveData(final Context context, final int transactionId, final PebbleDictionary data) {
Log.i(this.getClass().getSimpleName(), "Received value");
long var = -1;
if(data.contains(0))
{
var = data.getUnsignedIntegerAsLong(0);
}
switch ((int) var)
{
case 0:
broadcastUpdate(PEBBLE_BUTTON_MIDDLE);
break;
case 1:
broadcastUpdate(PEBBLE_BUTTON_UP);
break;
case 2:
broadcastUpdate(PEBBLE_BUTTON_DOWN);
break;
default:
broadcastUpdate(PEBBLE_BUTTON_UNKNOWN);
break;
}
PebbleKit.sendAckToPebble(getApplicationContext(), transactionId);
byte base = 0x65;
byte byteToSend = (byte) (base + var);
sendByteToBLE(byteToSend);
}
});
sendPebbleStateUpdate();
}
private void sendPebbleStateUpdate()
{
if(PebbleKit.isWatchConnected(this))
{
broadcastUpdate(PEBBLE_CONNECTED);
}
else {
broadcastUpdate(PEBBLE_DISCONNECTED);
}
}
private void sendByteToBLE(byte b)
{
BluetoothGattCharacteristic charactersistic = getTxCharacteristics();
if(charactersistic != null) {
sendByte(charactersistic, b);
}
}
private BluetoothGattCharacteristic getTxCharacteristics()
{
BluetoothGattService uartService = getUArtService();
if(uartService != null) {
List<BluetoothGattCharacteristic> characteristics = uartService.getCharacteristics();
for (BluetoothGattCharacteristic characteristic : characteristics) {
UUID uuid = characteristic.getUuid();
if (UUID_UART_TX_CHARACTERISTICS.equals(uuid)) {
return characteristic;
}
}
}
return null;
}
private BluetoothGattService getUArtService()
{
List<android.bluetooth.BluetoothGattService> services = mBluetoothGatt.getServices();
for(BluetoothGattService service : services)
{
UUID uuid = service.getUuid();
if(UUID_UART_SERVICE.equals(uuid))
{
return service;
}
}
return null;
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* @param address The device address of the destination device.
*
* @return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
//connect to pebble
this.startPebble();
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure resources are
* released properly.
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
if(mPebbleReceiever != null)
{
unregisterReceiver(mPebbleReceiever);
mPebbleReceiever = null;
}
}
/**
* Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
* asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
* callback.
*
* @param characteristic The characteristic to read from.
*/
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
/**
* Enables or disables notification on a give characteristic.
*
* @param characteristic Characteristic to act on.
* @param enabled If true, enable notification. False otherwise.
*/
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
// This is specific to Heart Rate Measurement.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())
|| UUID_UART_RX_CHARACTERISTICS.equals(characteristic.getUuid())
)
{
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
public void sendAByte(BluetoothGattCharacteristic characteristic)
{
byte b = 0x65;
sendByte(characteristic, b);
}
public void sendByte(BluetoothGattCharacteristic characteristic, byte b)
{
if(characteristic != null)
{
byte[] bytesToSend = {b}; //e
characteristic.setValue(bytesToSend);
mBluetoothGatt.writeCharacteristic(characteristic);
}
}
/**
* Retrieves a list of supported GATT services on the connected device. This should be
* invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
*
* @return A {@code List} of supported services.
*/
public List<BluetoothGattService> getSupportedGattServices() {
if (mBluetoothGatt == null) return null;
return mBluetoothGatt.getServices();
}
}