diff --git a/examples/heartrate/main.go b/examples/heartrate/main.go index 4a0fe6b..9cd86ba 100644 --- a/examples/heartrate/main.go +++ b/examples/heartrate/main.go @@ -16,8 +16,9 @@ func main() { must("enable BLE stack", adapter.Enable()) adv := adapter.DefaultAdvertisement() must("config adv", adv.Configure(bluetooth.AdvertisementOptions{ - LocalName: "Go HRS", - Interval: bluetooth.NewAdvertisementInterval(100), + LocalName: "Go HRS", + ServiceUUIDs: []bluetooth.UUID{bluetooth.New16BitUUID(0x2A37)}, + Interval: bluetooth.NewAdvertisementInterval(100), })) must("start adv", adv.Start()) diff --git a/gap.go b/gap.go index 81991ed..046e8f5 100644 --- a/gap.go +++ b/gap.go @@ -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 + } +} diff --git a/gap_linux.go b/gap_linux.go index 0db50c1..57220ad 100644 --- a/gap_linux.go +++ b/gap_linux.go @@ -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 } diff --git a/gap_nrf51.go b/gap_nrf51.go index 1744ed5..8b6196e 100644 --- a/gap_nrf51.go +++ b/gap_nrf51.go @@ -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) { - return errAdvertisementPacketTooBig - } + 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) diff --git a/gap_nrf528xx.go b/gap_nrf528xx.go index 5a0238f..da4fa0f 100644 --- a/gap_nrf528xx.go +++ b/gap_nrf528xx.go @@ -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) { - return errAdvertisementPacketTooBig - } + 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), diff --git a/uuid.go b/uuid.go index 516c315..50f690f 100644 --- a/uuid.go +++ b/uuid.go @@ -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{}