Add ble.go to factor out platform-independent code.

This commit is contained in:
Greg 2019-10-28 09:25:23 -04:00
parent 5955e369e4
commit 2d4f106dd7
2 changed files with 316 additions and 267 deletions

246
ble.go Normal file
View File

@ -0,0 +1,246 @@
package ble
import (
"fmt"
"sync"
"time"
"git.wow.st/gmp/ble/gatt"
)
type PeripheralListItem struct {
p Peripheral
seen time.Time
}
type Peripherals struct {
items []PeripheralListItem
sync.Mutex
}
type ConnectionListItem struct {
p Peripheral
state chan string
}
type Connections struct {
items []ConnectionListItem
close chan struct{}
sync.Mutex
}
func (c *Connections) UpdateState(p Peripheral, s string) {
var ch chan string
c.Lock()
for _, item := range c.items {
if p.Identifier == item.p.Identifier {
ch = item.state
}
}
c.Unlock()
ch <- s
}
func (ps *Peripherals) Add(x Peripheral) bool {
ps.Lock()
defer ps.Unlock()
found := false
for n, item := range ps.items {
if item.p.Identifier == x.Identifier {
item.p = x
item.seen = time.Now()
ps.items[n] = item
found = true
break
}
}
if found {
return false
}
//take ownership of this object
x.Retain()
item := PeripheralListItem{p: x, seen: time.Now()}
ps.items = append(ps.items, item)
return true
}
func (cs *Connections) Add(x ConnectionListItem) bool {
cs.Lock()
defer cs.Unlock()
found := false
for _, item := range cs.items {
if item.p.Identifier == x.p.Identifier {
// are we allowed to have multiple connections to the same peripheral?
return false
}
}
if found {
return false
}
cs.items = append(cs.items, x)
return true
}
type UpdateStateEvent struct {
State string
}
type DiscoverPeripheralEvent struct {
Peripheral Peripheral
}
//FIXME: create a Service type that is platform dependent to make the
//event platform independent
type DiscoverServiceEvent struct {
Peripheral Peripheral
Gatt gatt.Service
Service Service
}
//FIXME: create a Characteristic type that is platform dependent to make the
//event platform independent
type DiscoverCharacteristicEvent struct {
Peripheral Peripheral
Gatt gatt.Characteristic
Service Service
Characteristic Characteristic
}
//FIXME: create a Characteristic type that is platform dependent to make the
//event platform independent
type UpdateValueEvent struct {
Peripheral Peripheral
Characteristic Characteristic
Data []byte
}
type ConnectEvent struct {
Peripheral Peripheral
}
type ConnectTimeoutEvent struct {
Peripheral Peripheral
}
type UpdageValueEvent struct {
Peripheral Peripheral
Characteristic gatt.Characteristic
}
func (b *BLE) Events() chan interface{} {
return b.events
}
func (b *BLE) State() string {
b.Lock()
defer b.Unlock()
return b.stringState()
}
func (b *BLE) Scan() {
b.Lock()
defer b.Unlock()
if b.readyToScan() {
b.peripherals.Lock()
b.peripherals.items = b.peripherals.items[:0]
b.peripherals.Unlock()
fmt.Printf("Go: Scanning\n")
b.scan()
} else {
b.wantScan = true
}
}
func (b *BLE) StopScan() {
b.Lock()
if !b.ready {
b.Unlock()
return
}
b.Unlock()
fmt.Printf("Go: stopping scan\n")
b.stopScan()
}
func connectTracker(b *BLE, x ConnectionListItem) bool {
fmt.Printf("connectTracker(): %s\n", x.p.Name)
b.connections.Lock()
for _, item := range b.connections.items {
if item.p.Identifier == x.p.Identifier {
fmt.Printf("connectTracker(): already connecting to %s\n", x.p.Name)
b.connections.Unlock()
return true
}
}
b.connections.items = append(b.connections.items, x)
b.connections.Unlock()
b.connectPeripheral(x.p)
cancel := func() {
b.cancelConnection(x.p)
b.connections.Lock()
for n, item := range b.connections.items {
if item.p.Identifier == x.p.Identifier {
b.connections.items = append(b.connections.items[:n], b.connections.items[n+1:]...)
}
}
b.connections.Unlock()
}
go func() {
tick := time.NewTicker(time.Second * 5)
select {
case <-b.connections.close:
fmt.Printf("connectTracker(): Closing connection to %s\n", x.p.Name)
b.cancelConnection(x.p)
case <-tick.C:
fmt.Printf("connectTracker(): Connection to %s timed out\n", x.p.Name)
cancel()
b.events <- ConnectTimeoutEvent{x.p}
case state := <-x.state:
if state == "cancel" {
cancel()
}
fmt.Printf("connectTracker(): state %s\n", state)
}
}()
return true
}
func (b *BLE) Connect(p Peripheral) bool {
b.Lock()
if !b.ready {
b.Unlock()
fmt.Printf("--BLE not ready\n")
return false
}
b.Unlock()
if p.p == nil {
var ok bool
p, ok = b.knownPeripheral(p)
if !ok {
return false
}
}
item := ConnectionListItem{p, make(chan string)}
fmt.Printf("BLE.Connect() calling connectTracker\n")
return connectTracker(b, item)
}
func Disconnect(p Peripheral) {
b := peripheralLookup(p)
found := false
b.connections.Lock()
for i, item := range b.connections.items {
if item.p.Identifier == p.Identifier {
found = true
b.connections.items = append(b.connections.items[:i], b.connections.items[i+1:]...)
break
}
}
b.connections.Unlock()
if !found {
return
}
b.cancelConnection(p)
}

