Skip to content

Commit

Permalink
all: simplify advertisement configuration
Browse files Browse the repository at this point in the history
This changes the previous raw advertisement packets to structured
advertisement configuration. That means you can set the local name not
with a raw byte array but with a normal string.

While this departs a bit from the original low-level interface as is
often used on microcontroller BLE stacks, it is certainly easier to use
and better matches higher level APIs that are commonly provided by
general-purpose operating systems. If there is a need for raw BLE
packets (for baremetal systems only), this can easily be added in the
future.
  • Loading branch information
aykevl committed Jun 1, 2020
1 parent 0474d7b commit 086c797
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 93 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ Flashing will then need to be done a bit differently, using the CMSIS-DAP interf
Some things that will probably change:

* Add options to the `Scan` method, for example to filter on UUID.
* Change advertisement configuration to accept structured BLE packets instead of raw packets.
* Connect/disconnect events.
* The behavior around advertisement. Nordic SoftDevices stop advertising when a device connects, which is somewhat unintuitive.

Expand Down
11 changes: 4 additions & 7 deletions examples/advertisement/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@ import (
"github.com/tinygo-org/bluetooth"
)

// flags + local name
var advPayload = []byte("\x02\x01\x06" + "\x07\x09TinyGo")

var adapter = bluetooth.DefaultAdapter

func main() {
must("enable BLE stack", adapter.Enable())
adv := adapter.NewAdvertisement()
options := &bluetooth.AdvertiseOptions{
Interval: bluetooth.NewAdvertiseInterval(100),
}
must("config adv", adv.Configure(advPayload, nil, options))
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
LocalName: "Go Bluetooth",
Interval: bluetooth.NewAdvertisementInterval(100),
}))
must("start adv", adv.Start())

println("advertising...")
Expand Down
11 changes: 4 additions & 7 deletions examples/heartrate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import (
"github.com/tinygo-org/bluetooth"
)

// flags + local name
var advPayload = []byte("\x02\x01\x06" + "\x07\x09TinyGo")

var adapter = bluetooth.DefaultAdapter

// TODO: use atomics to access this value.
Expand All @@ -19,10 +16,10 @@ func main() {
adapter.SetEventHandler(handleBluetoothEvents)
must("enable BLE stack", adapter.Enable())
adv := adapter.NewAdvertisement()
options := &bluetooth.AdvertiseOptions{
Interval: bluetooth.NewAdvertiseInterval(100),
}
must("config adv", adv.Configure(advPayload, nil, options))
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
LocalName: "Go HRS",
Interval: bluetooth.NewAdvertisementInterval(100),
}))
must("start adv", adv.Start())

var heartRateMeasurement bluetooth.Characteristic
Expand Down
11 changes: 4 additions & 7 deletions examples/ledcolor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import (
"github.com/tinygo-org/bluetooth"
)

// flags + local name
var advPayload = []byte("\x02\x01\x06" + "\x0b\x09LED colors")

var adapter = bluetooth.DefaultAdapter

// TODO: use atomics to access this value.
Expand All @@ -27,10 +24,10 @@ func main() {
adapter.SetEventHandler(handleBluetoothEvents)
must("enable BLE stack", adapter.Enable())
adv := adapter.NewAdvertisement()
options := &bluetooth.AdvertiseOptions{
Interval: bluetooth.NewAdvertiseInterval(100),
}
must("config adv", adv.Configure(advPayload, nil, options))
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
LocalName: "LED colors",
Interval: bluetooth.NewAdvertisementInterval(100),
}))
must("start adv", adv.Start())

var ledColorCharacteristic bluetooth.Characteristic
Expand Down
57 changes: 46 additions & 11 deletions gap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,31 @@ package bluetooth
import "errors"

var (
errScanning = errors.New("bluetooth: a scan is already in progress")
errNotScanning = errors.New("bluetooth: there is no scan in progress")
errMalformedAdvertisementPayload = errors.New("bluetooth: malformed advertisement packet")
errScanning = errors.New("bluetooth: a scan is already in progress")
errNotScanning = errors.New("bluetooth: there is no scan in progress")
errAdvertisementPacketTooBig = errors.New("bluetooth: advertisement packet overflows")
)

