Skip to content

Commit

Permalink
linux: call startDiscovery async as it can block if adapter powered off
Browse files Browse the repository at this point in the history
  • Loading branch information
Lenart12 authored and deadprogram committed Feb 25, 2025
1 parent 905b208 commit 6a081be
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 25 deletions.
1 change: 1 addition & 0 deletions gap.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
var (
errScanning = errors.New("bluetooth: a scan is already in progress")
errNotScanning = errors.New("bluetooth: there is no scan in progress")
errScanStopped = errors.New("bluetooth: scan was stopped unexpectedly")
errAdvertisementPacketTooBig = errors.New("bluetooth: advertisement packet overflows")
errNotYetImplmented = errors.New("bluetooth: not implemented")
)
Expand Down
66 changes: 41 additions & 25 deletions gap_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,27 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
return errScanning
}

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

propertiesChangedMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.Properties")}
a.bus.AddMatchSignal(propertiesChangedMatchOptions...)
defer a.bus.RemoveMatchSignal(propertiesChangedMatchOptions...)

newObjectMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.ObjectManager")}
a.bus.AddMatchSignal(newObjectMatchOptions...)
defer a.bus.RemoveMatchSignal(newObjectMatchOptions...)

// Check if the adapter is powered on.
powered, err := a.adapter.GetProperty("org.bluez.Adapter1.Powered")
if err != nil {
return err
}
if !powered.Value().(bool) {
return errAdaptorNotPowered
}

// Channel that will be closed when the scan is stopped.
// Detecting whether the scan is stopped can be done by doing a non-blocking
// read from it. If it succeeds, the scan is stopped.
Expand All @@ -150,25 +171,13 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {

// This appears to be necessary to receive any BLE discovery results at all.
defer a.adapter.Call("org.bluez.Adapter1.SetDiscoveryFilter", 0)
err := a.adapter.Call("org.bluez.Adapter1.SetDiscoveryFilter", 0, map[string]interface{}{
err = a.adapter.Call("org.bluez.Adapter1.SetDiscoveryFilter", 0, map[string]interface{}{
"Transport": "le",
}).Err
if err != nil {
return err
}

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

propertiesChangedMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.Properties")}
a.bus.AddMatchSignal(propertiesChangedMatchOptions...)
defer a.bus.RemoveMatchSignal(propertiesChangedMatchOptions...)

newObjectMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.ObjectManager")}
a.bus.AddMatchSignal(newObjectMatchOptions...)
defer a.bus.RemoveMatchSignal(newObjectMatchOptions...)

// Go through all connected devices and present the connected devices as
// scan results. Also save the properties so that the full list of
// properties is known on a PropertiesChanged signal. We can't present the
Expand Down Expand Up @@ -200,10 +209,9 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
}

// Instruct BlueZ to start discovering.
err = a.adapter.Call("org.bluez.Adapter1.StartDiscovery", 0).Err
if err != nil {
return err
}
// NOTE: We must call Go here, not Call, because it can block if adapter is
// powered off, or was recently powered off.
startDiscovery := a.adapter.Go("org.bluez.Adapter1.StartDiscovery", 0, nil)

for {
// Check whether the scan is stopped. This is necessary to avoid a race
Expand All @@ -217,6 +225,12 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
}

select {
case <-startDiscovery.Done:
if startDiscovery.Err != nil {
close(cancelChan)
a.scanCancelChan = nil
return startDiscovery.Err
}
case sig := <-signal:
// This channel receives anything that we watch for, so we'll have
// to check for signals that are relevant to us.
Expand All @@ -236,14 +250,16 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
case "org.bluez.Adapter1":
// check power state
changes := sig.Body[1].(map[string]dbus.Variant)
for k, v := range changes {
if k == "Powered" && !v.Value().(bool) {
// adapter is powered off, stop the scan
close(cancelChan)
close(a.scanCancelChan)
a.scanCancelChan = nil
return errAdaptorNotPowered
}
if powered, ok := changes["Powered"]; ok && !powered.Value().(bool) {
// adapter is powered off, stop the scan
close(cancelChan)
a.scanCancelChan = nil
return errAdaptorNotPowered
} else if discovering, ok := changes["Discovering"]; ok && !discovering.Value().(bool) {
// adapter stopped discovering unexpectedly (e.g. due to external event)
close(cancelChan)
a.scanCancelChan = nil
return errScanStopped
}

case "org.bluez.Device1":
Expand Down

0 comments on commit 6a081be

Please sign in to comment.