Create custom BlueZ agent

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

View File

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