Initial commit.

This commit is contained in:
Greg 2019-10-23 18:28:19 -04:00
commit 133b0883e3
4 changed files with 62073 additions and 0 deletions

327
ble_darwin.go Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

43
nswrap.yaml Normal file
View 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" ]