ble/ble_android.go

404 lines
9.3 KiB
Go

//go:generate mkdir -p classes
//go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar -classpath blessed-full.jar -d classes BlessedConnect.java
//go:generate jar cf Ble.jar -C classes .
//go:generate rm -rf classes
package ble
import (
"log"
"sync"
"unsafe"
"gioui.org/app"
_ "gioui.org/app/permission/bluetooth"
"git.wow.st/gmp/ble/gatt"
)
/*
#include "jni_android.h"
*/
import "C"
// Types required for ble.go
type bleHandle struct {
BlessedConnect C.jobject
state int
}
type bleState string
type Peripheral struct {
Name string
RSSI int
Identifier string
peripheral C.jobject
}
type Service struct {
UUID string
service C.jobject
}
type Characteristic struct {
UUID string
characteristic C.jobject
}
// Internal global variables
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
//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
// FIXME: check what happens with "incomplete" Peripherals that have only
// an Identifier set. Do the other functions on Peripherals do something
// sensible?
func newPeripheral(name, id string, rssi int, peripheral C.jobject) Peripheral {
return Peripheral{
Name: name,
RSSI: rssi,
Identifier: id,
peripheral: peripheral,
}
}
func (p Peripheral) IsIncomplete() bool {
if p.peripheral == 0 {
return true
} else {
return false
}
}
// FIXME: should I call NewGlobalRef() here?
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.BlessedConnect) == 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.BlessedConnect);
})
}
//stopScan stops a scan in progress
func (b *BLE) stopScan() {
connect()
runInJVM(func(env *C.JNIEnv) {
C.stopScan(env, b.handle.BlessedConnect);
})
}
//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)
})
}
//cancelConnection cancels an in-progress connection attempt
func (b *BLE) cancelConnection(p Peripheral) {
connect()
runInJVM(func(env *C.JNIEnv) {
C.disconnect(env, b.handle.BlessedConnect, p.peripheral)
})
}
//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 (p Peripheral) DiscoverServices() {
//launch a goroutine because this function calls back directly
//from the same thread (the 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)
})
}()
}
//DiscoverCharacteristics asks a Peripheral for the Characteristics related
//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)
go func() {
connect()
log.Printf("discovering characteristics")
runInJVM(func(env *C.JNIEnv) {
C.discoverCharacteristics(env, gBLE.handle.BlessedConnect, p.peripheral, serv.service)
})
}()
}
//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)
}
})
}
//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(view uintptr) {
log.Printf("ble.Enable()")
setJVM(app.JavaVM())
runInJVM(func(env *JNIEnv) {
log.Printf("ble.Enable(): inside runInJVM()")
C.registerFragment(env, (C.jobject)(unsafe.Pointer(view)))
})
}
// Go callbacks from Java
//export Java_st_wow_git_ble_BlessedConnect_installComplete
func Java_st_wow_git_ble_BlessedConnect_installComplete(env *C.JNIEnv, class C.jclass, b C.jobject) {
log.Printf("installComplete()")
if (b == 0) {
log.Printf("BlessedConnect object is nil!")
}
gBLE.handle.BlessedConnect = (C.NewGlobalRef)(env,b)
if C.enabled(env, gBLE.handle.BlessedConnect) == 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_BlessedConnect_updateState
func Java_st_wow_git_ble_BlessedConnect_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(p C.jobject, cid *C.char) {
id := C.GoString(cid)
peripheral := gBLE.retrievePeripheral(id, p)
if peripheral.peripheral == 0 {
log.Printf("goOnConnect(): peripheral == null")
}
gBLE.connections.UpdateState(peripheral, "connected")
gBLE.events <- ConnectEvent{peripheral}
log.Printf("Go: goOnConnect returning\n")
}
//export goOnDisconnect
func goOnDisconnect(p C.jobject, cid *C.char) {
id := C.GoString(cid)
peripheral := gBLE.retrievePeripheral(id)
if peripheral.peripheral == 0 {
log.Printf("goOnDisconnect(): peripheral == null")
}
go func() {
gBLE.connections.UpdateState(peripheral, "cancel")
gBLE.events <- DisconnectEvent{peripheral}
}()
log.Printf("Go: goOnDisconnect returning\n")
}
//export goOnDiscoverService
func goOnDiscoverService(cid, cuuid *C.char, serv C.jobject) {
id := C.GoString(cid)
uuid := C.GoString(cuuid)
peripheral := gBLE.retrievePeripheral(id)
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, characteristic C.jobject) {
id := C.GoString(cid)
suuid := C.GoString(csuuid)
cuuid := C.GoString(ccuuid)
log.Printf("goOnDiscoverCharacteristic: %s", cuuid)
peripheral := gBLE.retrievePeripheral(id)
service := Service{
UUID: suuid,
service: serv,
}
ch := Characteristic{
UUID: cuuid,
characteristic: characteristic,
}
gBLE.events <- DiscoverCharacteristicEvent{
Peripheral: peripheral,
Service: service,
Characteristic: ch,
Gatt: gatt.Characteristic{cuuid},
}
}
//export goOnCharacteristicChanged
func goOnCharacteristicChanged(cid, ccuuid *C.char, characteristic C.jobject, cvalue *C.char, length C.jint) {
id := C.GoString(cid)
cuuid := C.GoString(ccuuid)
peripheral := gBLE.retrievePeripheral(id)
ch := Characteristic{
UUID: cuuid,
characteristic: characteristic,
}
gBLE.events <- UpdateValueEvent{
Peripheral: peripheral,
Characteristic: ch,
Data: C.GoBytes(unsafe.Pointer(cvalue), length),
}
}
// internal convenience functions
func (b *BLE) retrievePeripheral(id string, devs ...C.jobject) Peripheral {
var dev C.jobject
if len(devs) > 0 {
dev = devs[0]
}
found := false
var peripheral Peripheral
gBLE.peripherals.Lock()
for i, item := range gBLE.peripherals.items {
if item.p.Identifier == id {
peripheral = item.p
if dev != 0 {
peripheral.peripheral = dev
}
found = true
gBLE.peripherals.items[i].p = peripheral
break
}
}
gBLE.peripherals.Unlock()
if !found {
log.Printf("Go: peripheral not found!")
}
return peripheral
}