Compare commits

...

3 Commits

Author SHA1 Message Date
321b94a486 Working Android implementation. 2019-11-27 10:44:37 -05:00
99bcd1f4fb Add discoverCharacteristics() for Android. 2019-11-22 23:33:45 -05:00
86e724cc8c Partial android implementation, including Enable(), readyToScan(),
scan(), stopScan(), connectPeripheral(), cancelConnection(), and
DiscoverServices().
2019-11-22 16:15:02 -05:00
14 changed files with 770 additions and 302 deletions

1
.gitignore vendored
View File

@ -0,0 +1 @@
Ble.jar

239
BleConnect.java Normal file
View File

@ -0,0 +1,239 @@
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;
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);
Log.d("gio", "BleUtil Enable: adapter");
adapter = manager.getAdapter();
handler = new Handler(ctx.getMainLooper());
if (!enabled()) {
Log.d("gio", "BleConnect: enabling adapter");
Intent enableBtIntent = new Intent(adapter.ACTION_REQUEST_ENABLE);
Log.d("gio", "BleConnect: handler.post");
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()) {
return;
}
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<BluetoothGattCharacteristic> 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");
break;
}
case Activity.RESULT_CANCELED: {
Log.d("gio", "BleConnect: onActivityResult() -- Cancelled");
break;
}
}
}
}
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);
}

Binary file not shown.

View File

@ -1,35 +0,0 @@
package st.wow.git.ble;
import java.lang.Runnable;
import android.util.Log;
import java.lang.Class;
import java.lang.ClassLoader;
import java.lang.reflect.Constructor;
import android.os.Handler;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
public class BleUtil {
public void Enable(Context ctx) {
Log.d("gio", "BleUtil Enable: manager");
BluetoothManager manager = (BluetoothManager) ctx.getSystemService(ctx.BLUETOOTH_SERVICE);
Log.d("gio", "BleUtil Enable: adapter");
BluetoothAdapter ad = (BluetoothAdapter) manager.getAdapter();
if (ad == null || !ad.isEnabled()) {
Log.d("gio", "BleUtil Enable: handler");
Handler handler = new Handler(ctx.getMainLooper());
//BleEnabler en = new BleEnabler(ctx, ad);
Log.d("gio", "BleUtil Enable: Intent");
Intent enableBtIntent = new Intent(ad.ACTION_REQUEST_ENABLE);
Log.d("gio", "BleUtil Enable: handler.post");
handler.post(new Runnable() {
public void run() {
ctx.startActivity(enableBtIntent);
}
});
}
}
}

4
ble.go
View File

