419 lines
9.5 KiB
Go
419 lines
9.5 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 {
|
|
BleConnect 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.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) {
|
|
runInJVM(func(env *C.JNIEnv) {
|
|
C.connect(env, b.handle.BleConnect, 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.BleConnect, 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 (underlying Java call is synchronous)
|
|
go func() {
|
|
connect()
|
|
log.Printf("discovering services")
|
|
runInJVM(func(env *C.JNIEnv) {
|
|
C.discoverServices(env, gBLE.handle.BleConnect, 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 (underlying Java call is synchronous)
|
|
go func() {
|
|
connect()
|
|
log.Printf("discovering characteristics")
|
|
runInJVM(func(env *C.JNIEnv) {
|
|
C.discoverCharacteristics(env, gBLE.handle.BleConnect, p.peripheral, serv.service)
|
|
})
|
|
log.Printf("discovering characteristics done")
|
|
}()
|
|
}
|
|
|
|
//SetNotifyValue subscribes to a characteristic
|
|
func (p Peripheral) SetNotifyValue(c Characteristic) {
|
|
runInJVM(func(env *C.JNIEnv) {
|
|
C.readCharacteristic(env, gBLE.handle.BleConnect, p.peripheral, c.characteristic)
|
|
//result := (C.setCharacteristicNotification(env, gBLE.handle.BleConnect, p.peripheral, c.characteristic) == C.JNI_TRUE)
|
|
//log.Printf("setCharacteristicNotification: %s = %t", c.UUID, result)
|
|
})
|
|
}
|
|
|
|
//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.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_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)
|
|
|
|
var peripheral Peripheral
|
|
found := false
|
|
|
|
gBLE.peripherals.Lock()
|
|
for n, item := range gBLE.peripherals.items {
|
|
if item.p.Identifier == id {
|
|
peripheral = item.p
|
|
peripheral.peripheral = p
|
|
gBLE.peripherals.items[n].p = peripheral
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
gBLE.peripherals.Unlock()
|
|
|
|
if !found {
|
|
log.Printf("Go: peripheral not found!")
|
|
}
|
|
|
|
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 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),
|
|
}
|
|
}
|