all: add service UUIDs to advertisement packets

Support both 16-bit and 128-bit UUIDs on both Linux and Nordic chips.
This commit is contained in:
Ayke van Laethem 2020-06-02 14:00:32 +02:00
parent 49f8082e4b
commit 21100ebc19
No known key found for this signature in database
GPG key ID: E97FF5335DFDFDED
6 changed files with 84 additions and 16 deletions

View file

@ -17,6 +17,7 @@ func main() {
adv := adapter.DefaultAdvertisement()
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
LocalName: "Go HRS",
ServiceUUIDs: []bluetooth.UUID{bluetooth.New16BitUUID(0x2A37)},
Interval: bluetooth.NewAdvertisementInterval(100),
}))
must("start adv", adv.Start())

62
gap.go
View file

@ -15,6 +15,11 @@ type AdvertisementOptions struct {
// this is a zero-length string.
LocalName string
// ServiceUUIDs are the services (16-bit or 128-bit) that are broadcast as
// part of the advertisement packet, in data types such as "complete list of
// 128-bit UUIDs".
ServiceUUIDs []UUID
// Interval in BLE-specific units. Create an interval by using
// NewAdvertiseInterval.
Interval AdvertisementInterval
@ -139,6 +144,29 @@ func (buf *rawAdvertisementPayload) LocalName() string {
return ""
}
// addFromOptions constructs a new advertisement payload (assumed to be empty
// before the call) from the advertisement options. It returns true if it fits,
// false otherwise.
func (buf *rawAdvertisementPayload) addFromOptions(options AdvertisementOptions) (ok bool) {
buf.addFlags(0x06)
if options.LocalName != "" {
if !buf.addCompleteLocalName(options.LocalName) {
return false
}
}
// TODO: if there are multiple 16-bit UUIDs, they should be listed in
// one field.
// This is not possible for 128-bit service UUIDs (at least not in
// legacy advertising) because of the 31-byte advertisement packet
// limit.
for _, uuid := range options.ServiceUUIDs {
if !buf.addServiceUUID(uuid) {
return false
}
}
return true
}
// 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) {
@ -153,8 +181,8 @@ func (buf *rawAdvertisementPayload) addFlags(flags byte) (ok bool) {
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.
// addCompleteLocalName 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
@ -166,3 +194,33 @@ func (buf *rawAdvertisementPayload) addCompleteLocalName(name string) (ok bool)
buf.len += byte(len(name) + 2)
return true
}
// addServiceUUID adds a Service Class UUID (16-bit or 128-bit). It has
// currently only been designed for adding single UUIDs: multiple UUIDs are
// stored in separate fields without joining them together in one field.
func (buf *rawAdvertisementPayload) addServiceUUID(uuid UUID) (ok bool) {
// Don't bother with 32-bit UUID support, it doesn't seem to be used in
// practice.
if uuid.Is16Bit() {
if int(buf.len)+4 > len(buf.data) {
return false // UUID doesn't fit.
}
shortUUID := uuid.Get16Bit()
buf.data[buf.len+0] = 3 // length of field, including type
buf.data[buf.len+1] = 0x03 // type, 0x03 means "Complete List of 16-bit Service Class UUIDs"
buf.data[buf.len+2] = byte(shortUUID)
buf.data[buf.len+3] = byte(shortUUID >> 8)
buf.len += 4
return true
} else {
if int(buf.len)+18 > len(buf.data) {
return false // UUID doesn't fit.
}
buf.data[buf.len+0] = 17 // length of field, including type
buf.data[buf.len+1] = 0x07 // type, 0x07 means "Complete List of 128-bit Service Class UUIDs"
rawUUID := uuid.Bytes()
copy(buf.data[buf.len+2:], rawUUID[:])
buf.len += 18
return true
}
}

View file

@ -40,6 +40,9 @@ func (a *Advertisement) Configure(options AdvertisementOptions) error {
Timeout: 1<<16 - 1,
LocalName: options.LocalName,
}
for _, uuid := range options.ServiceUUIDs {
a.properties.ServiceUUIDs = append(a.properties.ServiceUUIDs, uuid.String())
}
return nil
}

View file

@ -29,13 +29,12 @@ func (a *Adapter) DefaultAdvertisement() *Advertisement {
// Configure this advertisement.
func (a *Advertisement) Configure(options AdvertisementOptions) error {
// Construct payload.
var payload rawAdvertisementPayload
payload.addFlags(0x06)
if options.LocalName != "" {
if !payload.addCompleteLocalName(options.LocalName) {
if !payload.addFromOptions(options) {
return errAdvertisementPacketTooBig
}
}
errCode := C.sd_ble_gap_adv_data_set(&payload.data[0], payload.len, nil, 0)
a.interval = options.Interval
return makeError(errCode)

View file

@ -44,14 +44,13 @@ func (a *Adapter) DefaultAdvertisement() *Advertisement {
// Configure this advertisement.
func (a *Advertisement) Configure(options AdvertisementOptions) error {
data := C.ble_gap_adv_data_t{}
// Construct payload.
var payload rawAdvertisementPayload
payload.addFlags(0x06)
if options.LocalName != "" {
if !payload.addCompleteLocalName(options.LocalName) {
if !payload.addFromOptions(options) {
return errAdvertisementPacketTooBig
}
}
data := C.ble_gap_adv_data_t{}
data.adv_data = C.ble_data_t{
p_data: &payload.data[0],
len: uint16(payload.len),

10
uuid.go
View file

@ -48,11 +48,19 @@ func (uuid UUID) Is16Bit() bool {
return uuid.Is32Bit() && uuid[3] == uint32(uint16(uuid[3]))
}
// Is32Bit returns whether this UUID is a 32-bit BLE UUID.
// Is32Bit returns whether this UUID is a 32-bit or 16-bit BLE UUID.
func (uuid UUID) Is32Bit() bool {
return uuid[0] == 0x5F9B34FB && uuid[1] == 0x80000080 && uuid[2] == 0x00001000
}
// Get16Bit returns the 16-bit version of this UUID. This is only valid if it
// actually is a 16-bit UUID, see Is16Bit.
func (uuid UUID) Get16Bit() uint16 {
// Note: using a Get* function as a getter because method names can't start
// with a number.
return uint16(uuid[3])
}
// Bytes returns a 16-byte array containing the raw UUID.
func (uuid UUID) Bytes() [16]byte {
buf := [16]byte{}