Greg
52217c3f6a
Java dependencies. Bug fix in retrievePeripheral so that we do not zero out the reference to the Java Peripheral object.
402 lines
9.2 KiB
Go
402 lines
9.2 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(w *app.Window) {
|
|
log.Printf("ble.Enable()")
|
|
w.RegisterFragment("st/wow/git/ble/BlessedConnect")
|
|
}
|
|
|
|
// 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)
|
|
h := app.PlatformHandle()
|
|
setJVM(h.JVM)
|
|
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, char 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,
|
|
}
|
|
|
|
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)
|
|
|
|
peripheral := gBLE.retrievePeripheral(id)
|
|
|
|
characteristic := Characteristic{
|
|
UUID: cuuid,
|
|
characteristic: char,
|
|
}
|
|
|
|
gBLE.events <- UpdateValueEvent{
|
|
Peripheral: peripheral,
|
|
Characteristic: characteristic,
|
|
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
|
|
}
|