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:
parent
49f8082e4b
commit
21100ebc19
6 changed files with 84 additions and 16 deletions
|
@ -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())
|
||||
|
||||
|
|
62
gap.go
62
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
10
uuid.go
10
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{}
|
||||
|
|
Loading…
Reference in a new issue