// AdvertiseOptions configures everything related to BLE advertisements.
type AdvertiseOptions struct {
Interval AdvertiseInterval
// AdvertisementOptions configures an advertisement instance. More options may
// be added over time.
type AdvertisementOptions struct {
// The (complete) local name that will be advertised. Optional, omitted if
// this is a zero-length string.
LocalName string

// Interval in BLE-specific units. Create an interval by using
// NewAdvertiseInterval.
Interval AdvertisementInterval
}

// AdvertiseInterval is the advertisement interval in 0.625µs units.
type AdvertiseInterval uint32
// AdvertisementInterval is the advertisement interval in 0.625µs units.
type AdvertisementInterval uint32

// NewAdvertiseInterval returns a new advertisement interval, based on an
// NewAdvertisementInterval returns a new advertisement interval, based on an
// interval in milliseconds.
func NewAdvertiseInterval(intervalMillis uint32) AdvertiseInterval {
func NewAdvertisementInterval(intervalMillis uint32) AdvertisementInterval {
// Convert an interval to units of
return AdvertiseInterval(intervalMillis * 8 / 5)
return AdvertisementInterval(intervalMillis * 8 / 5)
}

// Connection is a numeric identifier that indicates a connection handle.
Expand Down Expand Up @@ -146,3 +153,31 @@ func (buf *rawAdvertisementPayload) LocalName() string {
}
return ""
}

// addFlags adds a flags field to the advertisement buffer. It returns true on
// success (the flags can be added) and false on failure.
func (buf *rawAdvertisementPayload) addFlags(flags byte) (ok bool) {
if int(buf.len)+3 > len(buf.data) {
return false // flags don't fit
}

buf.data[buf.len] = 2 // length of field (including type)
buf.data[buf.len+1] = 0x01 // type, 0x01 means Flags
buf.data[buf.len+2] = flags // the flags
buf.len += 3
return true
}

// addFlags adds the Complete Local Name field to the advertisement buffer. It
// returns true on success (the name fits) and false on failure.
func (buf *rawAdvertisementPayload) addCompleteLocalName(name string) (ok bool) {
if int(buf.len)+len(name)+2 > len(buf.data) {
return false // name doesn't fit
}

buf.data[buf.len] = byte(len(name) + 1) // length of field (including type)
buf.data[buf.len+1] = 9 // type, 0x09 means Complete Local name
copy(buf.data[buf.len+2:], name) // copy the name into the buffer
buf.len += byte(len(name) + 2)
return true
}
40 changes: 6 additions & 34 deletions gap_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@
package bluetooth

import (
"errors"

"github.com/muka/go-bluetooth/api"
"github.com/muka/go-bluetooth/bluez/profile/adapter"
"github.com/muka/go-bluetooth/bluez/profile/advertising"
"github.com/muka/go-bluetooth/bluez/profile/device"
)

var (
ErrMalformedAdvertisement = errors.New("bluetooth: malformed advertisement packet")
)

// Advertisement encapsulates a single advertisement instance.
type Advertisement struct {
adapter *Adapter
Expand All @@ -31,39 +25,17 @@ func (a *Adapter) NewAdvertisement() *Advertisement {
}

// Configure this advertisement.
func (a *Advertisement) Configure(broadcastData, scanResponseData []byte, options *AdvertiseOptions) error {
//
// On Linux with BlueZ, it is not possible to set the advertisement interval.
func (a *Advertisement) Configure(options AdvertisementOptions) error {
if a.advertisement != nil {
panic("todo: configure advertisement a second time")
}
if scanResponseData != nil {
panic("todo: scan response data")
}

// Quick-and-dirty advertisement packet parser.
a.properties = &advertising.LEAdvertisement1Properties{
Type: advertising.AdvertisementTypeBroadcast,
Timeout: 1<<16 - 1,
}
for len(broadcastData) != 0 {
if len(broadcastData) < 2 {
return ErrMalformedAdvertisement
}
fieldLength := broadcastData[0]
fieldType := broadcastData[1]
fieldValue := broadcastData[2 : fieldLength+1]
if int(fieldLength) > len(broadcastData) {
return ErrMalformedAdvertisement
}
switch fieldType {
case 1:
// BLE advertisement flags. Ignore.
case 9:
// Complete Local Name.
a.properties.LocalName = string(fieldValue)
default:
return ErrMalformedAdvertisement
}
broadcastData = broadcastData[fieldLength+1:]
Type: advertising.AdvertisementTypeBroadcast,
Timeout: 1<<16 - 1,
LocalName: options.LocalName,
}

return nil
Expand Down
25 changes: 9 additions & 16 deletions gap_nrf51.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import "C"

// Advertisement encapsulates a single advertisement instance.
type Advertisement struct {
interval AdvertiseInterval
interval AdvertisementInterval
}

var globalAdvertisement Advertisement
Expand All @@ -28,22 +28,15 @@ func (a *Adapter) NewAdvertisement() *Advertisement {
}

// Configure this advertisement. Must be called after SoftDevice initialization.
func (a *Advertisement) Configure(broadcastData, scanResponseData []byte, options *AdvertiseOptions) error {
var (
p_data *byte
dlen byte
p_sr_data *byte
srdlen byte
)
if broadcastData != nil {
p_data = &broadcastData[0]
dlen = uint8(len(broadcastData))
func (a *Advertisement) Configure(options AdvertisementOptions) error {
var payload rawAdvertisementPayload
payload.addFlags(0x06)
if options.LocalName != "" {
if !payload.addCompleteLocalName(options.LocalName) {
return errAdvertisementPacketTooBig
}
}
if scanResponseData != nil {
p_sr_data = &scanResponseData[0]
srdlen = uint8(len(scanResponseData))
}
errCode := C.sd_ble_gap_adv_data_set(p_data, dlen, p_sr_data, srdlen)
errCode := C.sd_ble_gap_adv_data_set(&payload.data[0], payload.len, nil, 0)
a.interval = options.Interval
return makeError(errCode)
}
Expand Down
19 changes: 9 additions & 10 deletions gap_nrf528xx.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,18 @@ func (a *Adapter) NewAdvertisement() *Advertisement {
}

// Configure this advertisement. Must be called after SoftDevice initialization.
func (a *Advertisement) Configure(broadcastData, scanResponseData []byte, options *AdvertiseOptions) error {
func (a *Advertisement) Configure(options AdvertisementOptions) error {
data := C.ble_gap_adv_data_t{}
if broadcastData != nil {
data.adv_data = C.ble_data_t{
p_data: &broadcastData[0],
len: uint16(len(broadcastData)),
var payload rawAdvertisementPayload
payload.addFlags(0x06)
if options.LocalName != "" {
if !payload.addCompleteLocalName(options.LocalName) {
return errAdvertisementPacketTooBig
}
}
if scanResponseData != nil {
data.scan_rsp_data = C.ble_data_t{
p_data: &scanResponseData[0],
len: uint16(len(scanResponseData)),
}
data.adv_data = C.ble_data_t{
p_data: &payload.data[0],
len: uint16(payload.len),
}
params := C.ble_gap_adv_params_t{
properties: C.ble_gap_adv_properties_t{
Expand Down

0 comments on commit 086c797

Please sign in to comment.