Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions adapter.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package bluetooth

// AdapterState represents the state of the adaptor.
type AdapterState int

const (
// AdapterStatePoweredOff is the state of the adaptor when it is powered off.
AdapterStatePoweredOff = AdapterState(iota)
// AdapterStatePoweredOn is the state of the adaptor when it is powered on.
AdapterStatePoweredOn
// AdapterStateUnknown is the state of the adaptor when it is unknown.
AdapterStateUnknown
)

// BLEAdapter is the shared interface that all platform-specific Adapter types must implement.
type BLEAdapter interface {
Connect(address Address, params ConnectionParams) (Device, error)
Expand Down
33 changes: 29 additions & 4 deletions adapter_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ type Adapter struct {
// used to allow multiple callers to call Connect concurrently.
connectMap sync.Map

connectHandler func(device Device, connected bool)
connectHandler func(device Device, connected bool)
stateChangeHandler func(newState AdapterState)
}

// DefaultAdapter is the default adapter on the system.
Expand All @@ -37,9 +38,8 @@ var DefaultAdapter = &Adapter{
pm: cbgo.NewPeripheralManager(nil),
connectMap: sync.Map{},

connectHandler: func(device Device, connected bool) {
return
},
connectHandler: func(device Device, connected bool) {},
stateChangeHandler: func(newState AdapterState) {},
}

// Enable configures the BLE stack. It must be called before any
Expand Down Expand Up @@ -121,6 +121,24 @@ func (a *Adapter) Reset() error {
return nil
}

// SetStateChangeHandler sets a handler function to be called whenever the adaptor's
// state changes.
func (a *Adapter) SetStateChangeHandler(c func(newState AdapterState)) {
a.stateChangeHandler = c
}

// State returns the current state of the adapter.
func (a *Adapter) State() AdapterState {
switch a.cm.State() {
case cbgo.ManagerStatePoweredOn:
return AdapterStatePoweredOn
case cbgo.ManagerStatePoweredOff:
return AdapterStatePoweredOff
default:
return AdapterStateUnknown
}
}

// CentralManager delegate functions

type centralManagerDelegate struct {
Expand All @@ -132,18 +150,25 @@ type centralManagerDelegate struct {
// CentralManagerDidUpdateState when central manager state updated.
func (cmd *centralManagerDelegate) CentralManagerDidUpdateState(cmgr cbgo.CentralManager) {
var event error
var newState AdapterState
switch cmgr.State() {
case cbgo.ManagerStatePoweredOn:
newState = AdapterStatePoweredOn
event = nil
case cbgo.ManagerStatePoweredOff:
newState = AdapterStatePoweredOff
event = errors.New("bluetooth is powered off")
case cbgo.ManagerStateUnsupported:
newState = AdapterStatePoweredOff
event = errors.New("bluetooth is not supported on this device")
case cbgo.ManagerStateUnauthorized:
newState = AdapterStatePoweredOff
event = errors.New("bluetooth is not authorized for this app")
default:
cmd.a.stateChangeHandler(AdapterStateUnknown)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a bug: the zero AdapterState is AdapterStatePoweredOff, which will be used in this case.
Either reorder the different states (so that AdapterStateUnknown is zero), or better yet set it here explicitly:

newState = AdapterStateUnknown

return
}
cmd.a.stateChangeHandler(newState)
// Non-blocking send: poweredChan may be nil after Enable
// completes, or already buffered.
select {
Expand Down
83 changes: 80 additions & 3 deletions adapter_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,18 @@ type Adapter struct {
address string
defaultAdvertisement *Advertisement

connectHandler func(device Device, connected bool)
connectHandler func(device Device, connected bool)
stateChangeHandler func(newState AdapterState)
}

// NewAdapter creates a new Adapter with the given ID.
//
// Make sure to call Enable() before using it to initialize the adapter.
func NewAdapter(id string) *Adapter {
return &Adapter{
id: id,
connectHandler: func(device Device, connected bool) {},
id: id,
connectHandler: func(device Device, connected bool) {},
stateChangeHandler: func(newState AdapterState) {},
}
}

Expand Down Expand Up @@ -63,6 +65,9 @@ func (a *Adapter) Enable() (err error) {
}
addr.Store(&a.address)

if err := a.watchForStateChange(); err != nil {
return err
}
return nil
}

Expand All @@ -87,3 +92,75 @@ func (a *Adapter) Address() (MACAddress, error) {
}
return MACAddress{MAC: mac}, nil
}

// SetStateChangeHandler sets a handler function to be called whenever the adaptor's
// state changes.
func (a *Adapter) SetStateChangeHandler(c func(newState AdapterState)) {
a.stateChangeHandler = c
}

// State returns the current state of the adapter.
func (a *Adapter) State() AdapterState {
if a.adapter == nil {
return AdapterStateUnknown
}

prop, err := a.adapter.GetProperty("org.bluez.Adapter1.Powered")
if err != nil {
return AdapterStateUnknown
}
powered, ok := prop.Value().(bool)
if !ok {
return AdapterStateUnknown
}
if powered {
return AdapterStatePoweredOn
}
return AdapterStatePoweredOff
}

// watchForStateChange watches for D-Bus PropertiesChanged signals from the
// BlueZ adapter interface and reports Powered/Unpowered transitions to the
// registered state-change handler.
//
// We can watch for extra adapter properties here, see
// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.Adapter.rst
// for the full list.
func (a *Adapter) watchForStateChange() error {
matchOptions := []dbus.MatchOption{
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
dbus.WithMatchMember("PropertiesChanged"),
dbus.WithMatchArg(dbusPropertiesChangedInterfaceName, "org.bluez.Adapter1"),
}
if err := a.bus.AddMatchSignal(matchOptions...); err != nil {
return err
}

signal := make(chan *dbus.Signal)
a.bus.Signal(signal)

go func() {
for sig := range signal {
if sig.Name != dbusSignalPropertiesChanged {
continue
}
// Only react to property changes on the adapter interface.
if interfaceName, ok := sig.Body[dbusPropertiesChangedInterfaceName].(string); !ok || interfaceName != "org.bluez.Adapter1" {
continue
}
changes, ok := sig.Body[dbusPropertiesChangedDictionary].(map[string]dbus.Variant)
if !ok {
continue
}
if powered, ok := changes["Powered"].Value().(bool); ok {
if powered {
a.stateChangeHandler(AdapterStatePoweredOn)
} else {
a.stateChangeHandler(AdapterStatePoweredOff)
}
}
}
}()

return nil
}
Loading