2019-06-02 20:12:36 +03:00
|
|
|
package bluetooth
|
|
|
|
|
2020-05-28 00:13:04 +03:00
|
|
|
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")
|
|
|
|
)
|
|
|
|
|
2019-06-02 20:12:36 +03:00
|
|
|
// AdvertiseOptions configures everything related to BLE advertisements.
|
|
|
|
type AdvertiseOptions struct {
|
|
|
|
Interval AdvertiseInterval
|
|
|
|
}
|
|
|
|
|
|
|
|
// AdvertiseInterval is the advertisement interval in 0.625µs units.
|
|
|
|
type AdvertiseInterval uint32
|
|
|
|
|
|
|
|
// NewAdvertiseInterval returns a new advertisement interval, based on an
|
|
|
|
// interval in milliseconds.
|
|
|
|
func NewAdvertiseInterval(intervalMillis uint32) AdvertiseInterval {
|
|
|
|
// Convert an interval to units of
|
|
|
|
return AdvertiseInterval(intervalMillis * 8 / 5)
|
|
|
|
}
|
2019-11-09 15:07:07 +03:00
|
|
|
|
|
|
|
// Connection is a numeric identifier that indicates a connection handle.
|
|
|
|
type Connection uint16
|
|
|
|
|
|
|
|
// GAPEvent is a base (embeddable) event for all GAP events.
|
|
|
|
type GAPEvent struct {
|
|
|
|
Connection Connection
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConnectEvent occurs when a remote device connects to this device.
|
|
|
|
type ConnectEvent struct {
|
|
|
|
GAPEvent
|
|
|
|
}
|
|
|
|
|
|
|
|
// DisconnectEvent occurs when a remote device disconnects from this device.
|
|
|
|
type DisconnectEvent struct {
|
|
|
|
GAPEvent
|
|
|
|
}
|
2020-05-28 00:13:04 +03:00
|
|
|
|
|
|
|
// 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 {
|
|
|
|
// MAC address of the scanned device.
|
|
|
|
Address MAC
|
|
|
|
|
|
|
|
// RSSI the last time a packet from this device has been received.
|
|
|
|
RSSI int16
|
|
|
|
|
|
|
|
// The data obtained from the advertisement data, which may contain many
|
|
|
|
// different properties.
|
|
|
|
// Warning: this data may only stay valid until the next event arrives. If
|
|
|
|
// you need any of the fields to stay alive until after the callback
|
|
|
|
// returns, copy them.
|
|
|
|
AdvertisementPayload
|
|
|
|
}
|
|
|
|
|
|
|
|
// AdvertisementPayload contains information obtained during a scan (see
|
|
|
|
// ScanResult). It is provided as an interface as there are two possible
|
|
|
|
// implementations: an implementation that works with raw data (usually on
|
|
|
|
// low-level BLE stacks) and an implementation that works with structured data.
|
|
|
|
type AdvertisementPayload interface {
|
|
|
|
// LocalName is the (complete or shortened) local name of the device.
|
|
|
|
// Please note that many devices do not broadcast a local name, but may
|
|
|
|
// broadcast other data (e.g. manufacturer data or service UUIDs) with which
|
|
|
|
// they may be identified.
|
|
|
|
LocalName() string
|
|
|
|
|
|
|
|
// Bytes returns the raw advertisement packet, if available. It returns nil
|
|
|
|
// if this data is not available.
|
|
|
|
Bytes() []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// AdvertisementFields contains advertisement fields in structured form.
|
|
|
|
type AdvertisementFields struct {
|
|
|
|
// The LocalName part of the advertisement (either the complete local name
|
|
|
|
// or the shortened local name).
|
|
|
|
LocalName string
|
|
|
|
}
|
|
|
|
|
|
|
|
// advertisementFields wraps AdvertisementFields to implement the
|
|
|
|
// AdvertisementPayload interface. The methods to implement the interface (such
|
|
|
|
// as LocalName) cannot be implemented on AdvertisementFields because they would
|
|
|
|
// conflict with field names.
|
|
|
|
type advertisementFields struct {
|
|
|
|
AdvertisementFields
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalName returns the underlying LocalName field.
|
|
|
|
func (p *advertisementFields) LocalName() string {
|
|
|
|
return p.AdvertisementFields.LocalName
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bytes returns nil, as structured advertisement data does not have the
|
|
|
|
// original raw advertisement data available.
|
|
|
|
func (p *advertisementFields) Bytes() []byte {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// rawAdvertisementPayload encapsulates a raw advertisement packet. Methods to
|
|
|
|
// get the data (such as LocalName()) will parse just the needed field. Scanning
|
|
|
|
// the data should be fast as most advertisement packets only have a very small
|
|
|
|
// (3 or so) amount of fields.
|
|
|
|
type rawAdvertisementPayload struct {
|
|
|
|
data [31]byte
|
|
|
|
len uint8
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bytes returns the raw advertisement packet as a byte slice.
|
|
|
|
func (buf *rawAdvertisementPayload) Bytes() []byte {
|
|
|
|
return buf.data[:buf.len]
|
|
|
|
}
|
|
|
|
|
|
|
|
// findField returns the data of a specific field in the advertisement packet.
|
|
|
|
func (buf *rawAdvertisementPayload) findField(fieldType byte) []byte {
|
|
|
|
data := buf.Bytes()
|
|
|
|
for len(data) >= 2 {
|
|
|
|
fieldLength := data[0]
|
|
|
|
if int(fieldLength)+1 > len(data) {
|
|
|
|
// Invalid field length.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if fieldType == data[1] {
|
|
|
|
return data[2 : fieldLength+1]
|
|
|
|
}
|
|
|
|
data = data[fieldLength+1:]
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalName returns the local name (complete or shortened) in the advertisement
|
|
|
|
// payload.
|
|
|
|
func (buf *rawAdvertisementPayload) LocalName() string {
|
|
|
|
b := buf.findField(9) // Complete Local Name
|
|
|
|
if len(b) != 0 {
|
|
|
|
println("complete")
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
b = buf.findField(8) // Shortened Local Name
|
|
|
|
if len(b) != 0 {
|
|
|
|
println("shortened")
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|