Create custom BlueZ agent

This commit is contained in:
Elara 2021-12-16 21:30:29 -08:00
parent 9250d26fdc
commit 738e140bfb
2 changed files with 155 additions and 33 deletions

View File

@ -1,27 +1,116 @@
package infinitime package infinitime
import ( import (
"github.com/godbus/dbus/v5"
bt "github.com/muka/go-bluetooth/api" bt "github.com/muka/go-bluetooth/api"
"github.com/muka/go-bluetooth/bluez/profile/adapter" "github.com/muka/go-bluetooth/bluez/profile/adapter"
"github.com/muka/go-bluetooth/bluez/profile/agent"
"github.com/muka/go-bluetooth/hw/linux/btmgmt"
) )
var defaultAdapter *adapter.Adapter1 var defaultAdapter *adapter.Adapter1
var itdAgent *Agent
func Init() { func Init() {
conn, err := dbus.SystemBus()
if err != nil {
panic(err)
}
ag := &Agent{}
err = agent.ExposeAgent(conn, ag, agent.CapKeyboardDisplay, true)
if err != nil {
panic(err)
}
// Get bluez default adapter // Get bluez default adapter
da, err := bt.GetDefaultAdapter() da, err := bt.GetDefaultAdapter()
if err != nil { if err != nil {
panic(err) panic(err)
} }
da.SetPowered(true) daMgmt := btmgmt.NewBtMgmt(bt.GetDefaultAdapterID())
daMgmt.SetPowered(true)
defaultAdapter = da defaultAdapter = da
itdAgent = ag
} }
func Exit() error { func Exit() error {
if defaultAdapter != nil { if defaultAdapter != nil {
defaultAdapter.Close() defaultAdapter.Close()
} }
agent.RemoveAgent(itdAgent)
return bt.Exit() return bt.Exit()
} }
var errAuthFailed = dbus.NewError("org.bluez.Error.AuthenticationFailed", nil)
// Agent implements the agent.Agent1Client interface.
// It only requires RequestPasskey as that is all InfiniTime
// will use.
type Agent struct {
ReqPasskey func() (uint32, error)
}
// Release returns nil
func (*Agent) Release() *dbus.Error {
return nil
}
// RequestPinCode returns an empty string and nil
func (*Agent) RequestPinCode(device dbus.ObjectPath) (pincode string, err *dbus.Error) {
return "", nil
}
// DisplayPinCode returns nil
func (*Agent) DisplayPinCode(device dbus.ObjectPath, pincode string) *dbus.Error {
return nil
}
// RequestPasskey runs Agent.ReqPasskey and returns the result
func (a *Agent) RequestPasskey(device dbus.ObjectPath) (uint32, *dbus.Error) {
if a.ReqPasskey == nil {
return 0, errAuthFailed
}
passkey, err := a.ReqPasskey()
if err != nil {
return 0, errAuthFailed
}
return passkey, nil
}
// DisplayPasskey returns nil
func (*Agent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, entered uint16) *dbus.Error {
return nil
}
// RequestConfirmation returns nil
func (*Agent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
return nil
}
// RequestAuthorization returns nil
func (*Agent) RequestAuthorization(device dbus.ObjectPath) *dbus.Error {
return nil
}
// AuthorizeService returns nil
func (*Agent) AuthorizeService(device dbus.ObjectPath, uuid string) *dbus.Error {
return nil
}
// Cancel returns nil
func (*Agent) Cancel() *dbus.Error {
return nil
}
// Path returns "/dev/arsenm/infinitime/Agent"
func (*Agent) Path() dbus.ObjectPath {
return "/dev/arsenm/infinitime/Agent"
}
// Interface returns "org.bluez.Agent1"
func (*Agent) Interface() string {
return "org.bluez.Agent1"
}

View File

