package ble import "C" import ( "encoding/binary" "fmt" "runtime" "sync" "time" "git.wow.st/gmp/ble/ns" ) type BLE struct { events chan interface{} state ns.CBManagerState peripherals Peripherals hr int ready, wantScan bool sync.Mutex } var ble *BLE type State string type Peripheral struct { Name string RSSI int identifier *ns.NSUUID p *ns.CBPeripheral } func (p Peripheral) Identifier() string { return p.identifier.UUIDString().String() } type PeripheralListItem struct { p Peripheral seen time.Time } type Peripherals struct { items []PeripheralListItem sync.Mutex } func (ps *Peripherals) Add(x Peripheral) bool { ps.Lock() defer ps.Unlock() found := false for n,item := range ps.items { if item.p.identifier.IsEqual(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() x.p.GC() item := PeripheralListItem {p: x, seen: time.Now()} ps.items = append(ps.items, item) return true } type UpdateStateEvent struct { State string } type DiscoverEvent struct { Peripheral Peripheral } type ConnectEvent struct { Peripheral Peripheral } func (b *BLE) Events() chan interface{} { return b.events } func (b *BLE) HR() int { b.Lock() defer b.Unlock() return b.hr } func stringState(x ns.CBManagerState) string { switch x { case (ns.CBManagerState)(ns.CBManagerStateResetting): return "resetting" case (ns.CBManagerState)(ns.CBManagerStateUnsupported): return "unsupported" case (ns.CBManagerState)(ns.CBManagerStateUnauthorized): return "unauthorized" case (ns.CBManagerState)(ns.CBManagerStatePoweredOff): return "powered off" case (ns.CBManagerState)(ns.CBManagerStatePoweredOn): return "powered on" case (ns.CBManagerState)(ns.CBManagerStateUnknown): return "unknown" default: return "no state" } } func (b *BLE) State() string { b.Lock() defer b.Unlock() return stringState(ble.state) } func (b *BLE) setState(x ns.CBManagerState) { b.Lock() defer b.Unlock() b.state = x if b.ready && b.wantScan { go func() { fmt.Printf("Go: Scanning\n") cm.ScanForPeripheralsWithServices(nil, nil) }() } } func (b *BLE) Scan() { b.Lock() if b.state != (ns.CBManagerState)(ns.CBManagerStatePoweredOn) { b.wantScan = true b.Unlock() } else { b.Unlock() fmt.Printf("Go: Scanning\n") cm.ScanForPeripheralsWithServices(nil, nil) } } func (b *BLE) StopScan() { b.Lock() if !b.ready { b.Unlock() return } b.Unlock() fmt.Printf("Go: stopping scan\n") cm.StopScan() } func (b *BLE) Connect(p Peripheral) { b.Lock() if !b.ready { b.Unlock() return } b.Unlock() cm.ConnectPeripheral(p.p, nil) fmt.Printf("cm.ConnectPeripheral() returned\n") } func updateState(c *ns.CBCentralManager) { fmt.Printf("Go: did update state\n") st := cm.CBManager.State() if st == (ns.CBManagerState)(ns.CBManagerStatePoweredOn) { ble.ready = true } else { ble.ready = false } ble.setState(st) ble.events <- UpdateStateEvent{State: stringState(st)} } func peripheralName(p *ns.CBPeripheral) string { var ret string nsname := p.Name() if nsname.Ptr() != nil { ret = nsname.String() } return ret } func discoverPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral, d *ns.NSDictionary, rssi *ns.NSNumber) { peripheral := Peripheral{ Name: peripheralName(p), RSSI: (int)(rssi.IntValue()), identifier: p.Identifier(), p: p, } if peripheral.Name == "" { return } if ok := ble.peripherals.Add(peripheral); ok { ble.events <- DiscoverEvent{Peripheral: peripheral} } } func connectPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral) { fmt.Printf("Did connect peripheral\n") // set ourselves up as a peripheral delegate p.SetDelegate(cd) id := p.Identifier() peripheral := Peripheral{p: p, identifier: id} found := false ble.peripherals.Lock() for _, item := range ble.peripherals.items { if item.p.identifier == id { peripheral = item.p found = true break } } ble.peripherals.Unlock() if !found { peripheral.Name = peripheralName(p) ble.peripherals.Add(peripheral) } ble.events <- ConnectEvent{peripheral} fmt.Printf("Go: connectPeripheral returning\n") } func DiscoverServices(x Peripheral) { fmt.Printf("Discovering services on %s\n", x.Name) p := x.p // discover all services on this device p.DiscoverServices(nil) } func discoverServices(p *ns.CBPeripheral, e *ns.NSError) { fmt.Printf("Did discover services\n") p.Services().ObjectEnumerator().ForIn(func(o *ns.Id) bool { serv := o.CBService() uuid := serv.UUID() switch { case uuid.IsEqualTo(hrm_uuid): fmt.Printf("--heart rate monitor service\n") p.DiscoverCharacteristics(nil, serv) case uuid.IsEqualTo(info_uuid): fmt.Printf("--device information service\n") p.DiscoverCharacteristics(nil, serv) default: fmt.Printf("--unknown service\n") } return true }) fmt.Printf("Go: discoverServices returning\n") } func hr(d *ns.NSData) int { if l := int(d.Length()); l < 4 { return 0 } x := C.GoBytes(d.Bytes(), 4) flags := x[0] if flags&0x80 != 0 { // uint16 format return int(binary.BigEndian.Uint16(x[1:2])) } else { return int(x[1]) } } func discoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError) { fmt.Printf("Did discover characteristics\n") uuid := s.UUID() fmt.Printf("----%s\n", uuid.UUIDString()) if uuid.IsEqualTo(hrm_uuid) { s.Characteristics().ObjectEnumerator().ForIn(func(o *ns.Id) bool { chr := o.CBCharacteristic() chuuid := chr.UUID() fmt.Printf("------%s\n", chuuid.UUIDString()) if chuuid.IsEqualTo(hrv_uuid) { p.SetNotifyValue(1, chr) v := chr.Value() fmt.Println(hr(v)) } return true }) } fmt.Printf("Go: discoverCharacteristics returning\n") } func updateValue(p *ns.CBPeripheral, chr *ns.CBCharacteristic, e *ns.NSError) { if chr.UUID().IsEqualTo(hrv_uuid) { v := chr.Value() ble.Lock() ble.hr = hr(v) ble.Unlock() fmt.Printf("Heart rate: %d\n", ble.hr) } } var ( hrm_uuid *ns.CBUUID hrv_uuid *ns.CBUUID info_uuid *ns.CBUUID cd *ns.CBDelegate cm *ns.CBCentralManager ) func NewBLE() *BLE { if ble != nil { return 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(updateState) cd.CentralManagerDidDiscoverPeripheralCallback(discoverPeripheral) cd.CentralManagerDidConnectPeripheralCallback(connectPeripheral) cd.PeripheralDidDiscoverServicesCallback(discoverServices) cd.PeripheralDidDiscoverCharacteristicsForServiceCallback(discoverCharacteristics) cd.PeripheralDidUpdateValueForCharacteristicCallback(updateValue) hrm_uuid = ns.CBUUIDWithGoString("180D") hrv_uuid = ns.CBUUIDWithGoString("2A37") info_uuid = ns.CBUUIDWithGoString("180A") // We defined our own queue because this won't work on the main queue. cm = ns.CBCentralManagerAlloc().InitWithDelegateQueue(cd, queue) // 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 }