package ble import "C" import ( "fmt" "runtime" "time" "unsafe" "git.wow.st/gmp/ble/gatt" "git.wow.st/gmp/ble/ns" ) // Types required for ble.go type bleState ns.CBManagerState type bleHandle struct { cd *ns.CBDelegate cm *ns.CBCentralManager } type Peripheral struct { Name string RSSI int Identifier string p *ns.CBPeripheral } type Service *ns.CBService type Characteristic *ns.CBCharacteristic // Internal global variables var cdLookup map[unsafe.Pointer]*BLE var pdLookup map[unsafe.Pointer]*BLE var pcache map[unsafe.Pointer]*Peripheral // Functions required by API //peripheralLookup returns a pointer to a BLE struct related to the given //Peripheral. func peripheralLookup(p Peripheral) *BLE { return pdLookup[p.p.Ptr()] } //newPeripheral creates a new Peripheral struct func newPeripheral(x *ns.CBPeripheral) Peripheral { if pcache == nil { pcache = make(map[unsafe.Pointer]*Peripheral) } else { if ret,ok := pcache[x.Ptr()]; ok { return *ret } } ret := Peripheral{ Name: peripheralName(x), Identifier: x.Identifier().UUIDString().String(), p: x, } pcache[x.Ptr()] = &ret return ret } func (p Peripheral) IsIncomplete() bool { return p.p == nil } 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() } //stringState returns a string version of the BLE state func (b *BLE) stringState() string { x := b.state switch (ns.NSInteger)(x) { case ns.CBManagerStateResetting: return "resetting" case ns.CBManagerStateUnsupported: return "unsupported" case ns.CBManagerStateUnauthorized: return "unauthorized" case ns.CBManagerStatePoweredOff: return "powered off" case ns.CBManagerStatePoweredOn: return "powered on" case ns.CBManagerStateUnknown: return "unknown" default: return "no state" } } //readyToScan returns true if the hardware is ready to initiate a scan func (b *BLE) readyToScan() bool { return b.state == (bleState)(ns.CBManagerStatePoweredOn) } //scan puts the BLE hardware into scanning mode func (b *BLE) scan() { b.handle.cm.ScanForPeripheralsWithServices(nil, nil) } //stopScan stops a scan in progress func (b *BLE) stopScan() { b.handle.cm.StopScan() } //connectPeripheral attempts to connect to a Peripheral func (b *BLE) connectPeripheral(x Peripheral) { fmt.Printf("BLE.Connect(): calling cm.ConnectPeripheral(%p)\n", x.p.Ptr()) b.handle.cm.ConnectPeripheral(x.p, nil) } //cancelConnection cancels an in-progress connection attempt func (b *BLE) cancelConnection(p Peripheral) { b.handle.cm.CancelPeripheralConnection(p.p) } //knownPeripheral returns a Peripheral that is known to the system without //scanning func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) { fmt.Printf("RetrievePeripheralsWithIdentifiers\n") ps := b.handle.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 p, false } } //DiscoverServices asks a Peripheral for its Services func (x Peripheral) DiscoverServices() { fmt.Printf("Discovering services on %s\n", x.Name) // discover all services on this device x.p.DiscoverServices(nil) } //DiscoverCharacteristics asks a Peripheral for the Characteristics related //to a Service func (p Peripheral) DiscoverCharacteristics(serv Service) { p.p.DiscoverCharacteristics(nil, serv) } //SetNotifyValue subscribes to a characteristic func (p Peripheral) SetNotifyValue(c Characteristic) { p.p.SetNotifyValue((ns.BOOL)(true), c) } //NewBLE returns a pointer to a BLE struct after setting up the OS //Bluetooth API. func NewBLE() *BLE { ps := Peripherals{items: make([]PeripheralListItem, 0)} ble := &BLE{events: make(chan interface{}), peripherals: ps} queue := ns.DispatchQueueCreate(ns.CharWithGoString("go_hrm_queue"), nil) cd := ns.CBDelegateAlloc() cd.CentralManagerDidUpdateStateCallback(didUpdateState) cd.CentralManagerDidDiscoverPeripheralCallback(didDiscoverPeripheral) cd.CentralManagerDidConnectPeripheralCallback(didConnectPeripheral) cd.CentralManagerDidDisconnectPeripheralCallback(didDisconnectPeripheral) cd.PeripheralDidDiscoverServicesCallback(didDiscoverServices) cd.PeripheralDidDiscoverCharacteristicsForServiceCallback(didDiscoverCharacteristics) cd.PeripheralDidUpdateValueForCharacteristicCallback(didUpdateValue) ble.handle.cd = cd if cdLookup == nil { cdLookup = make(map[unsafe.Pointer]*BLE, 0) } if pdLookup == nil { pdLookup = make(map[unsafe.Pointer]*BLE, 0) } // We defined our own queue because this won't work on the main queue. ble.handle.cm = ns.CBCentralManagerAlloc().InitWithDelegateQueue(cd, queue) cdLookup[ble.handle.cm.Ptr()] = ble // For debugging purposes, run GC every second to make sure things are // not over-released. go func() { for { runtime.GC() time.Sleep(time.Second * 30) } }() return ble } //Enable is not required for Darwin. func (b *BLE) Enable(v uintptr) { } // Core Bluetooth callback functions func didUpdateState(d ns.CBDelegate, c *ns.CBCentralManager) { fmt.Print("didUpdateState") b := cdLookup[c.Ptr()] st := c.CBManager.State() if st == (ns.CBManagerState)(ns.CBManagerStatePoweredOn) { b.ready = true b.connections.close = make(chan struct{}) } else { if b.ready { close(b.connections.close) b.connections.Lock() for _, item := range b.connections.items { fmt.Printf("Closing connection to %s\n", item.p.Name) b.handle.cm.CancelPeripheralConnection(item.p.p) } b.connections.items = b.connections.items[:0] b.connections.Unlock() b.ready = false } } b.setState(st) b.events <- UpdateStateEvent{State: b.stringState()} } func didDiscoverPeripheral(d_ ns.CBDelegate, c *ns.CBCentralManager, p *ns.CBPeripheral, d *ns.NSDictionary, rssi *ns.NSNumber) { b := cdLookup[c.Ptr()] peripheral := newPeripheral(p) peripheral.RSSI = (int)(rssi.IntValue()) _didDiscoverPeripheral(b, peripheral) } func _didDiscoverPeripheral(b *BLE, peripheral Peripheral) { if peripheral.Name == "" { return } if ok := b.peripherals.Add(peripheral); ok { pdLookup[peripheral.p.Ptr()] = b b.events <- DiscoverPeripheralEvent{Peripheral: peripheral} } } func didConnectPeripheral(d ns.CBDelegate, c *ns.CBCentralManager, p *ns.CBPeripheral) { fmt.Printf("Did connect peripheral\n") b := cdLookup[c.Ptr()] // set ourselves up as a peripheral delegate p.SetDelegate(b.handle.cd) uuidstring := p.Identifier().UUIDString().String() peripheral := newPeripheral(p) found := false b.peripherals.Lock() for _, item := range b.peripherals.items { if item.p.Identifier == uuidstring { peripheral = item.p found = true break } } b.peripherals.Unlock() if !found { peripheral.Name = peripheralName(p) if ok := b.peripherals.Add(peripheral); ok { pdLookup[p.Ptr()] = b } } b.connections.UpdateState(peripheral, "connected") b.events <- ConnectEvent{peripheral} fmt.Printf("Go: didConnectPeripheral returning\n") } func didDisconnectPeripheral(d ns.CBDelegate, c *ns.CBCentralManager, p *ns.CBPeripheral, e *ns.NSError) { fmt.Printf("Did disconnect peripheral\n"); b := cdLookup[c.Ptr()] uuidstring := p.Identifier().UUIDString().String() peripheral := newPeripheral(p) found := false b.peripherals.Lock() for _, item := range b.peripherals.items { if item.p.Identifier == uuidstring { peripheral = item.p found = true break } } b.peripherals.Unlock() if !found { peripheral.Name = peripheralName(p) if ok := b.peripherals.Add(peripheral); ok { pdLookup[p.Ptr()] = b } } go func() { b.connections.UpdateState(peripheral, "cancelled") b.events <- DisconnectEvent{peripheral} }() } func didDiscoverServices(d ns.CBDelegate, 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 { serv := o.CBService() uuid := serv.UUID().UUIDString().String() b.events <-DiscoverServiceEvent{ Peripheral: newPeripheral(p), Gatt: gatt.Service{uuid}, Service: serv, } return true }) fmt.Printf("Go: didDiscoverServices returning\n") } func didDiscoverCharacteristics(d ns.CBDelegate, 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 { chr := o.CBCharacteristic() chuuid := chr.UUID() fmt.Printf("------%s\n", chuuid.UUIDString()) b.events <-DiscoverCharacteristicEvent{ Peripheral: newPeripheral(p), Service: s, Characteristic: chr, Gatt: gatt.Characteristic{chuuid.UUIDString().String()}, } return true }) fmt.Printf("Go: didDiscoverCharacteristics returning\n") } func didUpdateValue(d ns.CBDelegate, p *ns.CBPeripheral, chr *ns.CBCharacteristic, e *ns.NSError) { b := pdLookup[p.Ptr()] v := chr.Value() b.events <-UpdateValueEvent{ Peripheral: newPeripheral(p), Characteristic: chr, Data: C.GoBytes(v.Bytes(), (C.int)(v.Length())), } } // internal convenience functions func peripheralName(p *ns.CBPeripheral) string { var ret string nsname := p.Name() if nsname.Ptr() != nil { ret = nsname.String() } return ret } func (b *BLE) setState(x ns.CBManagerState) { b.Lock() defer b.Unlock() b.state = (bleState)(x) if b.ready && b.wantScan { fmt.Printf("Go: Scanning\n") b.scan() b.wantScan = false } }