diff --git a/cmd/tesla-control/commands.go b/cmd/tesla-control/commands.go index ea3f2b11..9ba9f417 100644 --- a/cmd/tesla-control/commands.go +++ b/cmd/tesla-control/commands.go @@ -243,6 +243,25 @@ func (c *Command) Usage(name string) { } var commands = map[string]*Command{ + "valet-mode-on": &Command{ + help: "Enable valet mode", + requiresAuth: true, + requiresFleetAPI: false, + args: []Argument{ + Argument{name: "PIN", help: "Valet mode PIN"}, + }, + handler: func(ctx context.Context, acct *account.Account, car *vehicle.Vehicle, args map[string]string) error { + return car.EnableValetMode(ctx, args["PIN"]) + }, + }, + "valet-mode-off": &Command{ + help: "Disable valet mode", + requiresAuth: true, + requiresFleetAPI: false, + handler: func(ctx context.Context, acct *account.Account, car *vehicle.Vehicle, args map[string]string) error { + return car.DisableValetMode(ctx) + }, + }, "unlock": &Command{ help: "Unlock vehicle", requiresAuth: true, diff --git a/pkg/vehicle/security.go b/pkg/vehicle/security.go index c5716e6a..c7305d39 100644 --- a/pkg/vehicle/security.go +++ b/pkg/vehicle/security.go @@ -3,6 +3,8 @@ package vehicle import ( "context" "crypto/ecdh" + "errors" + "strings" "google.golang.org/protobuf/proto" @@ -13,8 +15,41 @@ import ( "github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/vcsec" ) +// IsValidPIN returns true if the pin is four digits. +func IsValidPIN(pin string) bool { + if len(pin) != 4 { + return false + } + for _, c := range pin { + if c < '0' || c > '9' { + return false + } + } + return true +} + +var ErrInvalidPIN = errors.New("PIN codes must be four digits") + +// EnableValetMode enters the vehicle's Valet Mode. This sets certain restrictions but disables PIN +// to Drive. Consult the Owner's Manual for details. The PIN must be a four-digit string. +func (v *Vehicle) EnableValetMode(ctx context.Context, pin string) error { + return v.SetValetMode(ctx, true, pin) +} + +// DisableValetMode exits Valet Mode. +func (v *Vehicle) DisableValetMode(ctx context.Context) error { + return v.SetValetMode(ctx, false, "") +} + +// SetValetMode enables or disables Valet Mode. A password must be provided when turning valet mode +// on, and should be empty when turning valet mode off. +// +// Deprecated: Use EnableValetMode or DisableValetMode. func (v *Vehicle) SetValetMode(ctx context.Context, on bool, valetPassword string) error { - return v.executeCarServerAction(ctx, + if on && !IsValidPIN(valetPassword) { + return ErrInvalidPIN + } + err := v.executeCarServerAction(ctx, &carserver.Action_VehicleAction{ VehicleAction: &carserver.VehicleAction{ VehicleActionMsg: &carserver.VehicleAction_VehicleControlSetValetModeAction{ @@ -25,6 +60,10 @@ func (v *Vehicle) SetValetMode(ctx context.Context, on bool, valetPassword strin }, }, }) + if !on && err != nil && strings.HasSuffix(err.Error(), "already off") { + return nil + } + return err } func (v *Vehicle) ResetValetPin(ctx context.Context) error { diff --git a/pkg/vehicle/security_test.go b/pkg/vehicle/security_test.go new file mode 100644 index 00000000..b3530a07 --- /dev/null +++ b/pkg/vehicle/security_test.go @@ -0,0 +1,30 @@ +package vehicle + +import ( + "testing" +) + +func TestValidPIN(t *testing.T) { + validPINs := []string{ + "0000", + "0123", + "4569", + } + invalidPINs := []string{ + "", + "123a", + "12345", + "1", + "four", + } + for _, p := range validPINs { + if !IsValidPIN(p) { + t.Errorf("%s is a valid PIN", p) + } + } + for _, p := range invalidPINs { + if IsValidPIN(p) { + t.Errorf("%s is not a valid PIN", p) + } + } +}