@ -2,6 +2,7 @@ package ble
import ( import (
"fmt" "fmt"
"log"
"sync" "sync"
"time" "time"
@ -152,6 +153,7 @@ func (b *BLE) Scan() {
b.Lock() b.Lock()
defer b.Unlock() defer b.Unlock()
if b.readyToScan() { if b.readyToScan() {
log.Printf("ready to scan")
b.peripherals.Lock() b.peripherals.Lock()
b.peripherals.items = b.peripherals.items[:0] b.peripherals.items = b.peripherals.items[:0]
b.peripherals.Unlock() b.peripherals.Unlock()
@ -218,7 +220,7 @@ func connectTracker(b *BLE, x ConnectionListItem) bool {
} }
go func() { go func() {
tick := time.NewTicker(time.Second * 5) tick := time.NewTicker(time.Second * 60)
select { select {
case <-b.connections.close: case <-b.connections.close:
fmt.Printf("connectTracker(): Closing connection to %s\n", x.p.Name) fmt.Printf("connectTracker(): Closing connection to %s\n", x.p.Name)

View File

@ -1,146 +1,415 @@
//go:generate mkdir -p classes //go:generate mkdir -p classes
//go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar -d classes BleUtil.java //go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar -d classes BleConnect.java
//go:generate jar cf BleUtil.jar -C classes . //go:generate jar cf Ble.jar -C classes .
//go:generate rm -rf classes //go:generate rm -rf classes
package ble package ble
import ( import (
"log" "log"
"sync"
"unsafe"
"gioui.org/app" "gioui.org/app"
_ "gioui.org/app/permission/bluetooth_le" _ "gioui.org/app/permission/bluetooth"
"git.wow.st/gmp/ble/gatt"
) )
/* /*
#include <jni.h> #include "jni_android.h"
*/ */
import "C" import "C"
// Types required for ble.go // Types required for ble.go
type bleState string
type bleHandle struct { type bleHandle struct {
adapter C.jobject BleConnect C.jobject
class C.jclass state int
} }
type bleState string
type Peripheral struct { type Peripheral struct {
Name string Name string
RSSI int RSSI int
Identifier string Identifier string
device C.jobject
gatt C.jobject
} }
type Service string type Service struct {
type Characteristic string UUID string
service C.jobject
}
type Characteristic struct {
UUID string
characteristic C.jobject
}
// Internal global variables // Internal global variables
var jvmContext uintptr var (
gBLE *BLE // FIXME: move to lookup tables as in ble_darwin.go?
// FIXME: lifecycle, this may need to fire again? Or can I depend
// on my Fragment re-starting? Or call setRetained(true) from the
// fragment so we don't have to re-discover our BluetoothGatt and
// BluetoothDevice objects?
installCompleteOnce sync.Once
waitch chan struct{}
)
const (
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html
// FIXME: create a type for these?
STATE_OFF int = 10
STATE_TURNING_ON = 11
STATE_ON = 12
STATE_TURNING_OFF = 13
)
func init() {
waitch = make(chan struct{})
}
func connect() {
<-waitch
}
// Functions required by API // Functions required by API
//Init needs to be called before the BLE library can be used. On Android we
//need to set up the JVM by calling setJVM in jni_android.go.
func Init() {
log.Print("ble.Init()")
h := app.PlatformHandle()
jvmContext = h.Context
setJVM(h.JVM, h.Context)
}
//peripheralLookup returns a pointer to a BLE struct related to the given //peripheralLookup returns a pointer to a BLE struct related to the given
//Peripheral. //Peripheral.
func peripheralLookup(p Peripheral) *BLE { func peripheralLookup(p Peripheral) *BLE {
return &BLE{} return gBLE
} }
//newPeripheral creates a new Peripheral struct //newPeripheral creates a new Peripheral struct
func newPeripheral() Peripheral { // FIXME: check what happens with "incomplete" Peripherals that have only
return Peripheral{} // an Identifier set. Do the other functions on Peripherals do something
// sensible?
func newPeripheral(name, id string, rssi int, dev C.jobject) Peripheral {
return Peripheral{
Name: name,
RSSI: rssi,
Identifier: id,
device: dev,
}
} }
func (p Peripheral) IsIncomplete() bool { func (p Peripheral) IsIncomplete() bool {
return false if p.device == 0 {
return true
} else {
return false
}
} }
// FIXME: should I call NewGlobalRef() here?
func (p Peripheral) Retain() { func (p Peripheral) Retain() {
} }
//stringState returns a string version of the BLE state //stringState returns a string version of the BLE state
func (b *BLE) stringState() string { func (b *BLE) stringState() string {
return "" switch b.handle.state {
case STATE_OFF:
return "powered off"
case STATE_TURNING_ON:
return "turning on"
case STATE_ON:
return "powered on"
case STATE_TURNING_OFF:
return "turning off"
default:
return "no state"
}
} }
//readyToScan returns true if the hardware is ready to initiate a scan //readyToScan returns true if the hardware is ready to initiate a scan
func (b *BLE) readyToScan() bool { func (b *BLE) readyToScan() bool {
return true connect()
var ret bool
runInJVM(func(env *C.JNIEnv) {
ret = C.enabled(env, b.handle.BleConnect) == C.JNI_TRUE
})
return ret
} }
//scan puts the BLE hardware into scanning mode //scan puts the BLE hardware into scanning mode
func (b *BLE) scan() { func (b *BLE) scan() {
connect()
runInJVM(func(env *C.JNIEnv) {
C.scan(env, b.handle.BleConnect);
})
} }
//stopScan stops a scan in progress //stopScan stops a scan in progress
func (b *BLE) stopScan() { func (b *BLE) stopScan() {
connect()
runInJVM(func(env *C.JNIEnv) {
C.stopScan(env, b.handle.BleConnect);
})
} }
//connectPeripheral attempts to connect to a Peripheral //connectPeripheral attempts to connect to a Peripheral
func (b *BLE) connectPeripheral(x Peripheral) { func (b *BLE) connectPeripheral(x Peripheral) {
connect()
runInJVM(func(env *C.JNIEnv) {
C.connect(env, b.handle.BleConnect, x.device)
})
} }
//cancelConnection cancels an in-progress connection attempt //cancelConnection cancels an in-progress connection attempt
func (b *BLE) cancelConnection(p Peripheral) { func (b *BLE) cancelConnection(p Peripheral) {
connect()
runInJVM(func(env *C.JNIEnv) {
C.disconnect(env, b.handle.BleConnect, p.gatt)
})
} }
//knownPeripheral returns a Peripheral that is known to the system without //knownPeripheral returns a Peripheral that is known to the system without
//scanning //scanning
//Not implemented for Android
func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) { func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) {
return Peripheral{}, false return Peripheral{}, false
} }
//DiscoverServices asks a Peripheral for its Services //DiscoverServices asks a Peripheral for its Services
func (x Peripheral) DiscoverServices() { func (p Peripheral) DiscoverServices() {
connect()
log.Printf("discovering services")
runInJVM(func(env *C.JNIEnv) {
C.discoverServices(env, gBLE.handle.BleConnect, p.gatt)
})
} }
//DiscoverCharacteristics asks a Peripheral for the Characteristics related //DiscoverCharacteristics asks a Peripheral for the Characteristics related
//to a Service //to a Service
func (p Peripheral) DiscoverCharacteristics(serv Service) { func (p Peripheral) DiscoverCharacteristics(serv Service) {
//launch a goroutine because this function calls back directly
//from the same thread.
go func() {
connect()
log.Printf("discovering characteristics")
runInJVM(func(env *C.JNIEnv) {
C.discoverCharacteristics(env, gBLE.handle.BleConnect, p.gatt, serv.service)
})
log.Printf("discovering characteristics done")
}()
} }
//SetNotifyValue subscribes to a characteristic //SetNotifyValue subscribes to a characteristic
func (p Peripheral) SetNotifyValue(c Characteristic) { func (p Peripheral) SetNotifyValue(c Characteristic) {
runInJVM(func(env *C.JNIEnv) {
log.Printf("setCharacteristicNotification: %s", c.UUID)
C.setCharacteristicNotification(env, gBLE.handle.BleConnect, p.gatt, c.characteristic)
})
} }
//NewBLE returns a pointer to a BLE struct after setting up the OS //NewBLE returns a pointer to a BLE struct after setting up the OS
//Bluetooth API. //Bluetooth API.
func NewBLE() *BLE { func NewBLE() *BLE {
ps := Peripherals{items: make([]PeripheralListItem, 0)} ps := Peripherals{items: make([]PeripheralListItem, 0)}
h := bleHandle{} gBLE = &BLE{
RunInJVM(func(env *JNIEnv) {
h.adapter = GetBluetoothAdapter(env, jvmContext)
h.class = JniGetObjectClass(env, h.adapter)
})
ret := &BLE{
events: make(chan interface{}), events: make(chan interface{}),
peripherals: ps, peripherals: ps,
handle: h,
} }
return gBLE
//check if Bluetooth is enabled and if so, send an UpdateStateEvent
go func() {
RunInJVM(func(env *JNIEnv) {
mid := JniGetMethodID(env, h.class, "isEnabled", "()Z")
en := JniCallBooleanMethod(env, h.adapter, mid)
if en {
log.Print("It's enabled!")
ret.events <-UpdateStateEvent{"powered on"}
} else {
log.Print("It's not enabled")
}
})
}()
return ret
} }
//Enable
func (b *BLE) Enable(w *app.Window) {
log.Printf("ble.Enable()")
w.RegisterFragment("st/wow/git/ble/BleConnect")
}
// Go callbacks from Java
//export Java_st_wow_git_ble_BleConnect_installComplete
func Java_st_wow_git_ble_BleConnect_installComplete(env *C.JNIEnv, class C.jclass, b C.jobject) {
log.Printf("installComplete()")
if (b == 0) {
log.Printf("BleConnect object is nil!")
}
gBLE.handle.BleConnect = (C.NewGlobalRef)(env,b)
h := app.PlatformHandle()
setJVM(h.JVM)
if C.enabled(env, gBLE.handle.BleConnect) == C.JNI_TRUE {
gBLE.handle.state = STATE_ON
gBLE.ready = true
gBLE.events <- UpdateStateEvent{State: gBLE.stringState()}
}
installCompleteOnce.Do(func() {
close(waitch)
})
log.Printf("installComplete() returning")
}
//export Java_st_wow_git_ble_BleConnect_updateState
func Java_st_wow_git_ble_BleConnect_updateState(env *C.JNIEnv, class C.jclass, s C.jint) {
log.Printf("UpdateState: %d", s)
gBLE.handle.state = (int)(s)
if gBLE.handle.state == STATE_ON {
gBLE.ready = true
}
gBLE.events <- UpdateStateEvent{State: gBLE.stringState()}
}
//export goOnScan
func goOnScan(cname, cid *C.char, rssi C.int, dev C.jobject) {
name := C.GoString(cname);
id := C.GoString(cid);
if name == "" {
return
}
peripheral := newPeripheral(name, id, (int)(rssi), dev)
if ok := gBLE.peripherals.Add(peripheral); ok {
gBLE.events <- DiscoverPeripheralEvent{Peripheral: peripheral}
}
}
//export goOnConnect
func goOnConnect(gatt C.jobject, cid *C.char) {
id := C.GoString(cid)
var peripheral Peripheral
found := false
gBLE.peripherals.Lock()
for n, item := range gBLE.peripherals.items {
if item.p.Identifier == id {
peripheral = item.p
peripheral.gatt = gatt
gBLE.peripherals.items[n].p = peripheral
found = true
break
}
}
gBLE.peripherals.Unlock()
if !found {
log.Printf("Go: peripheral not found!")
}
if peripheral.gatt == 0 {
log.Printf("goOnConnect(): gatt == null")
}
gBLE.connections.UpdateState(peripheral, "connected")
gBLE.events <- ConnectEvent{peripheral}
log.Printf("Go: goOnConnect returning\n")
}
//export goOnDiscoverService
func goOnDiscoverService(cid, cuuid *C.char, serv C.jobject) {
id := C.GoString(cid)
uuid := C.GoString(cuuid)
var peripheral Peripheral
found := false
gBLE.peripherals.Lock()
for _, item := range gBLE.peripherals.items {
if item.p.Identifier == id {
peripheral = item.p
found = true
break
}
}
gBLE.peripherals.Unlock()
if !found {
log.Printf("Go: peripheral not found!")
}
service := Service{
UUID: uuid,
service: serv,
}
gBLE.events <- DiscoverServiceEvent{
Peripheral: peripheral,
Gatt: gatt.Service{uuid},
Service: service,
}
}
//export goOnDiscoverCharacteristic
func goOnDiscoverCharacteristic(cid, csuuid *C.char, serv C.jobject, ccuuid *C.char, char C.jobject) {
id := C.GoString(cid)
suuid := C.GoString(csuuid)
cuuid := C.GoString(ccuuid)
log.Printf("goOnDiscoverCharacteristic: %s", cuuid)
var peripheral Peripheral
found := false
gBLE.peripherals.Lock()
for _, item := range gBLE.peripherals.items {
if item.p.Identifier == id {
peripheral = item.p
found = true
break
}
}
gBLE.peripherals.Unlock()
if !found {
log.Printf("Go: peripheral not found!")
}
service := Service{
UUID: suuid,
service: serv,
}
characteristic := Characteristic{
UUID: cuuid,
characteristic: char,
}
gBLE.events <- DiscoverCharacteristicEvent{
Peripheral: peripheral,
Service: service,
Characteristic: characteristic,
Gatt: gatt.Characteristic{cuuid},
}
}
//export goOnCharacteristicChanged
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)
var peripheral Peripheral
found := false
gBLE.peripherals.Lock()
for _, item := range gBLE.peripherals.items {
if item.p.Identifier == id {
peripheral = item.p
found = true
break
}
}
gBLE.peripherals.Unlock()
if !found {
log.Printf("Go: peripheral not found!")
}
characteristic := Characteristic{
UUID: cuuid,
characteristic: char,
}
gBLE.events <- UpdateValueEvent{
Peripheral: peripheral,
Characteristic: characteristic,
Data: C.GoBytes(unsafe.Pointer(cvalue), length),
}
}

View File

@ -8,6 +8,8 @@ import (
"time" "time"
"unsafe" "unsafe"
"gioui.org/app"
"git.wow.st/gmp/ble/gatt" "git.wow.st/gmp/ble/gatt"
"git.wow.st/gmp/ble/ns" "git.wow.st/gmp/ble/ns"
) )
@ -40,12 +42,6 @@ var pcache map[unsafe.Pointer]*Peripheral
// Functions required by API // Functions required by API
//Init needs to be called before the BLE library can be used. No setup needed
//on Darwin.
func Init() {
}
//peripheralLookup returns a pointer to a BLE struct related to the given //peripheralLookup returns a pointer to a BLE struct related to the given
//Peripheral. //Peripheral.
func peripheralLookup(p Peripheral) *BLE { func peripheralLookup(p Peripheral) *BLE {
@ -206,6 +202,10 @@ func NewBLE() *BLE {
return ble return ble
} }
//Enable is not required for Darwin.
func (b *BLE) Enable(w *app.Window) {
}
// Core Bluetooth callback functions // Core Bluetooth callback functions
func didUpdateState(c *ns.CBCentralManager) { func didUpdateState(c *ns.CBCentralManager) {

9
gatt/gatt_android.go Normal file
View File

@ -0,0 +1,9 @@
package gatt
func (s Service) IsHRM() bool {
return len(s.UUID) >= 8 && s.UUID[:8] == "0000180d"
}
func (c Characteristic) IsHRV() bool {
return len(c.UUID) >= 0 && c.UUID[:8] == "00002a37"
}

9
gatt/gatt_darwin.go Normal file
View File

@ -0,0 +1,9 @@
package gatt
func (s Service) IsHRM() bool {
return s.UUID == "180D"
}
func (c Characteristic) IsHRV() bool {
return c.UUID == "2A37"
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module git.wow.st/gmp/ble
go 1.13
require gioui.org v0.0.0-20191126175243-2ca2e5462f16

24
go.sum Normal file
View File

@ -0,0 +1,24 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20191126175243-2ca2e5462f16 h1:p31rtmKm51xpj2QtqGNlljAyHEP1oStU8MDRl2Dv7Gs=
gioui.org v0.0.0-20191126175243-2ca2e5462f16/go.mod h1:KqFFi2Dq5gYA3FJ0sDOt8OBXoMsuxMtE8v2f0JExXAY=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

132
jni_android.c Normal file
View File

@ -0,0 +1,132 @@
#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
#include "_cgo_export.h"
jboolean
enabled(JNIEnv *env, jobject b) {
jclass cls = (*env)->GetObjectClass(env, b);
jmethodID mid = (*env)->GetMethodID(env, cls, "enabled", "()Z");
return (*env)->CallBooleanMethod(env, b, mid);
}
void
scan(JNIEnv *env, jobject b) {
jclass cls = (*env)->GetObjectClass(env, b);
jmethodID mid = (*env)->GetMethodID(env, cls, "scan", "()V");
(*env)->CallVoidMethod(env, b, mid);
}
void
stopScan(JNIEnv *env, jobject b) {
jclass cls = (*env)->GetObjectClass(env, b);
jmethodID mid = (*env)->GetMethodID(env, cls, "stopScan", "()V");
(*env)->CallVoidMethod(env, b, mid);
}
void
connect(JNIEnv *env, jobject b, jobject d) {
jclass cls = (*env)->GetObjectClass(env, b);
jmethodID mid = (*env)->GetMethodID(env, cls, "connect", "(Landroid/bluetooth/BluetoothDevice;)V");
(*env)->CallObjectMethod(env, b, mid, d);
}
void
disconnect(JNIEnv *env, jobject b, jobject g) {
jclass cls = (*env)->GetObjectClass(env, b);
jmethodID mid = (*env)->GetMethodID(env, cls, "disconnect", "(Landroid/bluetooth/BluetoothGatt;)V");
(*env)->CallVoidMethod(env, b, mid, g);
}
void
discoverServices(JNIEnv *env, jobject b, jobject p) {
jclass cls = (*env)->GetObjectClass(env, b);
jmethodID mid = (*env)->GetMethodID(env, cls, "discoverServices", "(Landroid/bluetooth/BluetoothGatt;)V");
(*env)->CallVoidMethod(env, b, mid, p);
}
void
discoverCharacteristics(JNIEnv *env, jobject b, jobject g, jobject s) {
jclass cls = (*env)->GetObjectClass(env, b);
jmethodID mid = (*env)->GetMethodID(env, cls, "discoverCharacteristics", "(Landroid/bluetooth/BluetoothGatt;Landroid/bluetooth/BluetoothGattService;)V");
(*env)->CallVoidMethod(env, b, mid, g, s);
}
void
setCharacteristicNotification(JNIEnv *env, jobject b, jobject g, jobject c) {
jclass cls = (*env)->GetObjectClass(env, b);
jmethodID mid = (*env)->GetMethodID(env, cls, "setCharacteristicNotification", "(Landroid/bluetooth/BluetoothGatt;Landroid/bluetooth/BluetoothGattCharacteristic;)V");
(*env)->CallVoidMethod(env, b, mid, g, c);
}
jint
GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
return (*vm)->GetEnv(vm, (void **)env, version);
}
jint
AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
}
jint
DetachCurrentThread(JavaVM *vm) {
return (*vm)->DetachCurrentThread(vm);
}
jobject
NewGlobalRef(JNIEnv *env, jobject o) {
return (*env)->NewGlobalRef(env, o);
}
void
Java_st_wow_git_ble_BleConnect_onScan(JNIEnv *env, jclass class, jstring jname, jstring jid, jint jrssi, jobject dev) {
const char* name = (*env)->GetStringUTFChars(env, jname, NULL);
const char* id = (*env)->GetStringUTFChars(env, jid, NULL);
jobject gdev = (*env)->NewGlobalRef(env, dev);
goOnScan(name, id, (int)jrssi, gdev);
(*env)->ReleaseStringUTFChars(env, jname, name);
(*env)->ReleaseStringUTFChars(env, jid, id);
}
void
Java_st_wow_git_ble_BleConnect_onConnect(JNIEnv *env, jclass class, jobject gatt, jstring jid) {
const char* id = (*env)->GetStringUTFChars(env, jid, NULL);
jobject ggatt = (*env)->NewGlobalRef(env, gatt);
goOnConnect(ggatt, id);
(*env)->ReleaseStringUTFChars(env, jid, id);
}
void
Java_st_wow_git_ble_BleConnect_onDiscoverService(JNIEnv *env, jclass class, jstring jid, jstring juuid, jobject serv) {
const char* id = (*env)->GetStringUTFChars(env, jid, NULL);
const char* uuid = (*env)->GetStringUTFChars(env, juuid, NULL);
jobject gserv = (*env)->NewGlobalRef(env, serv);
goOnDiscoverService(id, uuid, gserv);
(*env)->ReleaseStringUTFChars(env, jid, id);
(*env)->ReleaseStringUTFChars(env, juuid, uuid);
}
void
Java_st_wow_git_ble_BleConnect_onDiscoverCharacteristic(JNIEnv *env, jclass class, jstring jid, jstring jsuuid, jobject serv, jstring jcuuid, jobject chr) {
const char* id = (*env)->GetStringUTFChars(env, jid, NULL);
const char* suuid = (*env)->GetStringUTFChars(env, jsuuid, NULL);
const char* cuuid = (*env)->GetStringUTFChars(env, jcuuid, NULL);
jobject gchr = (*env)->NewGlobalRef(env, chr);
goOnDiscoverCharacteristic(id, suuid, serv, cuuid, gchr);
(*env)->ReleaseStringUTFChars(env, jid, id);
(*env)->ReleaseStringUTFChars(env, jsuuid, suuid);
(*env)->ReleaseStringUTFChars(env, jcuuid, cuuid);
}
void
Java_st_wow_git_ble_BleConnect_characteristicChanged(JNIEnv *env, jclass class, jstring jid, jstring jcuuid, jobject chr, jbyteArray jvalue, jint len) {
const char* id = (*env)->GetStringUTFChars(env, jid, NULL);
const char* cuuid = (*env)->GetStringUTFChars(env, jcuuid, NULL);
jbyte* value = (*env)->GetByteArrayElements(env, jvalue, NULL);
goOnCharacteristicChanged(id, cuuid, chr, value, len);
(*env)->ReleaseStringUTFChars(env, jid, id);
(*env)->ReleaseStringUTFChars(env, jcuuid, cuuid);
(*env)->ReleaseByteArrayElements(env, jvalue, value, len);
}

View File

@ -3,165 +3,7 @@ package ble
/* /*
#cgo LDFLAGS: -landroid -llog #cgo LDFLAGS: -landroid -llog
#include <stdlib.h> #import "jni_android.h"
#include <jni.h>
#include <android/log.h>
static jobject gClassLoader;
static jmethodID gFindClassMethod;
jclass
GetObjectClass(JNIEnv* env, jobject obj) {
jclass cls = (*env)->GetObjectClass(env, obj);
cls = (*env)->NewGlobalRef(env, cls);
return cls;
}
void
SetLoader(JNIEnv* env, jobject context) {
jclass cclass = (*env)->GetObjectClass(env, context);
jmethodID gcl_id = (*env)->GetMethodID(env, cclass, "getClassLoader", "()Ljava/lang/ClassLoader;");
jobject loader = (*env)->CallObjectMethod(env, context, gcl_id);
gClassLoader = (*env)->NewGlobalRef(env, loader);
jclass lclass = (*env)->GetObjectClass(env, loader);
gFindClassMethod = (*env)->GetMethodID(env, lclass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
}
jclass
FindClass(JNIEnv* env, char* name) {
jstring strClassName = (*env)->NewStringUTF(env, name);
return (*env)->CallObjectMethod(env, gClassLoader, gFindClassMethod, strClassName);
}
void
EnableBluetooth(JNIEnv* env, jobject ctx, jobject ad) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "Finding BleUtil");
jclass cls = FindClass(env, "st/wow/git/ble/BleUtil");
if (cls == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "not found");
return;
}
__android_log_write(ANDROID_LOG_DEBUG, "gio", "getting <init>");
jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
if (mid == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "not found");
return;
}
__android_log_write(ANDROID_LOG_DEBUG, "gio", "calling NewObject");
jobject bleutil = (*env)->NewObject(env, cls, mid);
if (bleutil == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "failed");
return;
}
__android_log_write(ANDROID_LOG_DEBUG, "gio", "Getting Enable()");
mid = (*env)->GetMethodID(env, cls, "Enable", "(Landroid/content/Context;)V");
if (mid == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "failed");
return;
}
__android_log_write(ANDROID_LOG_DEBUG, "gio", "Calling Enable()");
(*env)->CallVoidMethod(env, bleutil, mid, ctx);
}
jobject
GetBluetoothAdapter(JNIEnv* env, jobject ctx) {
jclass cls = (*env)->GetObjectClass(env, ctx);
if (cls == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get context class");
return NULL;
}
jfieldID id = (*env)->GetStaticFieldID(env, cls, "BLUETOOTH_SERVICE", "Ljava/lang/String;");
if (id == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get field ID");
return NULL;
}
jstring btsrv = (jstring)(*env)->GetStaticObjectField(env, cls, id);
if (btsrv == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get static object field");
return NULL;
}
jmethodID mid = (*env)->GetMethodID(env, cls, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
if (mid == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get method ID");
return NULL;
}
jobject manager = (*env)->CallObjectMethod(env, ctx, mid, btsrv);
if (manager == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get bluetooth manager");
return NULL;
}
cls = (*env)->GetObjectClass(env, manager);
if (cls == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get class of bluetooth manager");
return NULL;
}
mid = (*env)->GetMethodID(env, cls, "getAdapter", "()Landroid/bluetooth/BluetoothAdapter;");
if (mid == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get methodID for getAdapter()");
return NULL;
}
jobject ad = (*env)->CallObjectMethod(env, manager, mid);
if (ad == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get adapter");
} else {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Found");
}
ad = (*env)->NewGlobalRef(env, ad);
EnableBluetooth(env, ctx, ad);
return ad;
}
jstring
GetObjectStaticStringField(JNIEnv* env, jobject obj, char* fld) {
jclass cls = (*env)->GetObjectClass(env, obj);
if (cls == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetObjectStaticStringField(): Can't get class");
return NULL;
}
jfieldID id = (*env)->GetStaticFieldID(env, cls, fld, "Ljava/lang/String;");
if (id == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "GetObjectStaticStringField(): Can't find FieldID");
return NULL;
}
return (jstring)(*env)->GetStaticObjectField(env, cls, id);
}
JNIEXPORT jobject
CreateObject(JNIEnv* env, jclass cls) {
jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "()V");
return (*env)->NewObject(env, cls, init);
}
void CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
(*env)->CallVoidMethod(env, obj, methodID);
}
jboolean CallBooleanMethod(JNIEnv *env, jobject obj, jmethodID mid) {
return (*env)->CallBooleanMethod(env, obj, mid);
}
jint CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
return (*env)->CallIntMethod(env, obj, methodID);
}
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
return (*env)->GetMethodID(env, clazz, name, sig);
}
jint GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
return (*vm)->GetEnv(vm, (void **)env, version);
}
jint AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
}
jint DetachCurrentThread(JavaVM *vm) {
return (*vm)->DetachCurrentThread(vm);
}
*/ */
import "C" import "C"
@ -177,60 +19,17 @@ var theJVM *C.JavaVM
type JNIEnv = C.JNIEnv type JNIEnv = C.JNIEnv
func setJVM(jvm, context uintptr) { func setJVM(jvm uintptr) {
log.Print("set theJVM") log.Printf("set theJVM")
theJVM = (*C.JavaVM)(unsafe.Pointer(jvm)) theJVM = (*C.JavaVM)(unsafe.Pointer(jvm))
//log.Printf("theJVM = %d", uintptr(unsafe.Pointer(theJVM))) if theJVM == nil {
RunInJVM(func(env *C.JNIEnv) { log.Printf("theJVM is nil!")
C.SetLoader(env, (C.jobject)(context)) } else {
}) log.Printf("theJVM is not nil")
}
} }
func FindClass(env *C.JNIEnv, name string) C.jclass { func runInJVM(f func(env *C.JNIEnv)) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return C.FindClass((*C.JNIEnv)(env), cname)
}
func JniGetObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
return C.GetObjectClass(env, obj)
}
func JniGetObjectStaticStringField(env *C.JNIEnv, obj C.jobject, fld string) C.jstring {
cstring := C.CString(fld)
defer C.free(unsafe.Pointer(cstring))
return C.GetObjectStaticStringField(env, obj, cstring)
}
func GetBluetoothAdapter(env *C.JNIEnv, ctx uintptr) C.jobject {
return C.GetBluetoothAdapter(env, (C.jobject)(ctx))
}
func JniCallVoidMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) {
C.CallVoidMethod(env, obj, methodID)
}
func JniCallBooleanMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) bool {
return C.CallBooleanMethod(env, obj, methodID) == C.JNI_TRUE
}
func JniCallIntMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) int {
return (int)(C.CallIntMethod(env, obj, methodID))
}
func JniGetMethodID(env *C.JNIEnv, cls C.jclass, name, sig string) C.jmethodID {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
csig := C.CString(sig)
defer C.free(unsafe.Pointer(csig))
return C.GetMethodID(env, cls, cname, csig)
}
func CreateObject(env *C.JNIEnv, cls C.jclass) C.jobject {
return C.CreateObject(env, (C.jclass)(cls))
}
func RunInJVM(f func(env *C.JNIEnv)) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
var env *C.JNIEnv var env *C.JNIEnv

14
jni_android.h Normal file
View File

@ -0,0 +1,14 @@
#include <jni.h>
jboolean enabled(JNIEnv *env, jobject p);
void scan(JNIEnv *env, jobject b);
void stopScan(JNIEnv *env, jobject b);
void connect(JNIEnv *env, jobject b, jobject d);
void disconnect(JNIEnv *env, jobject b, jobject g);
void discoverServices(JNIEnv *env, jobject b, jobject p);
void discoverCharacteristics(JNIEnv *env, jobject b, jobject g, jobject s);
void setCharacteristicNotification(JNIEnv *env, jobject b, jobject g, jobject c);
jint GetEnv(JavaVM *vm, JNIEnv **env, jint version);
jint AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args);
jint DetachCurrentThread(JavaVM *vm);
jobject NewGlobalRef(JNIEnv *env, jobject o);