Create custom BlueZ agent
This commit is contained in:
parent
9250d26fdc
commit
738e140bfb
91
btsetup.go
91
btsetup.go
@ -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"
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user