macos: completed initial implementation
Signed-off-by: Ron Evans <ron@hybridgroup.com>
This commit is contained in:
parent
27cc0b725a
commit
5f44bb4a96
5 changed files with 188 additions and 45 deletions
|
@ -1,4 +1,4 @@
|
|||
version: 2
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -22,3 +22,24 @@ jobs:
|
|||
- run:
|
||||
name: "Run Windows smoke tests"
|
||||
command: make smoketest-windows
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "Install dependencies"
|
||||
command: |
|
||||
curl https://dl.google.com/go/go1.14.darwin-amd64.tar.gz -o go1.14.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
||||
- run: go version
|
||||
- run:
|
||||
name: "Run macOS smoke tests"
|
||||
command: make smoketest-macos
|
||||
|
||||
workflows:
|
||||
test-all:
|
||||
jobs:
|
||||
- build
|
||||
- build-macos
|
||||
|
|
|
@ -8,8 +8,8 @@ This package attempts to build a cross-platform Bluetooth Low Energy module for
|
|||
| | Windows | Linux | Nordic chips | macOS |
|
||||
| -------------------------------- | ------------------ | ------------------ | ------------------ | ------------------ |
|
||||
| API used | WinRT | BlueZ (over D-Bus) | SoftDevice | CoreBluetooth |
|
||||
| Scanning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Connect to peripheral | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
|
||||
| Scanning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Connect to peripheral | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Write peripheral characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
|
||||
| Receive notifications | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
|
||||
| Advertisement | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package bluetooth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/JuulLabs-OSS/cbgo"
|
||||
)
|
||||
|
||||
// Adapter is a connection to BLE devices.
|
||||
type Adapter struct {
|
||||
cbgo.CentralManagerDelegateBase
|
||||
cbgo.PeripheralManagerDelegateBase
|
||||
|
@ -12,24 +16,39 @@ type Adapter struct {
|
|||
pm cbgo.PeripheralManager
|
||||
|
||||
peripheralFoundHandler func(*Adapter, ScanResult)
|
||||
cancelChan chan struct{}
|
||||
scanChan chan error
|
||||
poweredChan chan error
|
||||
connectChan chan cbgo.Peripheral
|
||||
}
|
||||
|
||||
// DefaultAdapter is the default adapter on the system.
|
||||
//
|
||||
// Make sure to call Enable() before using it to initialize the adapter.
|
||||
var DefaultAdapter = &Adapter{
|
||||
cm: cbgo.NewCentralManager(nil),
|
||||
pm: cbgo.NewPeripheralManager(nil),
|
||||
cm: cbgo.NewCentralManager(nil),
|
||||
pm: cbgo.NewPeripheralManager(nil),
|
||||
connectChan: make(chan cbgo.Peripheral),
|
||||
}
|
||||
|
||||
// Enable configures the BLE stack. It must be called before any
|
||||
// Bluetooth-related calls (unless otherwise indicated).
|
||||
func (a *Adapter) Enable() error {
|
||||
if a.poweredChan != nil {
|
||||
return errors.New("already calling Enable function")
|
||||
}
|
||||
|
||||
// wait until powered
|
||||
a.poweredChan = make(chan error)
|
||||
a.cm.SetDelegate(a)
|
||||
// TODO: wait until powered
|
||||
a.pm.SetDelegate(a)
|
||||
// TODO: wait until powered
|
||||
select {
|
||||
case <-a.poweredChan:
|
||||
case <-time.NewTimer(10 * time.Second).C:
|
||||
return errors.New("timeout enabling CentralManager")
|
||||
}
|
||||
a.poweredChan = nil
|
||||
|
||||
// wait until powered?
|
||||
//a.pm.SetDelegate(a)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -38,6 +57,12 @@ func (a *Adapter) Enable() error {
|
|||
|
||||
// CentralManagerDidUpdateState when central manager state updated.
|
||||
func (a *Adapter) CentralManagerDidUpdateState(cmgr cbgo.CentralManager) {
|
||||
// powered on?
|
||||
if cmgr.State() == cbgo.ManagerStatePoweredOn {
|
||||
close(a.poweredChan)
|
||||
}
|
||||
|
||||
// TODO: handle other state changes.
|
||||
}
|
||||
|
||||
// DidDiscoverPeripheral when peripheral is discovered.
|
||||
|
@ -51,6 +76,8 @@ func (a *Adapter) DidDiscoverPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peri
|
|||
|
||||
// DidConnectPeripheral when peripheral is connected.
|
||||
func (a *Adapter) DidConnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral) {
|
||||
// Unblock now that we're connected.
|
||||
a.connectChan <- prph
|
||||
}
|
||||
|
||||
// DidDisconnectPeripheral when peripheral is disconnected.
|
||||
|
@ -89,23 +116,24 @@ func (a *Adapter) CentralDidUnsubscribe(pmgr cbgo.PeripheralManager, cent cbgo.C
|
|||
|
||||
// makeScanResult creates a ScanResult when peripheral is found.
|
||||
func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) ScanResult {
|
||||
var u [16]byte
|
||||
copy(u[:], prph.Identifier())
|
||||
uuid := NewUUID(u)
|
||||
uuid, _ := ParseUUID(prph.Identifier().String())
|
||||
|
||||
// TODO: create a list of serviceUUIDs.
|
||||
var serviceUUIDs []UUID
|
||||
for _, u := range advFields.ServiceUUIDs {
|
||||
parsedUUID, _ := ParseUUID(u.String())
|
||||
serviceUUIDs = append(serviceUUIDs, parsedUUID)
|
||||
}
|
||||
|
||||
// It is never a random address on macOS.
|
||||
return ScanResult{
|
||||
RSSI: int16(rssi),
|
||||
Address: Address{
|
||||
UUID: uuid,
|
||||
//IsRandom: prph.Identifier == "random",
|
||||
},
|
||||
AdvertisementPayload: &advertisementFields{
|
||||
AdvertisementFields{
|
||||
LocalName: advFields.LocalName,
|
||||
// TODO: fill in this info
|
||||
//ServiceUUIDs: serviceUUIDs,
|
||||
LocalName: advFields.LocalName,
|
||||
ServiceUUIDs: serviceUUIDs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
106
gap_darwin.go
106
gap_darwin.go
|
@ -2,27 +2,26 @@ package bluetooth
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/JuulLabs-OSS/cbgo"
|
||||
)
|
||||
|
||||
// Address contains a Bluetooth address, which is a MAC address plus some extra
|
||||
// information.
|
||||
// Address contains a Bluetooth address, which on macOS instead of a MAC address
|
||||
// is instead a UUID.
|
||||
type Address struct {
|
||||
// UUID if this is macOS.
|
||||
// UUID since this is macOS.
|
||||
UUID
|
||||
|
||||
isRandom bool
|
||||
}
|
||||
|
||||
// IsRandom if the address is randomly created.
|
||||
// IsRandom ignored on macOS.
|
||||
func (ad Address) IsRandom() bool {
|
||||
return ad.isRandom
|
||||
return false
|
||||
}
|
||||
|
||||
// SetRandom if is a random address.
|
||||
// SetRandom ignored on macOS.
|
||||
func (ad Address) SetRandom(val bool) {
|
||||
ad.isRandom = val
|
||||
}
|
||||
|
||||
// Set the address
|
||||
|
@ -37,7 +36,7 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
|
|||
return errors.New("must provide callback to Scan function")
|
||||
}
|
||||
|
||||
if a.cancelChan != nil {
|
||||
if a.scanChan != nil {
|
||||
return errors.New("already calling Scan function")
|
||||
}
|
||||
|
||||
|
@ -46,24 +45,21 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
|
|||
// Channel that will be closed when the scan is stopped.
|
||||
// Detecting whether the scan is stopped can be done by doing a non-blocking
|
||||
// read from it. If it succeeds, the scan is stopped.
|
||||
cancelChan := make(chan struct{})
|
||||
a.cancelChan = cancelChan
|
||||
a.scanChan = make(chan error)
|
||||
|
||||
a.cm.Scan(nil, &cbgo.CentralManagerScanOpts{
|
||||
AllowDuplicates: true,
|
||||
AllowDuplicates: false,
|
||||
})
|
||||
|
||||
for {
|
||||
// Check whether the scan is stopped. This is necessary to avoid a race
|
||||
// condition between the signal channel and the cancelScan channel when
|
||||
// the callback calls StopScan() (no new callbacks may be called after
|
||||
// StopScan is called).
|
||||
select {
|
||||
case <-cancelChan:
|
||||
// stop scanning here?
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
// Check whether the scan is stopped. This is necessary to avoid a race
|
||||
// condition between the signal channel and the cancelScan channel when
|
||||
// the callback calls StopScan() (no new callbacks may be called after
|
||||
// StopScan is called).
|
||||
select {
|
||||
case <-a.scanChan:
|
||||
close(a.scanChan)
|
||||
a.scanChan = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,13 +67,67 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
|
|||
// callback to stop the current scan. If no scan is in progress, an error will
|
||||
// be returned.
|
||||
func (a *Adapter) StopScan() error {
|
||||
if a.cancelChan != nil {
|
||||
return errors.New("already calling Scan function")
|
||||
if a.scanChan == nil {
|
||||
return errors.New("not calling Scan function")
|
||||
}
|
||||
|
||||
a.scanChan <- nil
|
||||
a.cm.StopScan()
|
||||
close(a.cancelChan)
|
||||
a.cancelChan = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Device is a connection to a remote peripheral.
|
||||
type Device struct {
|
||||
cbgo.PeripheralDelegateBase
|
||||
|
||||
cm cbgo.CentralManager
|
||||
}
|
||||
|
||||
// Connect starts a connection attempt to the given peripheral device address.
|
||||
func (a *Adapter) Connect(address Addresser, params ConnectionParams) (*Device, error) {
|
||||
adr := address.(Address)
|
||||
uuid, err := cbgo.ParseUUID(adr.UUID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prphs := a.cm.RetrievePeripheralsWithIdentifiers([]cbgo.UUID{uuid})
|
||||
if len(prphs) == 0 {
|
||||
return nil, fmt.Errorf("Connect failed: no peer with address: %s", adr.UUID.String())
|
||||
}
|
||||
a.cm.Connect(prphs[0], nil)
|
||||
|
||||
// wait on channel for connect
|
||||
select {
|
||||
case p := <-a.connectChan:
|
||||
d := &Device{
|
||||
cm: a.cm,
|
||||
}
|
||||
|
||||
p.SetDelegate(d)
|
||||
return d, nil
|
||||
case <-time.NewTimer(10 * time.Second).C:
|
||||
return nil, errors.New("timeout on Connect")
|
||||
}
|
||||
}
|
||||
|
||||
// Peripheral delegate functions
|
||||
|
||||
func (d *Device) DidDiscoverServices(prph cbgo.Peripheral, err error) {
|
||||
}
|
||||
func (d *Device) DidDiscoverCharacteristics(prph cbgo.Peripheral, svc cbgo.Service, err error) {
|
||||
}
|
||||
func (d *Device) DidDiscoverDescriptors(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
|
||||
}
|
||||
func (d *Device) DidUpdateValueForCharacteristic(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
|
||||
}
|
||||
func (d *Device) DidUpdateValueForDescriptor(prph cbgo.Peripheral, dsc cbgo.Descriptor, err error) {
|
||||
}
|
||||
func (d *Device) DidWriteValueForCharacteristic(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
|
||||
}
|
||||
func (d *Device) DidWriteValueForDescriptor(prph cbgo.Peripheral, dsc cbgo.Descriptor, err error) {
|
||||
}
|
||||
func (d *Device) DidUpdateNotificationState(prph cbgo.Peripheral, chr cbgo.Characteristic, err error) {
|
||||
}
|
||||
func (d *Device) DidReadRSSI(prph cbgo.Peripheral, rssi int, err error) {
|
||||
}
|
||||
|
|
44
gattc_darwin.go
Normal file
44
gattc_darwin.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package bluetooth
|
||||
|
||||
// DiscoverServices starts a service discovery procedure. Pass a list of service
|
||||
// UUIDs you are interested in to this function. Either a slice of all services
|
||||
// is returned (of the same length as the requested UUIDs and in the same
|
||||
// order), or if some services could not be discovered an error is returned.
|
||||
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeviceService is a BLE service on a connected peripheral device.
|
||||
type DeviceService struct {
|
||||
}
|
||||
|
||||
// DiscoverCharacteristics discovers characteristics in this service. Pass a
|
||||
// list of characteristic UUIDs you are interested in to this function. Either a
|
||||
// list of all requested services is returned, or if some services could not be
|
||||
// discovered an error is returned. If there is no error, the characteristics
|
||||
// slice has the same length as the UUID slice with characteristics in the same
|
||||
// order in the slice as in the requested UUID list.
|
||||
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeviceCharacteristic is a BLE characteristic on a connected peripheral
|
||||
// device.
|
||||
type DeviceCharacteristic struct {
|
||||
}
|
||||
|
||||
// WriteWithoutResponse replaces the characteristic value with a new value. The
|
||||
// call will return before all data has been written. A limited number of such
|
||||
// writes can be in flight at any given time. This call is also known as a
|
||||
// "write command" (as opposed to a write request).
|
||||
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// EnableNotifications enables notifications in the Client Characteristic
|
||||
// Configuration Descriptor (CCCD). This means that most peripherals will send a
|
||||
// notification with a new value every time the value of the characteristic
|
||||
// changes.
|
||||
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue