From 01a0e93a426718b0c20b9988c6a3c6b971f9d0ae Mon Sep 17 00:00:00 2001 From: Baden Parr <1badenparr@gmail.com> Date: Sat, 19 Aug 2023 22:56:16 +1200 Subject: [PATCH 1/7] add adapter state and change setter --- adapter.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/adapter.go b/adapter.go index f3f4528c..afc60d03 100644 --- a/adapter.go +++ b/adapter.go @@ -3,9 +3,45 @@ package bluetooth // Set this to true to print debug messages, for example for unknown events. const debug = false +// 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 + // AdapterStateResetting is the state of the adaptor when it is resetting. + AdapterStateResetting + // AdapterStateUnknown is the state of the adaptor when it is unknown. + AdapterStateUnknown +) + +func (as *AdapterState) String() string { + switch *as { + case AdapterStatePoweredOff: + return "PoweredOff" + case AdapterStatePoweredOn: + return "PoweredOn" + case AdapterStateResetting: + return "Resetting" + case AdapterStateUnknown: + return "Unknown" + default: + return "Unknown" + } +} + // SetConnectHandler sets a handler function to be called whenever the adaptor connects // or disconnects. You must call this before you call adaptor.Connect() for centrals // or adaptor.Start() for peripherals in order for it to work. func (a *Adapter) SetConnectHandler(c func(device Address, connected bool)) { a.connectHandler = c } + +// SetStateChangeHandler sets a handler function to be called whenever the adaptor's +// state changes. +// This is a no-op on bare metal. +func (a *Adapter) SetStateChangeHandler(c func(newState AdapterState)) { + a.stateChangeHandler = c +} From bf2de89d6c91035523baca8c7db896373094bed7 Mon Sep 17 00:00:00 2001 From: Baden Parr <1badenparr@gmail.com> Date: Sat, 19 Aug 2023 22:57:18 +1200 Subject: [PATCH 2/7] macos: add adapter power state callback reporting --- adapter_darwin.go | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/adapter_darwin.go b/adapter_darwin.go index 06c7ffc9..a8c4e06e 100644 --- a/adapter_darwin.go +++ b/adapter_darwin.go @@ -24,7 +24,8 @@ type Adapter struct { // used to allow multiple callers to call Connect concurrently. connectMap sync.Map - connectHandler func(device Address, connected bool) + connectHandler func(device Address, connected bool) + stateChangeHandler func(newState AdapterState) } // DefaultAdapter is the default adapter on the system. @@ -38,6 +39,9 @@ var DefaultAdapter = &Adapter{ connectHandler: func(device Address, connected bool) { return }, + stateChangeHandler: func(newState AdapterState) { + return + }, } // Enable configures the BLE stack. It must be called before any @@ -65,6 +69,8 @@ func (a *Adapter) Enable() error { for len(a.poweredChan) > 0 { <-a.poweredChan } + // we are done with this chan so lets remove it, this lets Enable() be reentrant if needed + a.poweredChan = nil // wait until powered? a.pmd = &peripheralManagerDelegate{a: a} @@ -83,9 +89,23 @@ type centralManagerDelegate struct { // CentralManagerDidUpdateState when central manager state updated. func (cmd *centralManagerDelegate) CentralManagerDidUpdateState(cmgr cbgo.CentralManager) { - // powered on? - if cmgr.State() == cbgo.ManagerStatePoweredOn { - cmd.a.poweredChan <- nil + + switch cmgr.State() { + case cbgo.ManagerStatePoweredOn: + // if we are waiting for a PoweredOn signal then send it + if cmd.a.poweredChan != nil { + cmd.a.poweredChan <- nil + } + cmd.a.stateChangeHandler(AdapterStatePoweredOn) + + case cbgo.ManagerStatePoweredOff: + cmd.a.stateChangeHandler(AdapterStatePoweredOff) + + case cbgo.ManagerStateResetting: + cmd.a.stateChangeHandler(AdapterStateResetting) + + default: + cmd.a.stateChangeHandler(AdapterStateUnknown) } // TODO: handle other state changes. From d212487e34212d3806ff0e331ff0b253f889d70d Mon Sep 17 00:00:00 2001 From: Baden Parr <1badenparr@gmail.com> Date: Sun, 20 Aug 2023 15:12:06 +1200 Subject: [PATCH 3/7] linux: add adapter powered state watching and change callback --- adapter_linux.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/adapter_linux.go b/adapter_linux.go index 8ff92600..67e7e90f 100644 --- a/adapter_linux.go +++ b/adapter_linux.go @@ -6,9 +6,11 @@ package bluetooth import ( + "context" "errors" "github.com/muka/go-bluetooth/api" + "github.com/muka/go-bluetooth/bluez" "github.com/muka/go-bluetooth/bluez/profile/adapter" ) @@ -18,7 +20,12 @@ type Adapter struct { cancelChan chan struct{} defaultAdvertisement *Advertisement - connectHandler func(device Address, connected bool) + ctx context.Context // context for our event watcher, canceled on power off event + cancel context.CancelFunc // cancel function to halt our event watcher context + propchanged chan *bluez.PropertyChanged // channel that adapter property changes will show up on + + connectHandler func(device Address, connected bool) + stateChangeHandler func(newState AdapterState) } // DefaultAdapter is the default adapter on the system. On Linux, it is the @@ -29,6 +36,9 @@ var DefaultAdapter = &Adapter{ connectHandler: func(device Address, connected bool) { return }, + stateChangeHandler: func(newState AdapterState) { + return + }, } // Enable configures the BLE stack. It must be called before any @@ -40,6 +50,8 @@ func (a *Adapter) Enable() (err error) { return } a.id, err = a.adapter.GetAdapterID() + a.ctx, a.cancel = context.WithCancel(context.Background()) + a.watchForStateChange() } return nil } @@ -54,3 +66,38 @@ func (a *Adapter) Address() (MACAddress, error) { } return MACAddress{MAC: mac}, nil } + +func (a *Adapter) watchForStateChange() error { + var err error + a.propchanged, err = a.adapter.WatchProperties() + if err != nil { + return err + } + + go func() { + for { + select { + case changed := <-a.propchanged: + // we will receive a nil if bluez.UnwatchProperties(a, ch) is called, if so we can stop watching + if changed == nil { + a.cancel() + return + } + switch changed.Name { + case "Powered": + if changed.Value.(bool) { + a.stateChangeHandler(AdapterStatePoweredOn) + } else { + a.stateChangeHandler(AdapterStatePoweredOff) + } + } + + continue + case <-a.ctx.Done(): + return + } + } + }() + + return nil +} From 315a5a20228e6c1d4b9400e30d2c20030e0413c6 Mon Sep 17 00:00:00 2001 From: Baden Parr <1badenparr@gmail.com> Date: Sun, 20 Aug 2023 15:15:52 +1200 Subject: [PATCH 4/7] remove resetting state as it is not general --- adapter.go | 4 ---- adapter_darwin.go | 3 --- 2 files changed, 7 deletions(-) diff --git a/adapter.go b/adapter.go index afc60d03..13e103e2 100644 --- a/adapter.go +++ b/adapter.go @@ -11,8 +11,6 @@ const ( AdapterStatePoweredOff = AdapterState(iota) // AdapterStatePoweredOn is the state of the adaptor when it is powered on. AdapterStatePoweredOn - // AdapterStateResetting is the state of the adaptor when it is resetting. - AdapterStateResetting // AdapterStateUnknown is the state of the adaptor when it is unknown. AdapterStateUnknown ) @@ -23,8 +21,6 @@ func (as *AdapterState) String() string { return "PoweredOff" case AdapterStatePoweredOn: return "PoweredOn" - case AdapterStateResetting: - return "Resetting" case AdapterStateUnknown: return "Unknown" default: diff --git a/adapter_darwin.go b/adapter_darwin.go index a8c4e06e..e1af7b15 100644 --- a/adapter_darwin.go +++ b/adapter_darwin.go @@ -101,9 +101,6 @@ func (cmd *centralManagerDelegate) CentralManagerDidUpdateState(cmgr cbgo.Centra case cbgo.ManagerStatePoweredOff: cmd.a.stateChangeHandler(AdapterStatePoweredOff) - case cbgo.ManagerStateResetting: - cmd.a.stateChangeHandler(AdapterStateResetting) - default: cmd.a.stateChangeHandler(AdapterStateUnknown) } From ed9477aec61b34ea277b6938dbdf254181a79b41 Mon Sep 17 00:00:00 2001 From: Baden Parr <1badenparr@gmail.com> Date: Sun, 20 Aug 2023 15:51:18 +1200 Subject: [PATCH 5/7] move SetStateChangeHandler to os specific implementations --- adapter.go | 7 ------- adapter_darwin.go | 6 ++++++ adapter_linux.go | 10 ++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/adapter.go b/adapter.go index 13e103e2..a6533316 100644 --- a/adapter.go +++ b/adapter.go @@ -34,10 +34,3 @@ func (as *AdapterState) String() string { func (a *Adapter) SetConnectHandler(c func(device Address, connected bool)) { a.connectHandler = c } - -// SetStateChangeHandler sets a handler function to be called whenever the adaptor's -// state changes. -// This is a no-op on bare metal. -func (a *Adapter) SetStateChangeHandler(c func(newState AdapterState)) { - a.stateChangeHandler = c -} diff --git a/adapter_darwin.go b/adapter_darwin.go index e1af7b15..892e992b 100644 --- a/adapter_darwin.go +++ b/adapter_darwin.go @@ -79,6 +79,12 @@ func (a *Adapter) Enable() 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 +} + // CentralManager delegate functions type centralManagerDelegate struct { diff --git a/adapter_linux.go b/adapter_linux.go index 67e7e90f..7a812633 100644 --- a/adapter_linux.go +++ b/adapter_linux.go @@ -67,6 +67,16 @@ 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 +} + +// watchForConnect watches for a signal from the bluez adapter interface that indicates a Powered/Unpowered event. +// +// We can add extra signals to watch for here, +// see https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt, for a full list func (a *Adapter) watchForStateChange() error { var err error a.propchanged, err = a.adapter.WatchProperties() From 5d5d23fad9617e15ee85bebadf4a97eafa03a46e Mon Sep 17 00:00:00 2001 From: Baden Parr <1badenparr@gmail.com> Date: Fri, 15 Sep 2023 14:54:22 +1200 Subject: [PATCH 6/7] macos: add method to query adapter state --- adapter_darwin.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/adapter_darwin.go b/adapter_darwin.go index 892e992b..1fe0ab69 100644 --- a/adapter_darwin.go +++ b/adapter_darwin.go @@ -85,6 +85,18 @@ 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 { From 5d5d0d99a30fb9604afe4c3170f06551c336e79f Mon Sep 17 00:00:00 2001 From: Baden Parr <1badenparr@gmail.com> Date: Fri, 15 Sep 2023 14:56:38 +1200 Subject: [PATCH 7/7] linux: add method to query adapter state --- adapter_linux.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/adapter_linux.go b/adapter_linux.go index 7a812633..3eda4b01 100644 --- a/adapter_linux.go +++ b/adapter_linux.go @@ -73,6 +73,22 @@ 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 + } + + powered, err := a.adapter.GetPowered() + if err != nil { + return AdapterStateUnknown + } + if powered { + return AdapterStatePoweredOn + } + return AdapterStatePoweredOff +} + // watchForConnect watches for a signal from the bluez adapter interface that indicates a Powered/Unpowered event. // // We can add extra signals to watch for here,