Skip to content

Commit

Permalink
[Draft] Improve BLE reliability
Browse files Browse the repository at this point in the history
 - Increase timeouts for BLE dialer and listener
 - Retry initial connection on non-fatal errors until context expires
 - Tweak scanning interval to reflect BLE advertisement parameters

TODO:

 - Retry connection using client MAC address instead of scanning
 - Optimize scanning interval
 - Adjust timeouts using caller context
 - Test Maximum BLE connections
 - Test vehicles with different BLE advertisement intervals
  • Loading branch information
Seth Terashima committed Jul 2, 2024
1 parent 05bc5dd commit def3d7b
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 4 deletions.
2 changes: 1 addition & 1 deletion cmd/tesla-control/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ var commands = map[string]*Command{
},
"session-info": &Command{
help: "Retrieve session info for PUBLIC_KEY from DOMAIN",
requiresAuth: true,
requiresAuth: false,
requiresFleetAPI: false,
args: []Argument{
Argument{name: "PUBLIC_KEY", help: "file containing public key (or corresponding private key)"},
Expand Down
37 changes: 35 additions & 2 deletions pkg/connector/ble/ble.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import (
"context"
"crypto/sha1"
"fmt"
"strings"
"sync"
"time"

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

const maxBLEMessageSize = 1024

var ErrMaxConnectionsExceeded = protocol.NewError("the vehicle is already connected to the maximum number of BLE devices", false, false)

var (
rxTimeout = time.Second // Timeout interval between receiving chunks of a mesasge
maxLatency = 4 * time.Second // Max allowed error when syncing vehicle clock
Expand Down Expand Up @@ -122,6 +126,27 @@ func (c *Connection) VIN() string {
}

func NewConnection(ctx context.Context, vin string) (*Connection, error) {
var lastError error
for {
conn, err := tryToConnect(ctx, vin)
if err == nil {
return conn, nil
}
if strings.Contains(err.Error(), "operation not permitted") {
return nil, err
}
log.Warning("BLE connection attempt failed: %s", err)
if err := ctx.Err(); err != nil {
if lastError != nil {
return nil, lastError
}
return nil, err
}
lastError = err
}
}

func tryToConnect(ctx context.Context, vin string) (*Connection, 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 @@ -145,19 +170,27 @@ func NewConnection(ctx context.Context, vin string) (*Connection, error) {

localName := fmt.Sprintf("S%02xC", digest[:8])
log.Debug("Searching for BLE beacon %s...", localName)
canConnect := false
filter := func(adv ble.Advertisement) bool {
if !adv.Connectable() || adv.LocalName() != localName {
ln := adv.LocalName()
log.Debug(" ... found '%s' (RSSI=%3d)", ln, adv.RSSI())
if ln != localName {
return false
}
canConnect = adv.Connectable()
return true
}

log.Debug("Connecting to BLE beacon...")
client, err := ble.Connect(ctx, filter)
if err != nil {
return nil, fmt.Errorf("failed to find BLE beacon for %s (%s): %s", vin, localName, err)
}

if !canConnect {
return nil, ErrMaxConnectionsExceeded
}

log.Debug("Connecting to BLE beacon %s...", client.Addr())
services, err := client.DiscoverServices([]ble.UUID{vehicleServiceUUID})
if err != nil {
return nil, fmt.Errorf("ble: failed to enumerate device services: %s", err)
Expand Down
16 changes: 15 additions & 1 deletion pkg/connector/ble/device_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,24 @@ package ble
import (
"github.com/go-ble/ble"
"github.com/go-ble/ble/linux"
"github.com/go-ble/ble/linux/hci/cmd"
"time"
)

const bleTimeout = 20 * time.Second

// TODO: Depending on the model and state, BLE advertisements come every 20ms or every 150ms.

var scanParams = cmd.LESetScanParameters{
LEScanType: 1, // Active scanning
LEScanInterval: 0x10, // 10ms
LEScanWindow: 0x10, // 10ms
OwnAddressType: 0, // Static
ScanningFilterPolicy: 2, // Basic filtered
}

func newDevice() (ble.Device, error) {
device, err := linux.NewDevice()
device, err := linux.NewDevice(ble.OptListenerTimeout(bleTimeout), ble.OptDialerTimeout(bleTimeout), ble.OptScanParams(scanParams))
if err != nil {
return nil, err
}
Expand Down

0 comments on commit def3d7b

Please sign in to comment.