Skip to content
Merged
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
11 changes: 5 additions & 6 deletions cmd/tesla-control/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/teslamotors/vehicle-command/internal/log"
"github.com/teslamotors/vehicle-command/pkg/account"
"github.com/teslamotors/vehicle-command/pkg/cli"
"github.com/teslamotors/vehicle-command/pkg/connector/ble"
"github.com/teslamotors/vehicle-command/pkg/protocol"
"github.com/teslamotors/vehicle-command/pkg/vehicle"
)
Expand Down Expand Up @@ -163,12 +164,10 @@ func main() {

acct, car, err := config.Connect(ctx)
if err != nil {
writeErr("Error: %s", err)
// Error isn't wrapped so we have to check for a substring explicitly.
if strings.Contains(err.Error(), "operation not permitted") {
// The underlying BLE package calls HCIDEVDOWN on the BLE device, presumably as a
// heavy-handed way of dealing with devices that are in a bad state.
writeErr("\nTry again after granting this application CAP_NET_ADMIN:\n\n\tsudo setcap 'cap_net_admin=eip' \"$(which %s)\"\n", os.Args[0])
if ble.IsAdapterError(err) {
writeErr("%s", ble.AdapterErrorHelpMessage(err))
} else {
writeErr("Error: %s", err)
}
return
}
Expand Down
10 changes: 10 additions & 0 deletions examples/ble/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ your car and turns on the AC.
For more fleshed out examples of other commands, see
[github.com/tesla/vehicle-command/pkg/cmd/tesla-control].

# Scanning for vehicles

To scan for vehicles, use the -scan-only flag:

./ble -scan-only -vin YOUR_VIN

# Pairing with the vehicle

To generate a key pair with OpenSSL:
Expand All @@ -26,5 +32,9 @@ Sending commands to the vehicle requires the private key you generated above:
./ble -vin YOUR_VIN -key private.pem

You can add the -debug flag to inspect the bytes sent over BLE.

You can also specify the Bluetooth adapter to use on Linux with the -bt-adapter flag:

./ble -vin YOUR_VIN -key private.pem -bt-adapter hci0
*/
package main
66 changes: 60 additions & 6 deletions examples/ble/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
"log"
"os"
"os/signal"
"runtime"
"time"

debugger "github.com/teslamotors/vehicle-command/internal/log"
Expand All @@ -27,29 +29,74 @@ func main() {

// Provided through command line options
var (
scanOnly bool
btAdapter string
privateKeyFile string
vin string
)
flag.BoolVar(&scanOnly, "scan-only", false, "Scan for vehicles and exit")
flag.StringVar(&privateKeyFile, "key", "", "Private key `file` for authorizing commands (PEM PKCS8 NIST-P256)")
flag.StringVar(&vin, "vin", "", "Vehicle Identification Number (`VIN`) of the car")
flag.BoolVar(&debug, "debug", false, "Enable debugging of TX/RX BLE packets")
if runtime.GOOS == "linux" {
flag.StringVar(&btAdapter, "bt-adapter", "", "Optional ID of Bluetooth adapter to use")
}

flag.Parse()

if debug {
debugger.SetLevel(debugger.LevelDebug)
}

// For simplcity, allow 30 seconds to wake up the vehicle, connect to it,
// and unlock. In practice you'd want a fresh timeout for each command.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err := ble.InitAdapterWithID(btAdapter)
if err != nil {
if ble.IsAdapterError(err) {
logger.Print(ble.AdapterErrorHelpMessage(err))
} else {
logger.Printf("Failed to initialize BLE adapter: %s", err)
}
return
}

if vin == "" {
logger.Printf("Must specify VIN")
return
}

var err error
if scanOnly {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
doneChan := make(chan struct{})
go func() {
_, err := ble.ScanVehicleBeacon(ctx, vin)
if err != nil && ctx.Err() == nil {
logger.Printf("Scan failed: %s", err)
} else if ctx.Err() == nil {
logger.Printf("Found vehicle")
status = 0
}
close(doneChan)
}()
logger.Printf("Scanning for BLE devices until interrupted")

signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
select {
case <-doneChan:
case <-signalChan:
logger.Printf("Stopping scan")
cancel()
<-doneChan
status = 130 // Script terminated by SIGINT
}
return
}

// For simplicity, allow 30 seconds to wake up the vehicle, connect to it,
// and unlock. In practice you'd want a fresh timeout for each command.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

var privateKey protocol.ECDHPrivateKey
if privateKeyFile != "" {
if privateKey, err = protocol.LoadPrivateKey(privateKeyFile); err != nil {
Expand All @@ -58,7 +105,14 @@ func main() {
}
}

conn, err := ble.NewConnection(ctx, vin)
scan, err := ble.ScanVehicleBeacon(ctx, vin)
if err != nil {
logger.Println(err)
return
}
logger.Printf("Found vehicle: %s (%s) %ddBm", scan.LocalName, scan.Address, scan.RSSI)

conn, err := ble.NewConnectionFromScanResult(ctx, vin, scan)
if err != nil {
logger.Printf("Failed to connect to vehicle: %s", err)
return
Expand Down
7 changes: 7 additions & 0 deletions pkg/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ type Config struct {
KeyringKeyName string // Username for private key in system keyring
KeyringTokenName string // Username for OAuth token in system keyring
VIN string
BtAdapterID string // ID of Bluetooth adapter to use (Linux only)
TokenFilename string
KeyFilename string
CacheFilename string
Expand Down Expand Up @@ -207,6 +208,7 @@ func (c *Config) RegisterCommandLineFlags() {
flag.StringVar(&c.Backend.FileDir, "keyring-file-dir", keyringDirectory, "keyring `directory` for file-backed keyring types")
flag.BoolVar(&c.Debug, "keyring-debug", false, "Enable keyring debug logging")
}
c.registerCommandLineFlagsOsSpecific()
}

// LoadCredentials attempts to open a keyring, prompting for a password if not needed. Call this
Expand Down Expand Up @@ -469,6 +471,11 @@ 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) {
err = ble.InitAdapterWithID(c.BtAdapterID)
if err != nil {
return nil, err
}

conn, err := ble.NewConnection(ctx, c.VIN)
if err != nil {
return nil, err
Expand Down
5 changes: 5 additions & 0 deletions pkg/cli/config_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package cli

func (c *Config) registerCommandLineFlagsOsSpecific() {
// Nothing yet
}
9 changes: 9 additions & 0 deletions pkg/cli/config_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cli

import "flag"

func (c *Config) registerCommandLineFlagsOsSpecific() {
if c.Flags.isSet(FlagBLE) {
flag.StringVar(&c.BtAdapterID, "bt-adapter", "", "ID of the Bluetooth adapter to use. Defaults to hci0.")
}
}
5 changes: 5 additions & 0 deletions pkg/cli/config_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package cli

func (c *Config) registerCommandLineFlagsOsSpecific() {
// Nothing yet
}
Loading