blob: 8a285b128143021bba0151b1514d1c88f58088e7 [file]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package bluetooth contains helpers to interact with the system's bluetooth
// adapters.
package bluetooth
import (
"context"
"time"
"github.com/godbus/dbus"
"chromiumos/tast/errors"
"chromiumos/tast/local/dbusutil"
"chromiumos/tast/testing"
)
// Adapter contains helper functions for getting and setting bluetooth adapter
// state.
type Adapter struct {
obj dbus.BusObject
path dbus.ObjectPath
}
const service = "org.bluez"
const adapterIface = service + ".Adapter1"
// Adapters creates an Adapter for all bluetooth adapters in the system.
func Adapters(ctx context.Context) ([]*Adapter, error) {
var adapters []*Adapter
_, obj, err := dbusutil.Connect(ctx, service, "/")
if err != nil {
return nil, err
}
managed, err := dbusutil.ManagedObjects(ctx, obj)
if err != nil {
return nil, err
}
for _, path := range managed[adapterIface] {
adapter, err := NewAdapter(ctx, path)
if err != nil {
return nil, err
}
adapters = append(adapters, adapter)
}
return adapters, nil
}
// NewAdapter creates a new bluetooth Adapter from the passed D-Bus object path.
func NewAdapter(ctx context.Context, path dbus.ObjectPath) (*Adapter, error) {
_, obj, err := dbusutil.Connect(ctx, service, path)
if err != nil {
return nil, err
}
return &Adapter{obj, path}, nil
}
// Path gets the D-Bus path this adapter was created from.
func (a *Adapter) Path() dbus.ObjectPath {
return a.path
}
const poweredProp = adapterIface + ".Powered"
// SetPowered turns a bluetooth adapter on or off
func (a *Adapter) SetPowered(ctx context.Context, powered bool) error {
return dbusutil.SetProperty(ctx, a.obj, poweredProp, powered)
}
// Powered returns whether a bluetooth adapter is powered on.
func (a *Adapter) Powered(ctx context.Context) (bool, error) {
value, err := dbusutil.Property(ctx, a.obj, poweredProp)
if err != nil {
return false, err
}
powered, ok := value.(bool)
if !ok {
return false, errors.New("powered property not a bool")
}
return powered, nil
}
const addressProp = adapterIface + ".Address"
// Address returns the MAC address of the adapter.
func (a *Adapter) Address(ctx context.Context) (string, error) {
value, err := dbusutil.Property(ctx, a.obj, addressProp)
if err != nil {
return "", err
}
address, ok := value.(string)
if !ok {
return "", errors.New("address property not a string")
}
return address, nil
}
const nameProp = adapterIface + ".Name"
// Name returns the name of the adapter.
func (a *Adapter) Name(ctx context.Context) (string, error) {
value, err := dbusutil.Property(ctx, a.obj, nameProp)
if err != nil {
return "", err
}
name, ok := value.(string)
if !ok {
return "", errors.New("name property not a string")
}
return name, nil
}
// StartDiscovery starts a discovery on the adapter.
func (a *Adapter) StartDiscovery(ctx context.Context) error {
c := a.obj.CallWithContext(ctx, adapterIface+".StartDiscovery", 0)
if c.Err != nil {
return errors.Wrap(c.Err, "failed to start discovery")
}
return nil
}
// StopDiscovery stops the discovery on the adapter.
func (a *Adapter) StopDiscovery(ctx context.Context) error {
c := a.obj.CallWithContext(ctx, adapterIface+".StopDiscovery", 0)
if c.Err != nil {
return errors.Wrap(c.Err, "failed to stop discovery")
}
return nil
}
// IsEnabled checks if bluetooth adapter present and powered on.
func IsEnabled(ctx context.Context) (bool, error) {
adapters, err := Adapters(ctx)
if err != nil {
return false, errors.Wrap(err, "unable to get Bluetooth adapters")
}
if len(adapters) != 1 {
return false, errors.Errorf("failed to verify the number of Bluetooth adapters got %d, expected 1 ", len(adapters))
}
return adapters[0].Powered(ctx)
}
// IsDisabled checks if bluetooth adapter present and powered off
func IsDisabled(ctx context.Context) (bool, error) {
status, err := IsEnabled(ctx)
return !status, err
}
// Enable powers on the bluetooth adapter
func Enable(ctx context.Context) error {
adapters, err := Adapters(ctx)
if err != nil {
return errors.Wrap(err, "unable to get Bluetooth adapters")
}
if len(adapters) != 1 {
return errors.Errorf("failed to verify the number of Bluetooth adapters got %d, expected 1 ", len(adapters))
}
return adapters[0].SetPowered(ctx, true)
}
// PollForBTEnabled polls bluetooth adapter state till Adapter is powered on
func PollForBTEnabled(ctx context.Context) error {
return PollForAdapterState(ctx, true)
}
// PollForBTDisabled polls bluetooth adapter state till Adapter is powered off
func PollForBTDisabled(ctx context.Context) error {
return PollForAdapterState(ctx, false)
}
//PollForAdapterState polls bluetooth adapter state until expected state is received or timeout occurs.
func PollForAdapterState(ctx context.Context, exp bool) error {
return testing.Poll(ctx, func(ctx context.Context) error {
status, err := IsEnabled(ctx)
if err != nil {
return testing.PollBreak(errors.Wrap(err, "failed to check Bluetooth status"))
}
if status != exp {
return errors.Errorf("failed to verify Bluetooth status, got %t, want %t", status, exp)
}
return nil
}, &testing.PollOptions{Timeout: 10 * time.Second, Interval: time.Second})
}