diff --git a/BleConnect.java b/BleConnect.java new file mode 100644 index 0000000..909857c --- /dev/null +++ b/BleConnect.java @@ -0,0 +1,267 @@ +package st.wow.git.ble; + +import java.lang.Runnable; +import java.lang.String; +import java.util.List; +import java.util.UUID; +import android.util.Log; +import java.lang.Class; +import java.lang.ClassLoader; +import java.lang.reflect.Constructor; +import android.app.Activity; +import android.app.Fragment; +import android.os.Handler; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.Manifest; + +public class BleConnect extends Fragment { + BluetoothManager manager; + BluetoothAdapter adapter; + Handler handler; + boolean wantScan = false; + + final int PERMISSION_REQUEST = 1; + + final int REQUEST_ENABLE_BT = 1; + + public BleConnect() { + Log.d("gio", "BleConnect()"); + } + + @Override public void onAttach(Context ctx) { + super.onAttach(ctx); + Log.d("gio", "BleConnect: onAttach()"); + ctx.registerReceiver(receiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); + manager = (BluetoothManager) ctx.getSystemService(ctx.BLUETOOTH_SERVICE); + adapter = manager.getAdapter(); + handler = new Handler(ctx.getMainLooper()); + if (!enabled()) { + Log.d("gio", "BleConnect: enabling adapter"); + Intent enableBtIntent = new Intent(adapter.ACTION_REQUEST_ENABLE); + handler.post(new Runnable() { + public void run() { + startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); + } + }); + } else { + Log.d("gio", "BleConnect: adapter is enabled"); + } + if (ctx.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST); + } + installComplete(this); + } + + public boolean enabled() { + return (adapter != null && adapter.isEnabled()); + } + + private final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.d("gio", "Received broadcast"); + if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + updateState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)); + } + } + }; + + @Override + public void onDestroy() { + Log.d("gio","onDestroy()"); + stopScan(); + getContext().unregisterReceiver(receiver); + super.onDestroy(); + } + + private final BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() { + public void onLeScan(final BluetoothDevice dev, int rssi, byte[] scanRecord) { + Log.d("gio","onLeScan(): " + dev.getName()); + onScan(dev.getName(), dev.getAddress(), rssi, dev); + } + }; + + public void scan() { + if (!enabled() || getContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + Log.d("gio","BleConnect: scan() not enabled or no permissions, wantScan = true"); + wantScan = true; + return; + } + Log.d("gio","BleConnect: scan() starting scan"); + wantScan = false; + handler.post(new Runnable() { + public void run() { + adapter.startLeScan(scanCallback); + } + }); + } + + public void stopScan() { + if (!enabled()) { + return; + } + Log.d("gio", "Stop scan"); + handler.post(new Runnable() { + public void run() { + adapter.stopLeScan(scanCallback); + } + }); + } + + private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { +@Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + switch (newState) { + case BluetoothProfile.STATE_CONNECTED: { + BluetoothDevice device = gatt.getDevice(); + Log.d("gio", "Connected"); + String addr = device.getAddress(); + Log.d("gio", "Address = " + addr); + onConnect(gatt, device.getAddress()); + break; + } + case BluetoothProfile.STATE_DISCONNECTED: { + Log.d("gio", "Disconnected"); + break; + } + default: { + Log.d("gio", "onConnectionStateChange: unknown state"); + break; + } + } + } + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + for (BluetoothGattService serv : gatt.getServices()) { + onDiscoverService(gatt.getDevice().getAddress(), serv.getUuid().toString(), serv); + } + } + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic chr) { + byte[] v = chr.getValue(); + characteristicChanged(gatt.getDevice().getAddress(), chr.getUuid().toString(), chr, v, v.length); + } + }; + + public void connect(BluetoothDevice dev) { + if (dev == null) { + return; + } + Log.d("gio","BleConnect: connect"); + + handler.post(new Runnable() { + public void run() { + dev.connectGatt(getContext(), false, gattCallback, BluetoothDevice.TRANSPORT_LE); + } + }); + } + + public void disconnect(BluetoothGatt gatt) { + if (gatt == null) { + return; + } + Log.d("gio","BleConnect: disconnect"); + handler.post(new Runnable() { + public void run() { + gatt.disconnect(); + } + }); + } + + public void discoverServices(BluetoothGatt gatt) { + if (gatt == null) { + return; + } + handler.post(new Runnable() { + public void run() { + gatt.discoverServices(); + } + }); + } + + public void discoverCharacteristics(BluetoothGatt gatt, BluetoothGattService serv) { + Log.d("gio","BleConnect: discoverCharacteristics()"); + if (gatt == null) { + Log.d("gio","BleConnect: gatt == null"); + return; + } + List chrs = serv.getCharacteristics(); + if (chrs.isEmpty()) { + Log.d("gio", "BleConnect: no characteristics found!"); + } + for (BluetoothGattCharacteristic chr : chrs) { + Log.d("gio","BleConnect: -- " + chr.getUuid().toString()); + onDiscoverCharacteristic(gatt.getDevice().getAddress(), serv.getUuid().toString(), serv, chr.getUuid().toString(), chr); + } + } + + public void setCharacteristicNotification(BluetoothGatt gatt, BluetoothGattCharacteristic chr) { + gatt.setCharacteristicNotification(chr, true); + + BluetoothGattDescriptor descriptor = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); // Client Characteristic Config + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + gatt.writeDescriptor(descriptor); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d("gio", "BleConnect: onActivityResult()"); + if (requestCode == REQUEST_ENABLE_BT) { + Log.d("gio", "BleConnect: onActivityResult() REQUEST_ENABLE_BT"); + switch (resultCode) { + case Activity.RESULT_OK: { + Log.d("gio", "BleConnect: onActivityResult() -- OK"); + if (wantScan) { + scan(); + } + break; + } + case Activity.RESULT_CANCELED: { + Log.d("gio", "BleConnect: onActivityResult() -- Cancelled"); + break; + } + } + } + } + + @Override + public void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults) { + Log.d("gio", "BleConnect: onRequestPermissionsResult"); + if (requestCode == PERMISSION_REQUEST) { + boolean granted = true; + for (int x : grantResults) { + if (x == PackageManager.PERMISSION_DENIED) { + granted = false; + break; + } + } + if (!granted) { + Log.d("gio", "BleConnect: permissions not granted"); + return; + } + Log.d("gio", "BleConnect: permissions granted"); + if (wantScan) { + scan(); + } + } + } + + static private native void installComplete(BleConnect p); + static private native void updateState(int s); + static private native void onScan(String name, String id, int rssi, BluetoothDevice dev); + static private native void onConnect(BluetoothGatt gatt, String id); + static private native void onDiscoverService(String id, String uuid, BluetoothGattService serv); + static private native void onDiscoverCharacteristic(String id, String suuid, BluetoothGattService serv, String cuuid, BluetoothGattCharacteristic chr); + static private native void characteristicChanged(String id, String cuuid, BluetoothGattCharacteristic chr, byte[] value, int length); +} + diff --git a/BlessedConnect.java b/BlessedConnect.java index bbd1ed6..79f9e22 100644 --- a/BlessedConnect.java +++ b/BlessedConnect.java @@ -18,6 +18,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.Manifest; +import android.os.Handler; +import android.os.HandlerThread; import android.util.Log; import com.welie.blessed.BluetoothCentral; @@ -43,7 +45,11 @@ public class BlessedConnect extends Fragment { if (ctx.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST); } - central = new BluetoothCentral(ctx, centralCallback, null); + Log.d("gio", "BlessedConnect: creating HandlerThread"); + HandlerThread thread = new HandlerThread("Bluetooth Thread"); +thread.start(); + Handler handler = new Handler(thread.getLooper()); + central = new BluetoothCentral(ctx, centralCallback, handler); installComplete(this); } @@ -61,8 +67,9 @@ public class BlessedConnect extends Fragment { Context ctx = getContext(); //The Blessed library does not expose this functionality, so //we need to get our own BluetoothAdapter and check its status. - BluetoothAdapter adapter = ((BluetoothManager)ctx.getSystemService(ctx.BLUETOOTH_SERVICE)).getAdapter(); - return (adapter != null && adapter.isEnabled()); + //BluetoothAdapter adapter = ((BluetoothManager)ctx.getSystemService(ctx.BLUETOOTH_SERVICE)).getAdapter(); + //return (adapter != null && adapter.isEnabled()); + return true; } @Override @@ -74,14 +81,12 @@ public class BlessedConnect extends Fragment { } public void scan() { - if (enabled()) { - central.scanForPeripherals(); - } else { - wantScan = true; - } + Log.d("gio", "BlessedConnect: Scan"); + central.scanForPeripherals(); } public void stopScan() { + Log.d("gio", "BlessedConnect: Stop scan"); central.stopScan(); } @@ -119,6 +124,7 @@ public class BlessedConnect extends Fragment { @Override public void onScanFailed(int errorCode) { Log.d("gio", "BlessedConnect: Scan failed"); + } }; @@ -136,15 +142,20 @@ public class BlessedConnect extends Fragment { public void onBondLost(BluetoothPeripheral peripheral) { Log.d("gio", "BlessedConnect: onBondLost()"); } - public void onCharacteristicUpdate(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic, int status) { + public void onCharacteristicUpdate(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic) { Log.d("gio", "BlessedConnect: onCharacteristicUpdate()"); characteristicChanged(peripheral.getAddress(), characteristic.getUuid().toString(), characteristic, value, value.length); } public void onCharacteristicWrite(BluetoothPeripheral peripheral, byte[] value, android.bluetooth.BluetoothGattCharacteristic characteristic, int status) { Log.d("gio", "onCharacteristicWrite(): " + characteristic.getUuid().toString()); } - public void onDescriptorRead(BluetoothPeripheral peripheral, byte[] value, android.bluetooth.BluetoothGattDescriptor descriptor, int status) { + public void onDescriptorRead(BluetoothPeripheral peripheral, byte[] value, android.bluetooth.BluetoothGattDescriptor descriptor) { Log.d("gio", "onDescriptorRead()"); + if (value == BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) { + Log.d("gio", "onDescriptorRead(): " + descriptor.getUuid().toString() + ": notification enabled"); + } else { + Log.d("gio", "onDescriptorRead(): " + descriptor.getUuid().toString() + ": notification not enabled"); + } } public void onDescriptorWrite(BluetoothPeripheral peripheral, byte[] value, android.bluetooth.BluetoothGattDescriptor descriptor, int status) { Log.d("gio", "onDescriptorWrite(): " + descriptor.getUuid().toString()); @@ -152,13 +163,16 @@ public class BlessedConnect extends Fragment { public void onMtuChanged(BluetoothPeripheral peripheral, int mtu, int status) { Log.d("gio", "onMtuChanged()"); } - public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + public void onReadRemoteRssi(BluetoothPeripheral peripheral, int rssi, int status) { Log.d("gio", "onReadRemoteRssi"); } public void onNotificationStateUpdate(BluetoothPeripheral peripheral, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { if(peripheral.isNotifying(characteristic)) { Log.i("gio", String.format("BlessedConnect: Notify set to 'on' for %s", characteristic.getUuid())); + BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); + Log.d("gio", "Reading descriptor"); + peripheral.readDescriptor(descriptor); } else { Log.i("gio", String.format("BlessedConnect: Notify set to 'off' for %s", characteristic.getUuid())); } @@ -199,9 +213,6 @@ public class BlessedConnect extends Fragment { } public void discoverCharacteristics(BluetoothPeripheral peripheral, BluetoothGattService serv) { - if (peripheral == null || serv == null) { - return; - } List chrs = serv.getCharacteristics(); for (BluetoothGattCharacteristic chr : chrs) { onDiscoverCharacteristic(peripheral.getAddress(), serv.getUuid().toString(), serv, chr.getUuid().toString(), chr); @@ -209,15 +220,9 @@ public class BlessedConnect extends Fragment { } public void readCharacteristic(BluetoothPeripheral peripheral, BluetoothGattCharacteristic chr) { - if (peripheral == null || chr == null) { - return; - } peripheral.readCharacteristic(chr); } public boolean setCharacteristicNotification(BluetoothPeripheral peripheral, BluetoothGattCharacteristic chr) { - if (peripheral == null || chr == null) { - return false; - } return peripheral.setNotify(chr, true); } @@ -273,3 +278,4 @@ public class BlessedConnect extends Fragment { static private native void onDiscoverCharacteristic(String id, String suuid, BluetoothGattService serv, String cuuid, BluetoothGattCharacteristic chr); static private native void characteristicChanged(String id, String cuuid, BluetoothGattCharacteristic chr, byte[] value, int length); } + diff --git a/ble_android.go b/ble_android.go index ca39f74..d9db660 100644 --- a/ble_android.go +++ b/ble_android.go @@ -24,7 +24,7 @@ import "C" // Types required for ble.go type bleHandle struct { - BlessedConnect C.jobject + BleConnect C.jobject state int } @@ -131,7 +131,7 @@ func (b *BLE) readyToScan() bool { connect() var ret bool runInJVM(func(env *C.JNIEnv) { - ret = C.enabled(env, b.handle.BlessedConnect) == C.JNI_TRUE + ret = C.enabled(env, b.handle.BleConnect) == C.JNI_TRUE }) return ret } @@ -140,7 +140,7 @@ func (b *BLE) readyToScan() bool { func (b *BLE) scan() { connect() runInJVM(func(env *C.JNIEnv) { - C.scan(env, b.handle.BlessedConnect); + C.scan(env, b.handle.BleConnect); }) } @@ -148,14 +148,14 @@ func (b *BLE) scan() { func (b *BLE) stopScan() { connect() runInJVM(func(env *C.JNIEnv) { - C.stopScan(env, b.handle.BlessedConnect); + C.stopScan(env, b.handle.BleConnect); }) } //connectPeripheral attempts to connect to a Peripheral func (b *BLE) connectPeripheral(x Peripheral) { runInJVM(func(env *C.JNIEnv) { - C.connect(env, b.handle.BlessedConnect, x.peripheral) + C.connect(env, b.handle.BleConnect, x.peripheral) }) } @@ -163,7 +163,7 @@ func (b *BLE) connectPeripheral(x Peripheral) { func (b *BLE) cancelConnection(p Peripheral) { connect() runInJVM(func(env *C.JNIEnv) { - C.disconnect(env, b.handle.BlessedConnect, p.peripheral) + C.disconnect(env, b.handle.BleConnect, p.peripheral) }) } @@ -177,12 +177,12 @@ func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) { //DiscoverServices asks a Peripheral for its Services func (p Peripheral) DiscoverServices() { //launch a goroutine because this function calls back directly - //from the same thread (the underlying Java call is synchronous) + //from the same thread (underlying Java call is synchronous) go func() { connect() log.Printf("discovering services") runInJVM(func(env *C.JNIEnv) { - C.discoverServices(env, gBLE.handle.BlessedConnect, p.peripheral) + C.discoverServices(env, gBLE.handle.BleConnect, p.peripheral) }) }() } @@ -191,23 +191,23 @@ func (p Peripheral) DiscoverServices() { //to a Service func (p Peripheral) DiscoverCharacteristics(serv Service) { //launch a goroutine because this function calls back directly - //from the same thread (the underlying Java call is synchronous) + //from the same thread (underlying Java call is synchronous) go func() { connect() log.Printf("discovering characteristics") runInJVM(func(env *C.JNIEnv) { - C.discoverCharacteristics(env, gBLE.handle.BlessedConnect, p.peripheral, serv.service) + C.discoverCharacteristics(env, gBLE.handle.BleConnect, p.peripheral, serv.service) }) + log.Printf("discovering characteristics done") }() } //SetNotifyValue subscribes to a characteristic func (p Peripheral) SetNotifyValue(c Characteristic) { runInJVM(func(env *C.JNIEnv) { - result := (C.setCharacteristicNotification(env, gBLE.handle.BlessedConnect, p.peripheral, c.characteristic) == C.JNI_TRUE) - if (!result) { - log.Printf("setCharacteristicNotification: %s failed", c.UUID) - } + C.readCharacteristic(env, gBLE.handle.BleConnect, p.peripheral, c.characteristic) + //result := (C.setCharacteristicNotification(env, gBLE.handle.BleConnect, p.peripheral, c.characteristic) == C.JNI_TRUE) + //log.Printf("setCharacteristicNotification: %s = %t", c.UUID, result) }) } @@ -236,10 +236,10 @@ func Java_st_wow_git_ble_BlessedConnect_installComplete(env *C.JNIEnv, class C.j if (b == 0) { log.Printf("BlessedConnect object is nil!") } - gBLE.handle.BlessedConnect = (C.NewGlobalRef)(env,b) + gBLE.handle.BleConnect = (C.NewGlobalRef)(env,b) h := app.PlatformHandle() setJVM(h.JVM) - if C.enabled(env, gBLE.handle.BlessedConnect) == C.JNI_TRUE { + if C.enabled(env, gBLE.handle.BleConnect) == C.JNI_TRUE { gBLE.handle.state = STATE_ON gBLE.ready = true gBLE.events <- UpdateStateEvent{State: gBLE.stringState()} @@ -354,6 +354,8 @@ func goOnDiscoverCharacteristic(cid, csuuid *C.char, serv C.jobject, ccuuid *C.c func goOnCharacteristicChanged(cid, ccuuid *C.char, char C.jobject, cvalue *C.char, length C.jint) { id := C.GoString(cid) cuuid := C.GoString(ccuuid) + log.Printf("goOnCharacteristicChanged: %s", cuuid) + log.Printf("goOnCharacteristicChanged: length = %d", length) peripheral := gBLE.retrievePeripheral(id) diff --git a/blessed-full.jar b/blessed-full.jar index e6cd12a..cb9affa 100644 Binary files a/blessed-full.jar and b/blessed-full.jar differ