diff --git a/ble.go b/ble.go new file mode 100644 index 0000000..121e1ab --- /dev/null +++ b/ble.go @@ -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) +} + diff --git a/ble_darwin.go b/ble_darwin.go index ed3b210..c9aea34 100644 --- a/ble_darwin.go +++ b/ble_darwin.go @@ -31,6 +31,9 @@ var cdLookup map[unsafe.Pointer]*BLE var pdLookup map[unsafe.Pointer]*BLE var pcache map[unsafe.Pointer]*Peripheral +func peripheralLookup(p Peripheral) *BLE { + return pdLookup[p.p.Ptr()] +} type State string @@ -42,6 +45,24 @@ type Peripheral struct { 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 { var ret string nsname := p.Name() @@ -68,125 +89,8 @@ func newPeripheral(x *ns.CBPeripheral) Peripheral { return ret } -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 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 { +func (b *BLE) stringState() string { + x := b.state switch x { case (ns.CBManagerState)(ns.CBManagerStateResetting): return "resetting" @@ -205,10 +109,16 @@ func stringState(x ns.CBManagerState) string { } } -func (b *BLE) State() string { - b.Lock() - defer b.Unlock() - return stringState(b.state) +func (b *BLE) readyToScan() bool { + return b.state == (ns.CBManagerState)(ns.CBManagerStatePoweredOn) +} + +func (b *BLE) scan() { + b.cm.ScanForPeripheralsWithServices(nil, nil) +} + +func (b *BLE) stopScan() { + b.cm.StopScan() } func (b *BLE) setState(x ns.CBManagerState) { @@ -217,38 +127,18 @@ func (b *BLE) setState(x ns.CBManagerState) { b.state = x if b.ready && b.wantScan { fmt.Printf("Go: Scanning\n") - b.cm.ScanForPeripheralsWithServices(nil, nil) + b.scan() b.wantScan = false } } -func (b *BLE) Scan() { - b.Lock() - defer b.Unlock() - 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 (b *BLE) connectPeripheral(x Peripheral) { + fmt.Printf("BLE.Connect(): calling cm.ConnectPeripheral(%p)\n", x.p.Ptr()) + b.cm.ConnectPeripheral(x.p, nil) } func CancelConnection(p Peripheral) { - b := pdLookup[p.p.Ptr()] + b := peripheralLookup(p) if b == nil { return } @@ -266,99 +156,24 @@ func CancelConnection(p Peripheral) { } } -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() - 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 { - 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") - ps := b.cm.RetrievePeripheralsWithIdentifiers(ns.NSArrayWithObjects(ns.NSUUIDAlloc().InitWithUUIDString(ns.NSStringWithGoString(p.Identifier)))) - if x := (int)(ps.Count()); x > 0 { - fmt.Printf("--found %d\n", x) - cbp := ps.ObjectAtIndex(0).CBPeripheral() -//NOTE: ns.ObjectAtIndex() calls SetFinalizer for us, which will be a problem -//later when we call GC(), so we first clear the finalizer here. - runtime.SetFinalizer(cbp, nil) - p = newPeripheral(cbp) - } else { - fmt.Printf("--none found\n") - return false - } - } - item := ConnectionListItem{p, make(chan string)} - fmt.Printf("BLE.Connect() calling connectTracker\n") - return connectTracker(b, item) -} - -func Disconnect(p Peripheral) { - 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 - } +func (b *BLE) cancelConnection(p Peripheral) { b.cm.CancelPeripheralConnection(p.p) } -func updateState(c *ns.CBCentralManager) { +func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) { + fmt.Printf("RetrievePeripheralsWithIdentifiers\n") + ps := b.cm.RetrievePeripheralsWithIdentifiers(ns.NSArrayWithObjects(ns.NSUUIDAlloc().InitWithUUIDString(ns.NSStringWithGoString(p.Identifier)))) + if x := (int)(ps.Count()); x > 0 { + fmt.Printf("--found %d\n", x) + cbp := ps.ObjectAtIndex(0).CBPeripheral() + return newPeripheral(cbp), true + } else { + fmt.Printf("--none found\n") + return Peripheral{}, false + } +} + +func didUpdateState(c *ns.CBCentralManager) { b := cdLookup[c.Ptr()] st := c.CBManager.State() if st == (ns.CBManagerState)(ns.CBManagerStatePoweredOn) { @@ -378,17 +193,17 @@ func updateState(c *ns.CBCentralManager) { } } 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()] peripheral := newPeripheral(p) 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 == "" { 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") b := cdLookup[c.Ptr()] @@ -428,7 +243,7 @@ func connectPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral) { b.connections.UpdateState(peripheral, "connected") b.events <- ConnectEvent{peripheral} - fmt.Printf("Go: connectPeripheral returning\n") + fmt.Printf("Go: didConnectPeripheral returning\n") } func (x Peripheral) DiscoverServices() { @@ -440,7 +255,7 @@ func (x Peripheral) DiscoverServices() { p.DiscoverServices(nil) } -func discoverServices(p *ns.CBPeripheral, e *ns.NSError) { +func didDiscoverServices(p *ns.CBPeripheral, e *ns.NSError) { b := pdLookup[p.Ptr()] fmt.Printf("Did discover services\n") p.Services().ObjectEnumerator().ForIn(func(o *ns.Id) bool { @@ -453,7 +268,7 @@ func discoverServices(p *ns.CBPeripheral, e *ns.NSError) { } return true }) - fmt.Printf("Go: discoverServices returning\n") + fmt.Printf("Go: didDiscoverServices returning\n") } func hr(d *ns.NSData) int { @@ -473,7 +288,7 @@ func (p Peripheral) DiscoverCharacteristics(serv *ns.CBService) { 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()] fmt.Printf("Did discover characteristics\n") 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 }) - fmt.Printf("Go: discoverCharacteristics returning\n") + fmt.Printf("Go: didDiscoverCharacteristics returning\n") } func (p Peripheral) SetNotifyValue(c *ns.CBCharacteristic) { 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()] v := chr.Value() 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 { ps := Peripherals{items: make([]PeripheralListItem, 0)} ble := &BLE{events: make(chan interface{}), peripherals: ps} - gble = ble queue := ns.DispatchQueueCreate(ns.CharWithGoString("go_hrm_queue"), nil) cd := ns.CBDelegateAlloc() - cd.CentralManagerDidUpdateStateCallback(updateState) - cd.CentralManagerDidDiscoverPeripheralCallback(discoverPeripheral) - cd.CentralManagerDidConnectPeripheralCallback(connectPeripheral) - cd.PeripheralDidDiscoverServicesCallback(discoverServices) - cd.PeripheralDidDiscoverCharacteristicsForServiceCallback(discoverCharacteristics) - cd.PeripheralDidUpdateValueForCharacteristicCallback(updateValue) + cd.CentralManagerDidUpdateStateCallback(didUpdateState) + cd.CentralManagerDidDiscoverPeripheralCallback(didDiscoverPeripheral) + cd.CentralManagerDidConnectPeripheralCallback(didConnectPeripheral) + cd.PeripheralDidDiscoverServicesCallback(didDiscoverServices) + cd.PeripheralDidDiscoverCharacteristicsForServiceCallback(didDiscoverCharacteristics) + cd.PeripheralDidUpdateValueForCharacteristicCallback(didUpdateValue) - hrm_uuid = ns.CBUUIDWithGoString("180D") - hrv_uuid = ns.CBUUIDWithGoString("2A37") - info_uuid = ns.CBUUIDWithGoString("180A") ble.cd = cd if cdLookup == nil {