@ -59,12 +59,14 @@ var (
ErrNotConnected = errors.New("not connected") ErrNotConnected = errors.New("not connected")
ErrCharNotAvail = errors.New("required characteristic is not available") ErrCharNotAvail = errors.New("required characteristic is not available")
ErrNoTimelineHeader = errors.New("events must contain the timeline header") ErrNoTimelineHeader = errors.New("events must contain the timeline header")
ErrPairTimeout = errors.New("reached timeout while pairing")
) )
type Options struct { type Options struct {
AttemptReconnect bool AttemptReconnect bool
WhitelistEnabled bool WhitelistEnabled bool
Whitelist []string Whitelist []string
OnReqPasskey func() (uint32, error)
} }
var DefaultOptions = &Options{ var DefaultOptions = &Options{
@ -94,6 +96,8 @@ func Connect(opts *Options) (*Device, error) {
} }
dev.opts = opts dev.opts = opts
dev.onReconnect = func() {} dev.onReconnect = func() {}
setOnPasskeyReq(opts.OnReqPasskey)
// Watch device properties // Watch device properties
devEvtCh, err := dev.device.WatchProperties() devEvtCh, err := dev.device.WatchProperties()
if err != nil { if err != nil {
@ -121,14 +125,29 @@ func Connect(opts *Options) (*Device, error) {
dev.device.Properties.Connected = false dev.device.Properties.Connected = false
// While not connected // While not connected
for !dev.device.Properties.Connected { for !dev.device.Properties.Connected {
// Attempt to connect via bluetooth address reConnDev := dev
reConnDev, err := ConnectByAddress(dev.device.Properties.Address)
paired, err := reConnDev.device.GetPaired()
if err != nil { if err != nil {
// Decrement disconnect event number
disconnEvtNum--
// Skip rest of loop
continue continue
} }
if !paired {
err = reConnDev.pairTimeout()
if err != nil {
continue
}
} else {
// Attempt to connect via bluetooth address
reConnDev, err = connectByName(opts)
if err != nil {
// Decrement disconnect event number
disconnEvtNum--
// Skip rest of loop
continue
}
}
// Store onReconn callback // Store onReconn callback
onReconn := dev.onReconnect onReconn := dev.onReconnect
// Set device to new device // Set device to new device
@ -154,6 +173,7 @@ func (i *Device) OnReconnect(f func()) {
// Connect connects to a paired InfiniTime device // Connect connects to a paired InfiniTime device
func connectByName(opts *Options) (*Device, error) { func connectByName(opts *Options) (*Device, error) {
setOnPasskeyReq(opts.OnReqPasskey)
// Create new device // Create new device
out := &Device{} out := &Device{}
// Get devices from default adapter // Get devices from default adapter
@ -203,6 +223,7 @@ func contains(ss []string, s string) bool {
// Pair attempts to discover and pair an InfiniTime device // Pair attempts to discover and pair an InfiniTime device
func pair(opts *Options) (*Device, error) { func pair(opts *Options) (*Device, error) {
setOnPasskeyReq(opts.OnReqPasskey)
// Create new device // Create new device
out := &Device{} out := &Device{}
// Start bluetooth discovery // Start bluetooth discovery
@ -237,10 +258,17 @@ func pair(opts *Options) (*Device, error) {
return nil, ErrNotFound return nil, ErrNotFound
} }
// Pair device // Connect to device
out.device.Pair() err = out.device.Connect()
if err != nil {
return nil, err
}
out.device.Properties.Connected = true // Pair device
err = out.pairTimeout()
if err != nil {
return nil, err
}
// Set connected to true // Set connected to true
out.device.Properties.Connected = true out.device.Properties.Connected = true
@ -254,32 +282,33 @@ func pair(opts *Options) (*Device, error) {
return out, nil return out, nil
} }
// ConnectByAddress tries to connect to an InifiniTime at // setOnPasskeyReq sets the callback for a passkey request.
// the specified InfiniTime address // It ensures the function will never be nil.
func ConnectByAddress(addr string) (*Device, error) { func setOnPasskeyReq(onReqPasskey func() (uint32, error)) {
var err error itdAgent.ReqPasskey = onReqPasskey
// Create new device if itdAgent.ReqPasskey == nil {
out := &Device{} itdAgent.ReqPasskey = func() (uint32, error) {
// Get device from bluetooth address return 0, nil
out.device, err = defaultAdapter.GetDeviceByAddress(addr) }
if err != nil {
return nil, err
} }
}
// Connect to device // pairTimeout tries to pair with the device.
err = out.device.Connect() // It will time out after 20 seconds.
if err != nil { func (i *Device) pairTimeout() error {
return nil, err errCh := make(chan error)
go func() {
errCh <- i.device.Pair()
}()
select {
case err := <-errCh:
return err
case <-time.After(20 * time.Second):
if err := i.device.CancelPairing(); err != nil {
return err
}
return ErrPairTimeout
} }
out.device.Properties.Connected = true
// Resolve characteristics
err = out.resolveChars()
if err != nil {
return nil, err
}
return out, nil
} }
// resolveChars attempts to set all required // resolveChars attempts to set all required
@ -719,7 +748,11 @@ func (i *Device) AddWeatherEvent(event interface{}) error {
} }
func (i *Device) checkStatus(char *gatt.GattCharacteristic1) error { func (i *Device) checkStatus(char *gatt.GattCharacteristic1) error {
if !i.device.Properties.Connected { connected, err := i.device.GetConnected()
if err != nil {
return err
}
if !connected {
return ErrNotConnected return ErrNotConnected
} }
if char == nil { if char == nil {