From f036c5e1346f360efa69d5cf616cdcbe41cbca15 Mon Sep 17 00:00:00 2001 From: Greg Date: Mon, 28 Oct 2019 12:43:33 -0400 Subject: [PATCH] Further restructuring and add stub implementation for Android. --- ble.go | 2 +- ble_android.go | 103 ++++++++++++++++++++++++++++++++++++++ ble_darwin.go | 42 +++++++++------- jni_android.go | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+), 18 deletions(-) create mode 100644 ble_android.go create mode 100644 jni_android.go diff --git a/ble.go b/ble.go index 9956ed2..11eabe7 100644 --- a/ble.go +++ b/ble.go @@ -245,7 +245,7 @@ func (b *BLE) Connect(p Peripheral) bool { return false } b.Unlock() - if p.p == nil { + if p.IsIncomplete() { var ok bool p, ok = b.knownPeripheral(p) if !ok { diff --git a/ble_android.go b/ble_android.go new file mode 100644 index 0000000..463edc2 --- /dev/null +++ b/ble_android.go @@ -0,0 +1,103 @@ +package ble + +import ( + "gioui.org/app" +) + +// Types required for ble.go + +type bleState string + +type bleHandle struct { +} + +type Peripheral struct { + Name string + RSSI int + Identifier string +} + +type Service string +type Characteristic string + +// Internal global variables + +// 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() { + h := app.PlatformHandle() + setJVM(h.JVM, h.Context) +} + +//peripheralLookup returns a pointer to a BLE struct related to the given +//Peripheral. +func peripheralLookup(p Peripheral) *BLE { + return &BLE{} +} + +//newPeripheral creates a new Peripheral struct +func newPeripheral() Peripheral { + return Peripheral{} +} + +func (p Peripheral) IsIncomplete() bool { + return false +} + +func (p Peripheral) Retain() { +} + +//stringState returns a string version of the BLE state +func (b *BLE) stringState() string { + return "" +} + +//readyToScan returns true if the hardware is ready to initiate a scan +func (b *BLE) readyToScan() bool { + return true +} + +//scan puts the BLE hardware into scanning mode +func (b *BLE) scan() { +} + +//stopScan stops a scan in progress +func (b *BLE) stopScan() { +} + +//connectPeripheral attempts to connect to a Peripheral +func (b *BLE) connectPeripheral(x Peripheral) { +} + +//cancelConnection cancels an in-progress connection attempt +func (b *BLE) cancelConnection(p Peripheral) { +} + +//knownPeripheral returns a Peripheral that is known to the system without +//scanning +func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) { + return Peripheral{}, false +} + +//DiscoverServices asks a Peripheral for its Services +func (x Peripheral) DiscoverServices() { +} + +//DiscoverCharacteristics asks a Peripheral for the Characteristics related +//to a Service +func (p Peripheral) DiscoverCharacteristics(serv Service) { +} + +//SetNotifyValue subscribes to a characteristic +func (p Peripheral) SetNotifyValue(c Characteristic) { +} + +//NewBLE returns a pointer to a BLE struct after setting up the OS +//Bluetooth API. +func NewBLE() *BLE { + ps := Peripherals{items: make([]PeripheralListItem, 0)} + return &BLE{events: make(chan interface{}), peripherals: ps} +} + diff --git a/ble_darwin.go b/ble_darwin.go index 4f5fa86..0831372 100644 --- a/ble_darwin.go +++ b/ble_darwin.go @@ -21,10 +21,6 @@ type bleHandle struct { cm *ns.CBCentralManager } -var cdLookup map[unsafe.Pointer]*BLE -var pdLookup map[unsafe.Pointer]*BLE -var pcache map[unsafe.Pointer]*Peripheral - type Peripheral struct { Name string RSSI int @@ -36,11 +32,13 @@ type Peripheral struct { type Service *ns.CBService type Characteristic *ns.CBCharacteristic -// Functions required by API +// Internal global variables -func peripheralLookup(p Peripheral) *BLE { - return pdLookup[p.p.Ptr()] -} +var cdLookup map[unsafe.Pointer]*BLE +var pdLookup map[unsafe.Pointer]*BLE +var pcache map[unsafe.Pointer]*Peripheral + +// Functions required by API //Init needs to be called before the BLE library can be used. No setup needed //on Darwin. @@ -48,6 +46,12 @@ func Init() { } +//peripheralLookup returns a pointer to a BLE struct related to the given +//Peripheral. +func peripheralLookup(p Peripheral) *BLE { + return pdLookup[p.p.Ptr()] +} + //newPeripheral creates a new Peripheral struct func newPeripheral(x *ns.CBPeripheral) Peripheral { if pcache == nil { @@ -66,6 +70,19 @@ func newPeripheral(x *ns.CBPeripheral) Peripheral { return ret } +func (p Peripheral) IsIncomplete() bool { + return p.p == nil +} + +func (x Peripheral) Retain() { +//NOTE: ns.ObjectAtIndex() calls SetFinalizer for us, which will be a problem +//later when we call GC(), so we always first clear the finalizer before +//setting a new one. + runtime.SetFinalizer(x.p, nil) + x.p.Retain() + x.p.GC() +} + //stringState returns a string version of the BLE state func (b *BLE) stringState() string { x := b.state @@ -319,15 +336,6 @@ func peripheralName(p *ns.CBPeripheral) string { return ret } -func (x *Peripheral) Retain() { -//NOTE: ns.ObjectAtIndex() calls SetFinalizer for us, which will be a problem -//later when we call GC(), so we always first clear the finalizer before -//setting a new one. - runtime.SetFinalizer(x.p, nil) - x.p.Retain() - x.p.GC() -} - func (b *BLE) setState(x ns.CBManagerState) { b.Lock() defer b.Unlock() diff --git a/jni_android.go b/jni_android.go new file mode 100644 index 0000000..0b2ba87 --- /dev/null +++ b/jni_android.go @@ -0,0 +1,131 @@ +package ble + +/* +#cgo LDFLAGS: -landroid + +#include +#include +#include + +static jobject gClassLoader; +static jmethodID gFindClassMethod; + +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); +} + +JNIEXPORT jobject +CreateObject(JNIEnv* env, jclass cls) { + jmethodID init = (*env)->GetMethodID(env, cls, "", "()V"); + return (*env)->NewObject(env, cls, init); +} + +void CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) { + (*env)->CallVoidMethod(env, obj, methodID); +} + +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 ( + "errors" + "fmt" + "log" + "runtime" + "unsafe" +) + +var theJVM *C.JavaVM + +type JNIEnv = C.JNIEnv + +func setJVM(jvm, context uintptr) { + log.Print("set theJVM") + theJVM = (*C.JavaVM)(unsafe.Pointer(jvm)) + log.Printf("theJVM = %d", uintptr(unsafe.Pointer(theJVM))) + RunInJVM(func(env *C.JNIEnv) { + C.SetLoader(env, (C.jobject)(context)) + }) +} + +func FindClass(env *C.JNIEnv, name string) C.jclass { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + return C.FindClass((*C.JNIEnv)(env), cname) +} + +func JniCallVoidMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) { + C.CallVoidMethod(env, obj, methodID) +} + +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() + defer runtime.UnlockOSThread() + var env *C.JNIEnv + var detach bool + log.Printf("RunInJVM(): theJVM = %d", uintptr(unsafe.Pointer(theJVM))) + if res := C.GetEnv(theJVM, &env, C.JNI_VERSION_1_6); res != C.JNI_OK { + if res != C.JNI_EDETACHED { + panic(fmt.Errorf("JNI GetEnv failed with error %d", res)) + } + if C.AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK { + panic(errors.New("runInJVM: AttachCurrentThread failed")) + } + detach = true + } + + if detach { + defer func() { + C.DetachCurrentThread(theJVM) + }() + } + f(env) +}