Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 2
version: 2.1

jobs:
build:
Expand All @@ -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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ smoketest-linux:
smoketest-windows:
# Test on Windows.
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/scanner

smoketest-macos:
# Test on macos.
GOOS=darwin CGO_ENABLED=1 go build -o /tmp/go-build-discard ./examples/scanner
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Go Bluetooth

[![CircleCI](https://circleci.com/gh/tinygo-org/bluetooth/tree/master.svg?style=svg)](https://circleci.com/gh/tinygo-org/bluetooth/tree/master)
[![CircleCI](https://circleci.com/gh/tinygo-org/bluetooth/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/bluetooth/tree/dev)
[![GoDoc](https://godoc.org/github.com/tinygo-org/bluetooth?status.svg)](https://godoc.org/github.com/tinygo-org/bluetooth)

This package attempts to build a cross-platform Bluetooth Low Energy module for Go. It currently supports the following systems:

| | Windows | Linux | Nordic chips |
| -------------------------------- | ------------------ | ------------------ | ------------------ |
| API used | WinRT | BlueZ (over D-Bus) | SoftDevice |
| Scanning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Connect to peripheral | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Write peripheral characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Receive notifications | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Advertisement | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Local services | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Local characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Send notifications | :x: | :heavy_check_mark: | :heavy_check_mark: |
| | 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: | :heavy_check_mark: |
| Write peripheral characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Receive notifications | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Advertisement | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
| Local services | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
| Local characteristics | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |
| Send notifications | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: |

## Baremetal support

Expand Down Expand Up @@ -52,7 +52,7 @@ Flashing will then need to be done a bit differently, using the CMSIS-DAP interf

## API stability

**The API is not stable!** Because many features are not yet implemented and some platforms (e.g. MacOS) are not yet supported, it's hard to say what a good API will be. Therefore, if you want stability you should pick a particular git commit and use that. Go modules can be useful for this purpose.
**The API is not stable!** Because many features are not yet implemented and some platforms (e.g. Windows and macOS) are not yet fully supported, it's hard to say what a good API will be. Therefore, if you want stability you should pick a particular git commit and use that. Go modules can be useful for this purpose.

Some things that will probably change:

Expand Down
124 changes: 124 additions & 0 deletions adapter_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package bluetooth

import (
"errors"
"time"

"github.com/JuulLabs-OSS/cbgo"
)

// Adapter is a connection to BLE devices.
type Adapter struct {
cmd *centralManagerDelegate
pmd *peripheralManagerDelegate

cm cbgo.CentralManager
pm cbgo.PeripheralManager

peripheralFoundHandler func(*Adapter, ScanResult)
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),
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.cmd = &centralManagerDelegate{a: a}
a.cm.SetDelegate(a.cmd)
select {
case <-a.poweredChan:
case <-time.NewTimer(10 * time.Second).C:
return errors.New("timeout enabling CentralManager")
}
a.poweredChan = nil

// wait until powered?
a.pmd = &peripheralManagerDelegate{a: a}
a.pm.SetDelegate(a.pmd)

return nil
}

// CentralManager delegate functions

type centralManagerDelegate struct {
cbgo.CentralManagerDelegateBase

a *Adapter
}

// CentralManagerDidUpdateState when central manager state updated.
func (cmd *centralManagerDelegate) CentralManagerDidUpdateState(cmgr cbgo.CentralManager) {
// powered on?
if cmgr.State() == cbgo.ManagerStatePoweredOn {
close(cmd.a.poweredChan)
}

// TODO: handle other state changes.
}

// DidDiscoverPeripheral when peripheral is discovered.
func (cmd *centralManagerDelegate) DidDiscoverPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral,
advFields cbgo.AdvFields, rssi int) {
if cmd.a.peripheralFoundHandler != nil {
sr := makeScanResult(prph, advFields, rssi)
cmd.a.peripheralFoundHandler(cmd.a, sr)
}
}

// DidConnectPeripheral when peripheral is connected.
func (cmd *centralManagerDelegate) DidConnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral) {
// Unblock now that we're connected.
cmd.a.connectChan <- prph
}

// makeScanResult creates a ScanResult when peripheral is found.
func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) ScanResult {
uuid, _ := ParseUUID(prph.Identifier().String())

var serviceUUIDs []UUID
for _, u := range advFields.ServiceUUIDs {
parsedUUID, _ := ParseUUID(u.String())
serviceUUIDs = append(serviceUUIDs, parsedUUID)
}

// Peripheral UUID is randomized on macOS, which means to
// different centrals it will appear to have a different UUID.
return ScanResult{
RSSI: int16(rssi),
Address: Address{
UUID: uuid,
},
AdvertisementPayload: &advertisementFields{
AdvertisementFields{
LocalName: advFields.LocalName,
ServiceUUIDs: serviceUUIDs,
},
},
}
}

// PeripheralManager delegate functions

type peripheralManagerDelegate struct {
cbgo.PeripheralManagerDelegateBase

a *Adapter
}
4 changes: 2 additions & 2 deletions adapter_nrf528xx.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ func handleEvent() {
// callback.
scanReportBuffer.len = byte(advReport.data.len)
globalScanResult.RSSI = int16(advReport.rssi)
globalScanResult.Address.MAC = advReport.peer_addr.addr
globalScanResult.Address.IsRandom = advReport.peer_addr.bitfield_addr_type() != 0
globalScanResult.Address.Set(advReport.peer_addr.addr)
globalScanResult.Address.SetRandom(advReport.peer_addr.bitfield_addr_type() != 0)
globalScanResult.AdvertisementPayload = &scanReportBuffer
// Signal to the main thread that there was a scan report.
// Scanning will be resumed (from the main thread) once the scan
Expand Down
41 changes: 35 additions & 6 deletions gap.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@ var (
errAdvertisementPacketTooBig = errors.New("bluetooth: advertisement packet overflows")
)

// MACAddress contains a Bluetooth address which is a MAC address.
type MACAddress struct {
// MAC address of the Bluetooth device.
MAC

isRandom bool
}

// IsRandom if the address is randomly created.
func (mac MACAddress) IsRandom() bool {
return mac.isRandom
}

// SetRandom if is a random address.
func (mac MACAddress) SetRandom(val bool) {
mac.isRandom = val
}

// Set the address
func (mac MACAddress) Set(val interface{}) {
m := val.(MAC)
mac.MAC = m
}

// AdvertisementOptions configures an advertisement instance. More options may
// be added over time.
type AdvertisementOptions struct {
Expand Down Expand Up @@ -41,11 +65,14 @@ func NewDuration(interval time.Duration) Duration {
// Connection is a numeric identifier that indicates a connection handle.
type Connection uint16

// Address contains a Bluetooth address, which is a MAC address plus some extra
// Addresser contains a Bluetooth address, which is a MAC address plus some extra
// information.
type Address struct {
// The MAC address of a Bluetooth device.
MAC
type Addresser interface {
// String of the address
String() string

// Set the address
Set(val interface{})

// Is this address a random address?
// Bluetooth addresses are roughly split in two kinds: public
Expand All @@ -54,14 +81,16 @@ type Address struct {
// random. Sometimes, it contains a hash.
// For more information:
// https://www.novelbits.io/bluetooth-address-privacy-ble/
IsRandom bool
// Set the address
SetRandom(bool)
IsRandom() bool
}

// ScanResult contains information from when an advertisement packet was
// received. It is passed as a parameter to the callback of the Scan method.
type ScanResult struct {
// Bluetooth address of the scanned device.
Address Address
Address Addresser

// RSSI the last time a packet from this device has been received.
RSSI int16
Expand Down
Loading