Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow selecting BT adapter via BD address #370

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ cscope.*
cmd/tesla-control/tesla-control
cmd/tesla-auth-token/tesla-auth-token
cmd/tesla-key-setup/tesla-key-setup
cmd/test-ble/test-ble
examples/unlock/unlock
examples/ble/ble
*.DS_Store
Expand Down
39 changes: 39 additions & 0 deletions cmd/test-ble/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"flag"
"strings"

goble "github.com/go-ble/ble"
"github.com/teslamotors/vehicle-command/internal/log"
"github.com/teslamotors/vehicle-command/pkg/connector/ble"
)

var (
bdAddr = flag.String("bdAddr", "", "Bluetooth device address")
)

func main() {
flag.Parse()
log.SetLevel(log.LevelDebug)

var err error
if bdAddr != nil {
log.Info("Target BLE device address: %s", *bdAddr)
err = ble.InitDevice(goble.NewAddr(*bdAddr))
} else {
log.Info("Using first available BLE device")
err = ble.InitDevice(nil)
}

if err != nil {
if strings.Contains(err.Error(), "failed to find a BLE device") {
log.Error("No BLE device found")
} else {
log.Error("Failed to initialize BLE device: %v", err)
}
return
}

log.Info("BLE is working")
}
13 changes: 13 additions & 0 deletions pkg/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import (
"github.com/teslamotors/vehicle-command/pkg/vehicle"

"github.com/99designs/keyring"
goble "github.com/go-ble/ble"
)

