//go:generate mkdir -p classes //go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar -d classes BleConnect.java //go:generate jar cf Ble.jar -C classes . //go:generate rm -rf classes package ble import ( "log" "sync" "gioui.org/app" _ "gioui.org/app/permission/bluetooth_le" "git.wow.st/gmp/ble/gatt" ) /* #include "jni_android.h" */ import "C" // Types required for ble.go type bleHandle struct { BleConnect C.jobject state int } type bleState string type Peripheral struct { Name string RSSI int Identifier string device C.jobject } type Service string type Characteristic string // Internal global variables var ( gBLE *BLE // FIXME: move to lookup tables as in ble_darwin.go? installCompleteOnce sync.Once waitch chan struct{} ) const ( 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 //peripheralLookup returns a pointer to a BLE struct related to the given //Peripheral. func peripheralLookup(p Peripheral) *BLE { return gBLE } //newPeripheral creates a new Peripheral struct 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 { if p.device == 0 { return true } else { return false } } func (p Peripheral) Retain() { } //stringState returns a string version of the BLE state func (b *BLE) stringState() string { 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 func (b *BLE) readyToScan() bool { 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 func (b *BLE) scan() { connect() runInJVM(func(env *C.JNIEnv) { C.scan(env, b.handle.BleConnect); }) } //stopScan stops a scan in progress func (b *BLE) stopScan() { connect() runInJVM(func(env *C.JNIEnv) { C.stopScan(env, b.handle.BleConnect); }) } //connectPeripheral attempts to connect to a 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 func (b *BLE) cancelConnection(p Peripheral) { connect() runInJVM(func(env *C.JNIEnv) { C.disconnect(env, b.handle.BleConnect) }) } //knownPeripheral returns a Peripheral that is known to the system without //scanning //Not implemented for Android func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) { return Peripheral{}, false } //DiscoverServices asks a Peripheral for its Services func (x Peripheral) DiscoverServices() { connect() log.Printf("discovering services") runInJVM(func(env *C.JNIEnv) { C.discoverServices(env, gBLE.handle.BleConnect, x.device) }) } //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)} gBLE = &BLE{ events: make(chan interface{}), peripherals: ps, } return gBLE } //Enable func (b *BLE) Enable(w *app.Window) { log.Printf("ble.Enable()") err := w.RegisterFragment("st/wow/git/ble/BleConnect") log.Printf("ble.Enable() RegisterFragment() returned") if err != nil { log.Printf("Error! %s", err) } } // 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(cid *C.char) { id := C.GoString(cid) 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!") } gBLE.connections.UpdateState(peripheral, "connected") gBLE.events <- ConnectEvent{peripheral} log.Printf("Go: goOnConnect returning\n") } //export goOnDiscoverService func goOnDiscoverService(cid *C.char, cuuid *C.char) { 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!") } gBLE.events <- DiscoverServiceEvent{ Peripheral: peripheral, Gatt: gatt.Service{uuid}, } }