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

Improve BLE reliability #269

Merged
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
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
36 changes: 34 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,26 @@ 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()
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
Loading