View File

@ -31,6 +31,9 @@ var cdLookup map[unsafe.Pointer]*BLE
var pdLookup map[unsafe.Pointer]*BLE var pdLookup map[unsafe.Pointer]*BLE
var pcache map[unsafe.Pointer]*Peripheral var pcache map[unsafe.Pointer]*Peripheral
func peripheralLookup(p Peripheral) *BLE {
return pdLookup[p.p.Ptr()]
}
type State string type State string
@ -42,6 +45,24 @@ type Peripheral struct {
p *ns.CBPeripheral p *ns.CBPeripheral
} }
type Service *ns.CBService
type Characteristic *ns.CBCharacteristic
//Init needs to be called before the BLE library can be used. No setup needed
//on Darwin.
func Init() {
}
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 peripheralName(p *ns.CBPeripheral) string { func peripheralName(p *ns.CBPeripheral) string {
var ret string var ret string
nsname := p.Name() nsname := p.Name()
@ -68,125 +89,8 @@ func newPeripheral(x *ns.CBPeripheral) Peripheral {
return ret return ret
} }
type PeripheralListItem struct { func (b *BLE) stringState() string {
p Peripheral x := b.state
seen time.Time
}
type Peripherals struct {
items []PeripheralListItem
sync.Mutex
}
type ConnectionListItem struct {
p Peripheral
state chan string
}
type Connections struct {
items []ConnectionListItem
close chan struct{}
sync.Mutex
}
func (c *Connections) UpdateState(p Peripheral, s string) {
var ch chan string
c.Lock()
for _, item := range c.items {
if p.Identifier == item.p.Identifier {
ch = item.state
}
}
c.Unlock()
ch <- s
}
func (ps *Peripherals) Add(x Peripheral) bool {
ps.Lock()
defer ps.Unlock()
found := false
for n, item := range ps.items {
if item.p.Identifier == x.Identifier {
item.p = x
item.seen = time.Now()
ps.items[n] = item
found = true
break
}
}
if found {
return false
}
//take ownership of this Objective-C object
x.p.Retain()
runtime.SetFinalizer(x.p, nil)
x.p.GC()
item := PeripheralListItem{p: x, seen: time.Now()}
ps.items = append(ps.items, item)
return true
}
func (cs *Connections) Add(x ConnectionListItem) bool {
cs.Lock()
defer cs.Unlock()
found := false
for _, item := range cs.items {
if item.p.Identifier == x.p.Identifier {
// are we allowed to have multiple connections to the same peripheral?
return false
}
}
if found {
return false
}
cs.items = append(cs.items, x)
return true
}
type UpdateStateEvent struct {
State string
}
type DiscoverPeripheralEvent struct {
Peripheral Peripheral
}
type DiscoverServiceEvent struct {
Peripheral Peripheral
Gatt gatt.Service
Service *ns.CBService
}
type DiscoverCharacteristicEvent struct {
Peripheral Peripheral
Gatt gatt.Characteristic
Service *ns.CBService
Characteristic *ns.CBCharacteristic
}
type UpdateValueEvent struct {
Peripheral Peripheral
Characteristic *ns.CBCharacteristic
Data []byte
}
type ConnectEvent struct {
Peripheral Peripheral
}
type ConnectTimeoutEvent struct {
Peripheral Peripheral
}
type UpdageValueEvent struct {
Peripheral Peripheral
Characteristic gatt.Characteristic
}
func (b *BLE) Events() chan interface{} {
return b.events
}
func stringState(x ns.CBManagerState) string {
switch x { switch x {
case (ns.CBManagerState)(ns.CBManagerStateResetting): case (ns.CBManagerState)(ns.CBManagerStateResetting):
return "resetting" return "resetting"
@ -205,10 +109,16 @@ func stringState(x ns.CBManagerState) string {
} }
} }
func (b *BLE) State() string { func (b *BLE) readyToScan() bool {
b.Lock() return b.state == (ns.CBManagerState)(ns.CBManagerStatePoweredOn)
defer b.Unlock() }
return stringState(b.state)
func (b *BLE) scan() {
b.cm.ScanForPeripheralsWithServices(nil, nil)
}
func (b *BLE) stopScan() {
b.cm.StopScan()
} }
func (b *BLE) setState(x ns.CBManagerState) { func (b *BLE) setState(x ns.CBManagerState) {
@ -217,38 +127,18 @@ func (b *BLE) setState(x ns.CBManagerState) {
b.state = x b.state = x
if b.ready && b.wantScan { if b.ready && b.wantScan {
fmt.Printf("Go: Scanning\n") fmt.Printf("Go: Scanning\n")
b.cm.ScanForPeripheralsWithServices(nil, nil) b.scan()
b.wantScan = false b.wantScan = false
} }
} }
func (b *BLE) Scan() { func (b *BLE) connectPeripheral(x Peripheral) {
b.Lock() fmt.Printf("BLE.Connect(): calling cm.ConnectPeripheral(%p)\n", x.p.Ptr())
defer b.Unlock() b.cm.ConnectPeripheral(x.p, nil)
if b.state != (ns.CBManagerState)(ns.CBManagerStatePoweredOn) {
b.wantScan = true
} else {
b.peripherals.Lock()
b.peripherals.items = b.peripherals.items[:0]
b.peripherals.Unlock()
fmt.Printf("Go: Scanning\n")
b.cm.ScanForPeripheralsWithServices(nil, nil)
}
}
func (b *BLE) StopScan() {
b.Lock()
if !b.ready {
b.Unlock()
return
}
b.Unlock()
fmt.Printf("Go: stopping scan\n")
b.cm.StopScan()
} }
func CancelConnection(p Peripheral) { func CancelConnection(p Peripheral) {
b := pdLookup[p.p.Ptr()] b := peripheralLookup(p)
if b == nil { if b == nil {
return return
} }
@ -266,99 +156,24 @@ func CancelConnection(p Peripheral) {
} }
} }
func connectTracker(b *BLE, x ConnectionListItem) bool { func (b *BLE) cancelConnection(p Peripheral) {
fmt.Printf("connectTracker(): %s\n", x.p.Name) b.cm.CancelPeripheralConnection(p.p)
b.connections.Lock()
for _, item := range b.connections.items {
if item.p.Identifier == x.p.Identifier {
fmt.Printf("connectTracker(): already connecting to %s\n", x.p.Name)
b.connections.Unlock()
return true
}
}
b.connections.items = append(b.connections.items, x)
b.connections.Unlock()
fmt.Printf("BLE.Connect(): calling cm.ConnectPeripheral(%p)\n", x.p.p.Ptr())
b.cm.ConnectPeripheral(x.p.p, nil)
cancel := func() {
b.cm.CancelPeripheralConnection(x.p.p)
b.connections.Lock()
for n, item := range b.connections.items {
if item.p.Identifier == x.p.Identifier {
b.connections.items = append(b.connections.items[:n], b.connections.items[n+1:]...)
}
}
b.connections.Unlock()
}
go func() {
tick := time.NewTicker(time.Second * 5)
select {
case <-b.connections.close:
fmt.Printf("connectTracker(): Closing connection to %s\n", x.p.Name)
b.cm.CancelPeripheralConnection(x.p.p)
case <-tick.C:
fmt.Printf("connectTracker(): Connection to %s timed out\n", x.p.Name)
cancel()
b.events <- ConnectTimeoutEvent{x.p}
case state := <-x.state:
if state == "cancel" {
cancel()
}
fmt.Printf("connectTracker(): state %s\n", state)
}
}()
return true
} }
func (b *BLE) Connect(p Peripheral) bool { func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) {
b.Lock()
if !b.ready {
b.Unlock()
fmt.Printf("--BLE not ready\n")
return false
}
b.Unlock()
if p.p == nil {
fmt.Printf("RetrievePeripheralsWithIdentifiers\n") fmt.Printf("RetrievePeripheralsWithIdentifiers\n")
ps := b.cm.RetrievePeripheralsWithIdentifiers(ns.NSArrayWithObjects(ns.NSUUIDAlloc().InitWithUUIDString(ns.NSStringWithGoString(p.Identifier)))) ps := b.cm.RetrievePeripheralsWithIdentifiers(ns.NSArrayWithObjects(ns.NSUUIDAlloc().InitWithUUIDString(ns.NSStringWithGoString(p.Identifier))))
if x := (int)(ps.Count()); x > 0 { if x := (int)(ps.Count()); x > 0 {
fmt.Printf("--found %d\n", x) fmt.Printf("--found %d\n", x)
cbp := ps.ObjectAtIndex(0).CBPeripheral() cbp := ps.ObjectAtIndex(0).CBPeripheral()
//NOTE: ns.ObjectAtIndex() calls SetFinalizer for us, which will be a problem return newPeripheral(cbp), true
//later when we call GC(), so we first clear the finalizer here.
runtime.SetFinalizer(cbp, nil)
p = newPeripheral(cbp)
} else { } else {
fmt.Printf("--none found\n") fmt.Printf("--none found\n")
return false return Peripheral{}, false
} }
}
item := ConnectionListItem{p, make(chan string)}
fmt.Printf("BLE.Connect() calling connectTracker\n")
return connectTracker(b, item)
} }
func Disconnect(p Peripheral) { func didUpdateState(c *ns.CBCentralManager) {
b := pdLookup[p.p.Ptr()]
found := false
b.connections.Lock()
for i, item := range b.connections.items {
if item.p.Identifier == p.Identifier {
found = true
b.connections.items = append(b.connections.items[:i], b.connections.items[i+1:]...)
break
}
}
b.connections.Unlock()
if !found {
return
}
b.cm.CancelPeripheralConnection(p.p)
}
func updateState(c *ns.CBCentralManager) {
b := cdLookup[c.Ptr()] b := cdLookup[c.Ptr()]
st := c.CBManager.State() st := c.CBManager.State()
if st == (ns.CBManagerState)(ns.CBManagerStatePoweredOn) { if st == (ns.CBManagerState)(ns.CBManagerStatePoweredOn) {
@ -378,17 +193,17 @@ func updateState(c *ns.CBCentralManager) {
} }
} }
b.setState(st) b.setState(st)
b.events <- UpdateStateEvent{State: stringState(st)} b.events <- UpdateStateEvent{State: b.stringState()}
} }
func discoverPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral, d *ns.NSDictionary, rssi *ns.NSNumber) { func didDiscoverPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral, d *ns.NSDictionary, rssi *ns.NSNumber) {
b := cdLookup[c.Ptr()] b := cdLookup[c.Ptr()]
peripheral := newPeripheral(p) peripheral := newPeripheral(p)
peripheral.RSSI = (int)(rssi.IntValue()) peripheral.RSSI = (int)(rssi.IntValue())
_discoverPeripheral(b, peripheral) _didDiscoverPeripheral(b, peripheral)
} }
func _discoverPeripheral(b *BLE, peripheral Peripheral) { func _didDiscoverPeripheral(b *BLE, peripheral Peripheral) {
if peripheral.Name == "" { if peripheral.Name == "" {
return return
} }
@ -398,7 +213,7 @@ func _discoverPeripheral(b *BLE, peripheral Peripheral) {
} }
} }
func connectPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral) { func didConnectPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral) {
fmt.Printf("Did connect peripheral\n") fmt.Printf("Did connect peripheral\n")
b := cdLookup[c.Ptr()] b := cdLookup[c.Ptr()]
@ -428,7 +243,7 @@ func connectPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral) {
b.connections.UpdateState(peripheral, "connected") b.connections.UpdateState(peripheral, "connected")
b.events <- ConnectEvent{peripheral} b.events <- ConnectEvent{peripheral}
fmt.Printf("Go: connectPeripheral returning\n") fmt.Printf("Go: didConnectPeripheral returning\n")
} }
func (x Peripheral) DiscoverServices() { func (x Peripheral) DiscoverServices() {
@ -440,7 +255,7 @@ func (x Peripheral) DiscoverServices() {
p.DiscoverServices(nil) p.DiscoverServices(nil)
} }
func discoverServices(p *ns.CBPeripheral, e *ns.NSError) { func didDiscoverServices(p *ns.CBPeripheral, e *ns.NSError) {
b := pdLookup[p.Ptr()] b := pdLookup[p.Ptr()]
fmt.Printf("Did discover services\n") fmt.Printf("Did discover services\n")
p.Services().ObjectEnumerator().ForIn(func(o *ns.Id) bool { p.Services().ObjectEnumerator().ForIn(func(o *ns.Id) bool {
@ -453,7 +268,7 @@ func discoverServices(p *ns.CBPeripheral, e *ns.NSError) {
} }
return true return true
}) })
fmt.Printf("Go: discoverServices returning\n") fmt.Printf("Go: didDiscoverServices returning\n")
} }
func hr(d *ns.NSData) int { func hr(d *ns.NSData) int {
@ -473,7 +288,7 @@ func (p Peripheral) DiscoverCharacteristics(serv *ns.CBService) {
p.p.DiscoverCharacteristics(nil, serv) p.p.DiscoverCharacteristics(nil, serv)
} }
func discoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError) { func didDiscoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError) {
b := pdLookup[p.Ptr()] b := pdLookup[p.Ptr()]
fmt.Printf("Did discover characteristics\n") fmt.Printf("Did discover characteristics\n")
s.Characteristics().ObjectEnumerator().ForIn(func(o *ns.Id) bool { s.Characteristics().ObjectEnumerator().ForIn(func(o *ns.Id) bool {
@ -488,14 +303,14 @@ func discoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError)
} }
return true return true
}) })
fmt.Printf("Go: discoverCharacteristics returning\n") fmt.Printf("Go: didDiscoverCharacteristics returning\n")
} }
func (p Peripheral) SetNotifyValue(c *ns.CBCharacteristic) { func (p Peripheral) SetNotifyValue(c *ns.CBCharacteristic) {
p.p.SetNotifyValue(1, c) p.p.SetNotifyValue(1, c)
} }
func updateValue(p *ns.CBPeripheral, chr *ns.CBCharacteristic, e *ns.NSError) { func didUpdateValue(p *ns.CBPeripheral, chr *ns.CBCharacteristic, e *ns.NSError) {
b := pdLookup[p.Ptr()] b := pdLookup[p.Ptr()]
v := chr.Value() v := chr.Value()
b.events <-UpdateValueEvent{ b.events <-UpdateValueEvent{
@ -505,33 +320,21 @@ func updateValue(p *ns.CBPeripheral, chr *ns.CBCharacteristic, e *ns.NSError) {
} }
} }
var (
hrm_uuid *ns.CBUUID
hrv_uuid *ns.CBUUID
info_uuid *ns.CBUUID
gble *BLE
)
func NewBLE() *BLE { func NewBLE() *BLE {
ps := Peripherals{items: make([]PeripheralListItem, 0)} ps := Peripherals{items: make([]PeripheralListItem, 0)}
ble := &BLE{events: make(chan interface{}), peripherals: ps} ble := &BLE{events: make(chan interface{}), peripherals: ps}
gble = ble
queue := ns.DispatchQueueCreate(ns.CharWithGoString("go_hrm_queue"), nil) queue := ns.DispatchQueueCreate(ns.CharWithGoString("go_hrm_queue"), nil)
cd := ns.CBDelegateAlloc() cd := ns.CBDelegateAlloc()
cd.CentralManagerDidUpdateStateCallback(updateState) cd.CentralManagerDidUpdateStateCallback(didUpdateState)
cd.CentralManagerDidDiscoverPeripheralCallback(discoverPeripheral) cd.CentralManagerDidDiscoverPeripheralCallback(didDiscoverPeripheral)
cd.CentralManagerDidConnectPeripheralCallback(connectPeripheral) cd.CentralManagerDidConnectPeripheralCallback(didConnectPeripheral)
cd.PeripheralDidDiscoverServicesCallback(discoverServices) cd.PeripheralDidDiscoverServicesCallback(didDiscoverServices)
cd.PeripheralDidDiscoverCharacteristicsForServiceCallback(discoverCharacteristics) cd.PeripheralDidDiscoverCharacteristicsForServiceCallback(didDiscoverCharacteristics)
cd.PeripheralDidUpdateValueForCharacteristicCallback(updateValue) cd.PeripheralDidUpdateValueForCharacteristicCallback(didUpdateValue)
hrm_uuid = ns.CBUUIDWithGoString("180D")
hrv_uuid = ns.CBUUIDWithGoString("2A37")
info_uuid = ns.CBUUIDWithGoString("180A")
ble.cd = cd ble.cd = cd
if cdLookup == nil { if cdLookup == nil {