Initial commit.
This commit is contained in:
commit
133b0883e3
327
ble_darwin.go
Normal file
327
ble_darwin.go
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
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.state == (ns.CBManagerState)(ns.CBManagerStatePoweredOn) && b.wantScan {
|
||||||
|
go func() {
|
||||||
|
fmt.Printf("Go: Scanning\n")
|
||||||
|
//cm.ScanForPeripheralsWithServices(ns.NSArrayWithObjects(hrm_uuid), nil)
|
||||||
|
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() {
|
||||||
|
fmt.Printf("Go: stopping scan\n")
|
||||||
|
cm.StopScan()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BLE) Connect(p Peripheral) {
|
||||||
|
go func() {
|
||||||
|
cm.ConnectPeripheral(p.p, nil)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateState(c *ns.CBCentralManager) {
|
||||||
|
fmt.Printf("Go: did update state\n")
|
||||||
|
st := cm.CBManager.State()
|
||||||
|
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)
|
||||||
|
|
||||||
|
// discover all services on this device
|
||||||
|
|
||||||
|
p.DiscoverServices(nil)
|
||||||
|
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.events <- ConnectEvent{peripheral}
|
||||||
|
fmt.Printf("Go: discoverPeripheral returning\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
fmt.Printf("Go: updateValue returning\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
hrm_uuid *ns.CBUUID
|
||||||
|
hrv_uuid *ns.CBUUID
|
||||||
|
info_uuid *ns.CBUUID
|
||||||
|
cd *ns.CBDelegate
|
||||||
|
cm *ns.CBCentralManager
|
||||||
|
//peripheral *ns.CBPeripheral
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
87
ns/exports.go
Normal file
87
ns/exports.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package ns
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo CFLAGS: -x objective-c -fno-objc-arc
|
||||||
|
#cgo LDFLAGS: -framework Foundation -framework CoreBluetooth
|
||||||
|
#pragma clang diagnostic ignored "-Wformat-security"
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreBluetooth/CoreBluetooth.h>
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//export CBDelegateCentralManagerDidUpdateState
|
||||||
|
func CBDelegateCentralManagerDidUpdateState(o unsafe.Pointer, central unsafe.Pointer) {
|
||||||
|
CBDelegateMux.RLock()
|
||||||
|
cb := CBDelegateLookup[o].CentralManagerDidUpdateState
|
||||||
|
CBDelegateMux.RUnlock()
|
||||||
|
if cb == nil { return }
|
||||||
|
a1 := &CBCentralManager{}; a1.ptr = central
|
||||||
|
cb(a1)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export CBDelegateCentralManagerDidConnectPeripheral
|
||||||
|
func CBDelegateCentralManagerDidConnectPeripheral(o unsafe.Pointer, central unsafe.Pointer, peripheral unsafe.Pointer) {
|
||||||
|
CBDelegateMux.RLock()
|
||||||
|
cb := CBDelegateLookup[o].CentralManagerDidConnectPeripheral
|
||||||
|
CBDelegateMux.RUnlock()
|
||||||
|
if cb == nil { return }
|
||||||
|
a1 := &CBCentralManager{}; a1.ptr = central
|
||||||
|
a2 := &CBPeripheral{}; a2.ptr = peripheral
|
||||||
|
cb(a1, a2)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export CBDelegateCentralManagerDidDiscoverPeripheral
|
||||||
|
func CBDelegateCentralManagerDidDiscoverPeripheral(o unsafe.Pointer, central unsafe.Pointer, peripheral unsafe.Pointer, advertisementData unsafe.Pointer, RSSI unsafe.Pointer) {
|
||||||
|
CBDelegateMux.RLock()
|
||||||
|
cb := CBDelegateLookup[o].CentralManagerDidDiscoverPeripheral
|
||||||
|
CBDelegateMux.RUnlock()
|
||||||
|
if cb == nil { return }
|
||||||
|
a1 := &CBCentralManager{}; a1.ptr = central
|
||||||
|
a2 := &CBPeripheral{}; a2.ptr = peripheral
|
||||||
|
a3 := &NSDictionary{}; a3.ptr = advertisementData
|
||||||
|
a4 := &NSNumber{}; a4.ptr = RSSI
|
||||||
|
cb(a1, a2, a3, a4)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export CBDelegatePeripheralDidDiscoverServices
|
||||||
|
func CBDelegatePeripheralDidDiscoverServices(o unsafe.Pointer, peripheral unsafe.Pointer, error unsafe.Pointer) {
|
||||||
|
CBDelegateMux.RLock()
|
||||||
|
cb := CBDelegateLookup[o].PeripheralDidDiscoverServices
|
||||||
|
CBDelegateMux.RUnlock()
|
||||||
|
if cb == nil { return }
|
||||||
|
a1 := &CBPeripheral{}; a1.ptr = peripheral
|
||||||
|
a2 := &NSError{}; a2.ptr = error
|
||||||
|
cb(a1, a2)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export CBDelegatePeripheralDidDiscoverCharacteristicsForService
|
||||||
|
func CBDelegatePeripheralDidDiscoverCharacteristicsForService(o unsafe.Pointer, peripheral unsafe.Pointer, service unsafe.Pointer, error unsafe.Pointer) {
|
||||||
|
CBDelegateMux.RLock()
|
||||||
|
cb := CBDelegateLookup[o].PeripheralDidDiscoverCharacteristicsForService
|
||||||
|
CBDelegateMux.RUnlock()
|
||||||
|
if cb == nil { return }
|
||||||
|
a1 := &CBPeripheral{}; a1.ptr = peripheral
|
||||||
|
a2 := &CBService{}; a2.ptr = service
|
||||||
|
a3 := &NSError{}; a3.ptr = error
|
||||||
|
cb(a1, a2, a3)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export CBDelegatePeripheralDidUpdateValueForCharacteristic
|
||||||
|
func CBDelegatePeripheralDidUpdateValueForCharacteristic(o unsafe.Pointer, peripheral unsafe.Pointer, characteristic unsafe.Pointer, error unsafe.Pointer) {
|
||||||
|
CBDelegateMux.RLock()
|
||||||
|
cb := CBDelegateLookup[o].PeripheralDidUpdateValueForCharacteristic
|
||||||
|
CBDelegateMux.RUnlock()
|
||||||
|
if cb == nil { return }
|
||||||
|
a1 := &CBPeripheral{}; a1.ptr = peripheral
|
||||||
|
a2 := &CBCharacteristic{}; a2.ptr = characteristic
|
||||||
|
a3 := &NSError{}; a3.ptr = error
|
||||||
|
cb(a1, a2, a3)
|
||||||
|
}
|
61616
ns/main.go
Normal file
61616
ns/main.go
Normal file
File diff suppressed because it is too large
Load Diff
43
nswrap.yaml
Normal file
43
nswrap.yaml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
inputfiles:
|
||||||
|
- /System/Library/Frameworks/Foundation.framework/Headers/Foundation.h
|
||||||
|
- /System/Library/Frameworks/CoreBluetooth.framework/Headers/CoreBluetooth.h
|
||||||
|
|
||||||
|
classes:
|
||||||
|
- NSObject
|
||||||
|
- NSNumber
|
||||||
|
- NSData
|
||||||
|
- NSUUID
|
||||||
|
- CBManager
|
||||||
|
- CBCentralManager
|
||||||
|
- CBPeripheralManager
|
||||||
|
- CBPeripheral
|
||||||
|
- CBCentral
|
||||||
|
- CBService
|
||||||
|
- CBAttribute
|
||||||
|
- CBCharacteristic
|
||||||
|
- CBDescriptor
|
||||||
|
- CBError
|
||||||
|
- CBUUID
|
||||||
|
- CBAdvertisementData
|
||||||
|
- NSArray
|
||||||
|
- NSMutableArray
|
||||||
|
- NSDictionary
|
||||||
|
- NSEnumerator
|
||||||
|
- NSString
|
||||||
|
- NSAutoreleasePool
|
||||||
|
|
||||||
|
functions: [ NSMakeRange, dispatch_queue_create ]
|
||||||
|
enums: [ CB.* ]
|
||||||
|
frameworks: [ Foundation, CoreBluetooth ]
|
||||||
|
delegates:
|
||||||
|
CBDelegate:
|
||||||
|
CBCentralManagerDelegate:
|
||||||
|
- centralManagerDidUpdateState
|
||||||
|
- centralManagerDidDiscoverPeripheral
|
||||||
|
- centralManagerDidConnectPeripheral
|
||||||
|
CBPeripheralDelegate:
|
||||||
|
- peripheralDidDiscoverServices
|
||||||
|
- peripheralDidDiscoverCharacteristicsForService
|
||||||
|
- peripheralDidUpdateValueForCharacteristic
|
||||||
|
|
||||||
|
pragma: [ clang diagnostic ignored "-Wformat-security" ]
|
Loading…
Reference in New Issue
Block a user