var DomainsByName = map[string]protocol.Domain{
Expand Down Expand Up @@ -150,6 +151,7 @@ type Config struct {
DisableCache bool
Backend keyring.Config
BackendType backendType
BdAddress string
Debug bool // Enable keyring debug messages

// Domains can limit a vehicle connection to relevant subsystems, which can reduce
Expand Down Expand Up @@ -183,6 +185,9 @@ func (c *Config) RegisterCommandLineFlags() {
if c.Flags.isSet(FlagVIN) {
flag.StringVar(&c.VIN, "vin", "", "Vehicle Identification Number. Defaults to $TESLA_VIN.")
}
if c.Flags.isSet(FlagBLE) {
flag.StringVar(&c.BdAddress, "bdAddr", "", "Bluetooth device address")
}
if c.Flags.isSet(FlagPrivateKey) {
if !c.Flags.isSet(FlagVIN) {
log.Debug("FlagPrivateKey is set but FlagVIN is not. A VIN is required to send vehicle commands.")
Expand Down Expand Up @@ -469,6 +474,14 @@ func (c *Config) ConnectRemote(ctx context.Context, skey protocol.ECDHPrivateKey

// ConnectLocal connects to a vehicle over BLE.
func (c *Config) ConnectLocal(ctx context.Context, skey protocol.ECDHPrivateKey) (car *vehicle.Vehicle, err error) {
var addr goble.Addr
if c.BdAddress != "" {
addr = goble.NewAddr(c.BdAddress)
}
err = ble.InitDevice(addr)
if err != nil {
return nil, err
}
conn, err := ble.NewConnection(ctx, c.VIN)
if err != nil {
return nil, err
Expand Down
38 changes: 34 additions & 4 deletions pkg/connector/ble/ble.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,23 @@ func VehicleLocalName(vin string) string {
return fmt.Sprintf("S%02xC", digest[:8])
}

func initDevice() error {
// InitDevice initializes a device with the local Bluetooth device address.
// If bdAddr is nil, the first available BLE device will be used.
// On linux it is a MAC address, on MacOS it is a UUID.
// If this function is not called before making a connection, the first available
// BLE device will be used, and this function will return an error.
// NOTE: Currently targeting an address is only supported on Linux.
func InitDevice(bdAddr ble.Addr) error {
mu.Lock()
defer mu.Unlock()
if device != nil {
return fmt.Errorf("ble: device already initialized")
}

return initDevice(bdAddr)
}

func initDevice(bdAddr ble.Addr) error {
var err error
// We don't want concurrent calls to NewConnection that would defeat
// the point of reusing the existing BLE device. Note that this is not
Expand All @@ -141,7 +157,7 @@ func initDevice() error {
log.Debug("Reusing existing BLE device")
} else {
log.Debug("Creating new BLE device")
device, err = newDevice()
device, err = newDevice(bdAddr)
if err != nil {
return fmt.Errorf("failed to find a BLE device: %s", err)
}
Expand All @@ -150,13 +166,27 @@ func initDevice() error {
return nil
}

func StopDevice() error {
mu.Lock()
defer mu.Unlock()
if device == nil {
log.Debug("Closing a non-existent BLE device")
return nil
}
if err := device.Stop(); err != nil {
return fmt.Errorf("ble: failed to stop device: %s", err)
}
device = nil
return nil
}

type Advertisement = ble.Advertisement

func ScanVehicleBeacon(ctx context.Context, vin string) (Advertisement, error) {
mu.Lock()
defer mu.Unlock()

if err := initDevice(); err != nil {
if err := initDevice(nil); err != nil {
return nil, err
}

Expand Down Expand Up @@ -235,7 +265,7 @@ func tryToConnect(ctx context.Context, vin string, target Advertisement) (*Conne
mu.Lock()
defer mu.Unlock()

if err = initDevice(); err != nil {
if err = initDevice(nil); err != nil {
return nil, false, err
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/connector/ble/device_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package ble
import (
"github.com/go-ble/ble"
"github.com/go-ble/ble/darwin"
"github.com/teslamotors/vehicle-command/internal/log"
)

func newDevice() (ble.Device, error) {
func newDevice(bdAddr ble.Addr) (ble.Device, error) {
if bdAddr != nil || bdAddr.String() != "" {
log.Warning("Setting the Bluetooth device address is not supported on Darwin")
}
device, err := darwin.NewDevice()
if err != nil {
return nil, err
Expand Down
58 changes: 55 additions & 3 deletions pkg/connector/ble/device_linux.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package ble

import (
"fmt"
"strings"
"time"

"github.com/go-ble/ble"
"github.com/go-ble/ble/linux"
"github.com/go-ble/ble/linux/hci"
"github.com/go-ble/ble/linux/hci/cmd"
"time"
"github.com/teslamotors/vehicle-command/internal/log"
)

const bleTimeout = 20 * time.Second
Expand All @@ -19,8 +24,55 @@ var scanParams = cmd.LESetScanParameters{
ScanningFilterPolicy: 2, // Basic filtered
}

func newDevice() (ble.Device, error) {
device, err := linux.NewDevice(ble.OptListenerTimeout(bleTimeout), ble.OptDialerTimeout(bleTimeout), ble.OptScanParams(scanParams))
func newDevice(bdAddr ble.Addr) (ble.Device, error) {
maxHciDevices := 16
hciX := -1
log.Debug("Scanning for HCI devices")
bdAddrStr := ""
if bdAddr != nil {
bdAddrStr = bdAddr.String()
}
var lastErr error
for i := 0; i < maxHciDevices; i++ {
devHci, err := hci.NewHCI(ble.OptDeviceID(i))
if err != nil {
return nil, fmt.Errorf("can't create HCI %d: %v", i, err)
}

if err = devHci.Init(); err != nil {
if !strings.Contains(err.Error(), "no such device") {
lastErr = err
log.Debug("Can't init HCI %d: %v", i, err)
}
continue
}
if err = devHci.Close(); err != nil {
return nil, fmt.Errorf("can't close HCI %d: %v", i, err)
}

log.Debug("Found HCI %d: %s", i, devHci.Addr())
if bdAddrStr == "" || devHci.Addr().String() == bdAddrStr {
hciX = i
break
}
}

if hciX == -1 && lastErr != nil {
return nil, lastErr
} else if hciX == -1 {
return nil, fmt.Errorf("no device with address %s", bdAddr)
}
log.Debug("Using HCI %d", hciX)

opts := []ble.Option{
ble.OptDeviceID(hciX),
ble.OptListenerTimeout(bleTimeout),
ble.OptDialerTimeout(bleTimeout),
ble.OptScanParams(scanParams),
}

device, err := linux.NewDeviceWithName("vehicle-command", opts...)

if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/connector/ble/device_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package ble

import (
"errors"

"github.com/go-ble/ble"
)

func newDevice() (ble.Device, error) {
func newDevice(ble.Addr) (ble.Device, error) {
return nil, errors.New("not supported on Windows")
}