MyTesla.psm1

#Region '.\_PrefixCode.ps1' 0
# Code in here will be prepended to top of the psm1-file.
$Script:TeslaConfiguration = @{
    'LastSeen' = [System.DateTimeOffset]::MinValue
}

$Script:AuthUrl = @{
    'USA' = 'auth.tesla.com'
    'China' = 'auth.tesla.cn'
}
#EndRegion '.\_PrefixCode.ps1' 10
#Region '.\Classes\TeslaSeat.ps1' 0
enum TeslaSeat {
    Driver = 0
    Passenger = 1
    BackseatLeft = 2
    BackseatCenter = 3
    BackseatRight = 4
} 
#EndRegion '.\Classes\TeslaSeat.ps1' 8
#Region '.\Classes\TeslaVehicle.ps1' 0
Class TeslaVehicle {
    [System.Int64] $Id
    [System.Int64] $vehicle_id
    [System.String] $vin
    [System.String] $display_name
    [System.String] $option_codes
    [System.String] $color
    [System.String] $access_type
    [System.String[]] $tokens
    [System.String] $state
    [System.Boolean] $in_service
    [System.String] $id_s
    [System.Boolean] $calendar_enabled
    [System.Int64] $api_version
    [System.Object] $backseat_token
    [System.Object] $backseat_token_updated_at
}
#EndRegion '.\Classes\TeslaVehicle.ps1' 18
#Region '.\Private\AuthHelpers.ps1' 0
function Get-RandomString {
    param (
        [Parameter(Mandatory)]
        [int]
        $Length
    )
    -join (Get-Random -InputObject 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.ToCharArray() -Count $Length)
}

function ConvertTo-SHA256Hash {
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string]
        $String
    )
    process {
        $Hasher = [System.Security.Cryptography.SHA256]::Create()
        $HashBytes = $Hasher.ComputeHash([System.Text.Encoding]::Default.GetBytes($String))
        $Hash = ConvertTo-Hex -Bytes $HashBytes
        Write-Output $Hash
    }
}

function ConvertTo-Hex {
    param(
        [Parameter(ValueFromPipeline)]
        [byte[]]
        $Bytes,

        [switch]
        $ToUpper
    )
    process {
        $format = if ($ToUpper.IsPresent) { 'X2' } else { 'x2' }
        $HexChars = $Bytes | Foreach-Object -MemberName ToString -ArgumentList $format
        $HexString = -join $HexChars
        Write-Output $HexString
    }
}

function ConvertTo-Base64 {
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string]$String
    )
    process {
        [convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($String))
    }
}

function ConvertTo-UrlEncodedContent {
    [CmdletBinding()]
    [OutputType([String])]
    param(
        [hashtable]
        $Hashtable
    )
    $Dictionary = [System.Collections.Generic.Dictionary[string, string]]::new()
    foreach ($key in $Hashtable.Keys) {
        $Dictionary.Add($key, $Hashtable[$key])
    }
    $FormUrlencodedContent = [System.Net.Http.FormUrlEncodedContent]::new($Dictionary)
    return [System.Text.Encoding]::UTF8.GetString($FormUrlencodedContent.ReadAsByteArrayAsync().Result)
}
function ConvertTo-QueryString {
    [CmdletBinding()]
    [OutputType([String])]
    param(
        [System.Collections.Specialized.OrderedDictionary]
        $Hashtable
    )
    $QueryString = [System.Web.HttpUtility]::ParseQueryString('')
    foreach ($key in $Hashtable.Keys) {
        $QueryString.Add($key, $Hashtable[$key])
    }
    return $QueryString.ToString()
}

function New-LoginSession {
    param(
        [validateset('USA', 'China')]
        [string]
        $Region = 'USA'
    )

    $LoginSession = @{
        'CodeVerifier' = Get-RandomString -Length 86
        'State'        = Get-RandomString -Length 20
        'Region'       = $Region
        'BaseUri'      = $Script:AuthUrl[$Region]
        'UserAgent'    = '007'
    }
    $LoginSession['CodeChallenge'] = $LoginSession['CodeVerifier'] | ConvertTo-SHA256Hash | ConvertTo-Base64

    $Fragment = 'oauth2/v3/authorize'
    $Uri = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $Fragment)

    $Query = [ordered]@{
        'client_id'             = 'ownerapi'
        'code_challenge'        = $LoginSession.CodeChallenge
        'code_challenge_method' = 'S256'
        'redirect_uri'          = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, 'void/callback').Uri.ToString()
        'response_type'         = 'code'
        'scope'                 = 'openid email offline_access'
        'state'                 = $LoginSession.State
    }
    $Uri.Query = ConvertTo-QueryString -Hashtable $Query

    $Params = @{
        Uri                = $Uri.Uri.ToString()
        Method             = 'GET'
        UserAgent          = $LoginSession.UserAgent
        WebSession         = $LoginSession.WebSession
        MaximumRedirection = 0
        Headers            = @{
            'Accept'          = 'application/json'
            'Accept-Encoding' = 'gzip, deflate'
        }
    }
    $Response = Invoke-WebRequest @Params -SessionVariable 'WebSession' -ErrorAction 'Stop'
    $FormFields = @{}
    [Regex]::Matches($Response.Content, 'type=\"hidden\" name=\"(?<name>.*?)\" value=\"(?<value>.*?)\"') | Foreach-Object {
        $FormFields.Add($_.Groups['name'].Value, $_.Groups['value'].Value)
    }
    $LoginSession.FormFields = $FormFields
    $LoginSession['WebSession'] = $WebSession

    return $LoginSession
}

function Get-TeslaAuthCode {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSAvoidUsingPlainTextForPassword',
        'Password',
        Justification = 'We are sending the password as plain text in body,
                         not really a point to have it in a securestring in this private function.'

    )]
    param(
        [Parameter(Mandatory)]
        [string]
        $Username,

        [Parameter(Mandatory)]
        [string]
        $Password,

        [Parameter()]
        [string]
        $MfaCode,

        [Parameter(Mandatory)]
        [hashtable]
        $LoginSession
    )
    $LoginSession.FormFields['identity'] = $Username
    $LoginSession.FormFields['credential'] = $Password

    $Fragment = 'oauth2/v3/authorize'
    $Uri = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $Fragment)

    $Query = [ordered]@{
        'client_id'             = 'ownerapi'
        'code_challenge'        = $LoginSession.CodeChallenge
        'code_challenge_method' = 'S256'
        'redirect_uri'          = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, 'void/callback').Uri.ToString()
        'response_type'         = 'code'
        'scope'                 = 'openid email offline_access'
        'state'                 = $LoginSession.State
    }
    $Uri.Query = ConvertTo-QueryString -Hashtable $Query

    # Try here to catch HTTP Redirect
    try {
        $Body = ConvertTo-UrlEncodedContent $LoginSession.FormFields
        $Params = @{
            Uri                = $Uri.Uri.ToString()
            Method             = 'POST'
            ContentType        = 'application/x-www-form-urlencoded'
            Body               = $Body
            UserAgent          = $LoginSession.UserAgent
            WebSession         = $LoginSession.WebSession
            MaximumRedirection = 0
            Headers            = @{
                'Accept'          = 'application/json'
                'Accept-Encoding' = 'gzip, deflate'
            }
        }
        $Response = Invoke-WebRequest @Params -ErrorAction 'Stop'
        if ($Response.StatusCode -eq [System.Net.HttpStatusCode]::OK -and $Response.Content.Contains('passcode')) {
            Write-Verbose -Message 'MFA Requried'
            $MFARequirements = Get-MFARequirements -LoginSession $LoginSession
            if (-not [string]::IsNullOrEmpty($MfaCode)) {
                foreach ($MFAId in $MFARequirements) {
                    if (Submit-MfaCode -MfaId $MfaId.id -MfaCode $MfaCode -LoginSession $LoginSession) {
                        $Code = Get-TeslaAuthCodeMfa -LoginSession $LoginSession
                        return $Code
                    }
                }
            }
            else {
                throw 'MFA code is required.' # use $MFARequirements here
            }

        }
        else {
            throw 'Failed to get AuthCode, no redirect.'
        }
    }
    catch [Microsoft.PowerShell.Commands.HttpResponseException] {
        if ($_.exception.response.StatusCode -eq [System.Net.HttpStatusCode]::Redirect) {
            Write-Verbose -Message 'Got redirect, parsing Location without MFA'
            [uri]$Location = $_.exception.response.headers.Location.OriginalString
            if (-not [string]::IsNullOrEmpty($Location)) {
                $Code = [System.Web.HttpUtility]::ParseQueryString($Location.Query).Get('Code')
                if (-not [string]::IsNullOrEmpty($Code)) {
                    return $Code
                }
                else {
                    throw 'No auth code received. Please try again later.'
                }
            }
            else {
                throw 'Redirect location not found.'
            }
        }
        else {
            throw
        }
    }

}

function Submit-MfaCode {
    param(
        [Parameter(Mandatory)]
        [string]
        $MfaId,

        [Parameter(Mandatory)]
        [string]
        $MfaCode,

        [Parameter(Mandatory)]
        [hashtable]
        $LoginSession
    )
    $Fragment = 'oauth2/v3/authorize/mfa/verify'
    $Uri = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $Fragment)
    $Params = @{
        Uri         = $Uri.Uri.ToString()
        Method      = 'POST'
        ContentType = 'application/json; charset=utf-8'
        Body        = [ordered]@{
            'factor_id'      = $MfaId
            'passcode'       = $MfaCode
            'transaction_id' = $LoginSession.FormFields.transaction_id
        } | ConvertTo-Json -Compress
        
        UserAgent   = $LoginSession.UserAgent
        WebSession  = $LoginSession.WebSession
        Headers     = @{
            'Accept'          = 'application/json'
            'Accept-Encoding' = 'gzip, deflate'
            'Referer'         = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $null).Uri.ToString()
        }
    }
    $Response = Invoke-WebRequest @Params -ErrorAction 'Stop'
    $Content = $Response.Content | ConvertFrom-Json
    $IsValid = [bool]$Content.data.valid
    return $IsValid
}

function Get-MFARequirements {
    param(
        [Parameter(Mandatory)]
        [hashtable]
        $LoginSession
    )
    $Fragment = 'oauth2/v3/authorize/mfa/factors'
    $Uri = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $Fragment)
    $Query = [ordered]@{
        'transaction_id' = $LoginSession.FormFields.transaction_id
    }
    $Uri.Query = ConvertTo-QueryString -Hashtable $Query

    $Params = @{
        Uri                = $Uri.Uri
        Method             = 'GET'
        UserAgent          = $LoginSession.UserAgent
        WebSession         = $LoginSession.WebSession
        MaximumRedirection = 0
        Headers            = @{
            'Accept'          = 'application/json'
            'Accept-Encoding' = 'gzip, deflate'
        }
    }
    $Response = Invoke-WebRequest @Params -ErrorAction 'Stop'
    $Content = $Response.Content | ConvertFrom-Json
    return $Content.data
    # foreach ($MfaId in $Content.data) {
    # if (Test-MfaCode -MfaId $MfaId.id -MfaCode $MfaCode -LoginSession $LoginSession) {
    # return $true
    # }
    # }

    # # If we get here, MFA was invalid
    # return $false
}

function Get-TeslaAuthCodeMfa {
    param(
        [Parameter(Mandatory)]
        [hashtable]
        $LoginSession
    )

    $Fragment = 'oauth2/v3/authorize'
    $Uri = [System.UriBuilder]::new('https',$LoginSession.BaseUri,443,$Fragment)

    $Body = ConvertTo-UrlEncodedContent @{
        'transaction_id' = $LoginSession.FormFields['transaction_id']
    }

    $Query = [ordered]@{
        'client_id'             = 'ownerapi'
        'code_challenge'        = $LoginSession.CodeChallenge
        'code_challenge_method' = 'S256'
        'redirect_uri'          = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, 'void/callback').Uri.ToString()
        'response_type'         = 'code'
        'scope'                 = 'openid email offline_access'
        'state'                 = $LoginSession.State
    }
    $Uri.Query = ConvertTo-QueryString -Hashtable $Query

    try {
        $Params = @{
            Uri                = $Uri.Uri.ToString()
            Method             = 'POST'
            ContentType        = 'application/x-www-form-urlencoded'
            Body               = $Body
            UserAgent          = $LoginSession.UserAgent
            WebSession         = $LoginSession.WebSession
            MaximumRedirection = 0
            Headers            = @{
                'Accept'          = 'application/json'
                'Accept-Encoding' = 'gzip, deflate'
            }
        }
        $Response = Invoke-WebRequest @Params -ErrorAction 'Stop'
        # If we get here, we failed. Write terminating error.
        Write-Error -Message 'Failed to get AuthCode with MFA, no redirect.' -TargetObject $Response -ErrorAction 'Stop'
    }
    catch [Microsoft.PowerShell.Commands.HttpResponseException] {
        if ($_.exception.response.StatusCode -eq [System.Net.HttpStatusCode]::Redirect) {
            [uri]$Location = $_.exception.response.headers.Location.OriginalString
            if (-not [string]::IsNullOrEmpty($Location)) {
                $Code = [System.Web.HttpUtility]::ParseQueryString($Location.Query).Get('Code')
                if (-not [string]::IsNullOrEmpty($Code)) {
                    return $Code
                }
                else {
                    Write-Error -Message 'No auth code received. Please try again later.' -Exception $_.Exception -TargetObject $_ -ErrorAction 'Stop'
                }
            }
            else {
                Write-Error -Message 'Redirect location not found.' -Exception $_.Exception -TargetObject $_ -ErrorAction 'Stop'
            }
        }
    }
}
function Get-TeslaAuthToken {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]
        $Code,

        [Parameter(Mandatory)]
        [hashtable]
        $LoginSession
    )

    $Fragment = 'oauth2/v3/token'
    $Uri = [System.UriBuilder]::new('https',$LoginSession.BaseUri,443,$Fragment)
    $Params = @{
        Uri         = $Uri.Uri.ToString()
        Method      = 'POST'
        Body        = [ordered]@{
            'grant_type'    = 'authorization_code'
            'client_id'     = 'ownerapi'
            'code'          = $Code
            'code_verifier' = $LoginSession.CodeVerifier
            'redirect_uri'  = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, 'void/callback').Uri.ToString()
        } | ConvertTo-Json -Compress
        ContentType = 'application/json; charset=utf-8'
        UserAgent   = $LoginSession.UserAgent
        WebSession  = $LoginSession.WebSession
        Headers     = @{
            'Accept'          = 'application/json'
            'Accept-Encoding' = 'gzip, deflate'
        }
    }
    $Response = Invoke-WebRequest @Params -ErrorAction 'Stop'

    $CreationTime = $Response.Headers.Date | Foreach-Object { $_ -as [System.DateTimeOffset] }
    $Content = $Response.Content | ConvertFrom-Json
    $Token = @{
        AccessToken  = $Content.access_token
        RefreshToken = $Content.refresh_token
        IdToken      = $Content.id_token
        WhenCreated  = $CreationTime
        WhenExpires  = $CreationTime.AddSeconds($Content.expires_in)
        State        = $Content.state
        Response     = $Response
    }
    return $Token
}

function Get-TeslaAccessToken {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]
        $AuthToken
    )

    $Fragment = 'oauth/token'
    $Uri = [System.UriBuilder]::new('https','owner-api.teslamotors.com',443,$Fragment)
    $Params = @{
        Uri         = $Uri.Uri.ToString()
        Method      = 'POST'
        Body        = @{
            'grant_type'    = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
            'client_id'     = '81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384'
            'client_secret' = 'c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3'
        } | ConvertTo-Json -Compress
        Headers     = @{
            'Authorization' = [System.Net.Http.Headers.AuthenticationHeaderValue]::new('Bearer', $AuthToken)
        }
        ContentType = 'application/json; charset=utf-8'
        UserAgent   = '007'
    }
    $Response = Invoke-WebRequest @Params -ErrorAction 'Stop'
    $Content = $Response.Content | ConvertFrom-Json
    $CreationTime = [System.DateTimeOffset]::FromUnixTimeSeconds($Content.created_at)
    $Token = @{
        AccessToken  = $Content.access_token
        TokenType    = $Content.token_type
        RefreshToken = $Content.refresh_token
        CreatedAt    = $CreationTime
        ExpiresAt    = $CreationTime.AddSeconds($Content.expires_in)
    }
    return $Token
}
#EndRegion '.\Private\AuthHelpers.ps1' 455
#Region '.\Private\ConvertFrom-Timestamp.ps1' 0
function ConvertFrom-Timestamp {
    [CmdletBinding()]
    param (
        [parameter(Mandatory, ValueFromPipeline)]
        [double]
        $Timestamp
    )
    
    process {
        [System.DateTimeOffset]::UnixEpoch.AddMilliSeconds($Timestamp).ToLocalTime()
    }
}
#EndRegion '.\Private\ConvertFrom-Timestamp.ps1' 13
#Region '.\Private\Get-TeslaToken.ps1' 0
function Get-TeslaToken {
    [CmdletBinding()]
    param (
        # Type of token
        [Parameter()]
        [validateset('Bearer', 'Access', 'Refresh')]
        [string]
        $Type,

        # Auto renew token if it expires in less than this many days, set to 0 to disable autorenewal.
        [int]
        [ValidateRange(0, 45)]
        $RenewalThreshold = 10
    )

    if ( 
        $RenewalThreshold -gt 0 -and
        $null -ne $Script:TeslaConfiguration['Token'].WhenExpires -and
        $Script:TeslaConfiguration['Token'].WhenExpires -lt [System.DateTimeOffset]::Now.AddDays($RenewalThreshold)
    ) {
        try {
            Connect-Tesla -RefreshToken $Script:TeslaConfiguration['Token'].RefreshToken -ErrorAction 'Stop'
        }
        catch {
            throw 'Failed to renew token in cache.'
        }
    }

    if ($null -ne $Script:TeslaConfiguration['Token']) {
        switch ($Type) {
            'Bearer' { 
                if ($null -ne $Script:TeslaConfiguration['Token'].'AccessToken') {
                    return 'Bearer {0}' -f ($Script:TeslaConfiguration['Token'].'AccessToken' | Unprotect-SecureString)
                }
            }
            'Access' { 
                if ($null -ne $Script:TeslaConfiguration['Token'].'AccessToken') {
                    return $Script:TeslaConfiguration['Token'].'AccessToken' | Unprotect-SecureString
                }
            }
            'Refresh' { 
                if ($null -ne $Script:TeslaConfiguration['Token'].'RefreshToken') {
                    return $Script:TeslaConfiguration['Token'].'RefreshToken' | Unprotect-SecureString
                }
            }
            Default {
                throw "Type [$Type] not implemented"
            }
        }
    }

    throw 'Not signed in, please use Connect-Tesla to sign in to the API'
    
}
#EndRegion '.\Private\Get-TeslaToken.ps1' 55
#Region '.\Private\Unprotect-SecureString.ps1' 0
<#
.NOTES
    Code taken from http://blog.majcica.com/2015/11/17/powershell-tips-and-tricks-decoding-securestring/
#>

function Unprotect-SecureString {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [securestring]
        $SecureString
    )
    
    process {
        $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString);

        try {
            return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr);
        }
        finally {
            [Runtime.InteropServices.Marshal]::FreeBSTR($bstr);
        }
    }
    
}
#EndRegion '.\Private\Unprotect-SecureString.ps1' 25
#Region '.\Public\Close-TeslaChargePort.ps1' 0
function Close-TeslaChargePort {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    #Todo: Will this work without waking up the vehicle?
    $Fragment = "api/1/vehicles/$Id/command/charge_port_door_close"
    $null = Resume-TeslaVehicle -Id $Id -Wait
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Close-TeslaChargePort.ps1' 25
#Region '.\Public\Connect-Tesla.ps1' 0
function Connect-Tesla {
    [CmdletBinding(DefaultParameterSetName = 'Credential')]
    param (
        # Credentials used to connect to Tesla API
        [Parameter(ParameterSetName = 'Credential', Mandatory)]
        [pscredential]
        $Credential,

        # MFA Code from Authenticator app. Only needed if MFA is enabled on your account.
        [Parameter(ParameterSetName = 'Credential')]
        [string]
        $MFACode,

        # Refreshtoken used to connect to Tesla API
        [Parameter(ParameterSetName = 'RefreshToken', Mandatory)]
        [securestring]
        $RefreshToken,

        # Access token used to connect to Tesla API
        [Parameter(ParameterSetName = 'AccessToken', Mandatory)]
        [securestring]
        $AccessToken,

        # Region to sign in to, can be USA or China (if you are not in China, use USA)
        [Parameter(ParameterSetName = 'RefreshToken')]
        [Parameter(ParameterSetName = 'Credential')]
        [ValidateSet('USA', 'China')]
        [string]
        $Region = 'USA',

        [Parameter(ParameterSetName = 'RefreshToken')]
        [Parameter(ParameterSetName = 'Credential')]
        [switch]
        $PassThru
    )

    $ErrorActionPreference = 'Stop'
    switch ($pscmdlet.ParameterSetName) {
        'Credential' { 
            $Username = $Credential.UserName
            $Password = $Credential.GetNetworkCredential().Password

            $LoginSession = New-LoginSession -Region $Region
            $Code = Get-TeslaAuthCode -Username $Username -Password $Password -MfaCode $MFACode -LoginSession $LoginSession
            $AuthTokens = Get-TeslaAuthToken -Code $Code -LoginSession $LoginSession
            $Token = Get-TeslaAccessToken -AuthToken $AuthTokens.AccessToken
            $Token['AccessToken'] = $Token['AccessToken'] | ConvertTo-SecureString -AsPlainText -Force
            $Token['RefreshToken'] = $Token['RefreshToken'] | ConvertTo-SecureString -AsPlainText -Force
            $Token['IdToken'] = $AuthTokens.IdToken
        }
        'RefreshToken' { 
            throw 'Refresh token support not implemented'
        }
        'AccessToken' {
            $Token = [PSCustomObject]@{
                'AccessToken' = $AccessToken
            }
        }
        Default {
            throw "Unsupported parameter set name: [$($pscmdlet.ParameterSetName)]"
        }
    }

    $Script:TeslaConfiguration['Token'] = $Token
    if ($PassThru.IsPresent) {
        Write-Output $Token
    }

}
#EndRegion '.\Public\Connect-Tesla.ps1' 70
#Region '.\Public\Export-TeslaContext.ps1' 0
function Export-TeslaContext {
    [CmdletBinding()]
    param (
        # Path to where the context should be saved
        [Parameter()]
        [String]
        $Path = "$Env:APPDATA\MyTesla\TeslaContext.json",

        # Store context as plain text
        [Parameter()]
        [switch]
        $AsPlainText,

        # Force file to be overwritten, don't warn about plain text export
        [Parameter()]
        [switch]
        $Force
    )
    
    $ParentPath = Split-Path -Path $Path -Parent
    if (-not (Test-Path -Path $ParentPath -PathType Container)) {
        $null = New-Item -Path $ParentPath -ItemType 'Directory' -ErrorAction Stop
    }

    $ConfigurationToExport = Get-TeslaContext
    if ($ConfigurationToExport.ContainsKey('Token')) {
        $ConfigurationToExport.Token = $ConfigurationToExport.Token.Clone()
    }
    
    if ($null -ne $ConfigurationToExport.Token.AccessToken) {
        if ($AsPlainText.IsPresent) {
            $ConfigurationToExport.Token.AccessToken = Unprotect-SecureString -SecureString $ConfigurationToExport.Token.AccessToken
        }
        else {
            $ConfigurationToExport.Token.AccessToken = $ConfigurationToExport.Token.AccessToken | ConvertFrom-Securestring
        }
    }

    if ($null -ne $ConfigurationToExport.Token.RefreshToken) {
        if ($AsPlainText.IsPresent) {
            $ConfigurationToExport.Token.RefreshToken = Unprotect-SecureString -SecureString $ConfigurationToExport.Token.RefreshToken
        }
        else {
            $ConfigurationToExport.Token.RefreshToken = $ConfigurationToExport.Token.RefreshToken | ConvertFrom-Securestring
        }
    }

    $JsonConfiguration = $ConfigurationToExport | ConvertTo-Json -Compress
    if (-not $AsPlainText.IsPresent) {
        $JsonConfiguration = ConvertTo-SecureString -String $JsonConfiguration -AsPlainText -Force | ConvertFrom-Securestring
    }

    $Params = @{
        Path     = $Path
        Value    = $JsonConfiguration
        Encoding = 'utf8'
    }
    if ($Force.IsPresent) {
        $Params['Force'] = $Force
    }

    Set-Content @Params
}
#EndRegion '.\Public\Export-TeslaContext.ps1' 64
#Region '.\Public\Get-TeslaChargeState.ps1' 0
function Get-TeslaChargeState {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id') -and $Script:TeslaConfiguration.ContainsKey('CurrentVehicleId')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    $Fragment = "api/1/vehicles/$Id/data_request/charge_state"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Get-TeslaChargeState.ps1' 22
#Region '.\Public\Get-TeslaClimateState.ps1' 0
function Get-TeslaClimateState {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id') -and $Script:TeslaConfiguration.ContainsKey('CurrentVehicleId')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/data_request/climate_state"
    $null = Resume-TeslaVehicle -Id $Id -Wait
    Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Get-TeslaClimateState.ps1' 24
#Region '.\Public\Get-TeslaContext.ps1' 0
function Get-TeslaContext {
    [CmdletBinding()]
    param (
        [switch]
        $Force
    )
    
    $Script:TeslaConfiguration.Clone()
    
}
#EndRegion '.\Public\Get-TeslaContext.ps1' 11
#Region '.\Public\Get-TeslaDriveState.ps1' 0
function Get-TeslaDriveState {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/data_request/drive_state"
    $null = Resume-TeslaVehicle -Id $Id -Wait
    Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Get-TeslaDriveState.ps1' 24
#Region '.\Public\Get-TeslaGUISettings.ps1' 0
function Get-TeslaGUISettings {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id') -and $Script:TeslaConfiguration.ContainsKey('CurrentVehicleId')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/data_request/gui_settings"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Get-TeslaGUISettings.ps1' 23
#Region '.\Public\Get-TeslaVehicle.ps1' 0
function Get-TeslaVehicle {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    $Fragment = 'api/1/vehicles'
    if ($PSBoundParameters.ContainsKey('Id')) {
        $Fragment += "/$Id"
    }

    Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Get-TeslaVehicle.ps1' 19
#Region '.\Public\Get-TeslaVehicleConfig.ps1' 0
function Get-TeslaVehicleConfig {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

        
    $Fragment = "api/1/vehicles/$Id/data_request/vehicle_config"
    
    Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Get-TeslaVehicleConfig.ps1' 25
#Region '.\Public\Get-TeslaVehicleData.ps1' 0
function Get-TeslaVehicleData {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

        
    $Fragment = "api/1/vehicles/$Id/vehicle_data"
    $null = Resume-TeslaVehicle -Id $Id -Wait
    Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Get-TeslaVehicleData.ps1' 25
#Region '.\Public\Get-TeslaVehicleState.ps1' 0
function Get-TeslaVehicleState {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/data_request/vehicle_state"
    $null = Resume-TeslaVehicle -Id $Id -Wait
    Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Get-TeslaVehicleState.ps1' 24
#Region '.\Public\Import-TeslaContext.ps1' 0
function Import-TeslaContext {
    [CmdletBinding()]
    param (
        # Path to exported Tesla context
        [Parameter()]
        [string]
        $Path = "$Env:APPDATA\MyTesla\TeslaContext.json"
    )
    
    if (-not (Test-Path -Path $Path -PathType Leaf)) {
        throw "File not found: $Path"
    }

    $ContextContent = Get-Content -Path $Path -Encoding 'utf8'

    try {
        # Try reading the context as plain json
        $TeslaContext = $ContextContent | ConvertFrom-Json -AsHashtable -ErrorAction 'Stop'
        try {
            $TeslaContext.Token.AccessToken = $TeslaContext.Token.AccessToken | ConvertTo-SecureString -AsPlainText -Force -ErrorAction 'stop'
            $TeslaContext.Token.RefreshToken = $TeslaContext.Token.RefreshToken | ConvertTo-SecureString -AsPlainText -Force -ErrorAction 'stop'
        }
        catch {
            # Ignore errors here
        }
    }
    catch {
        # Not valid plain json, try decrypting
        $TeslaContext = $ContextContent | 
        ConvertTo-SecureString -ErrorAction 'Stop' | 
        Unprotect-SecureString -ErrorAction 'Stop' | 
        ConvertFrom-Json -AsHashtable -ErrorAction 'Stop'
        $TeslaContext.Token.AccessToken = $TeslaContext.Token.AccessToken | ConvertTo-SecureString
        $TeslaContext.Token.RefreshToken = $TeslaContext.Token.RefreshToken | ConvertTo-SecureString
    }

    $Script:TeslaConfiguration = $TeslaContext
    $Script:TeslaConfiguration['LastSeen'] = [System.DateTimeOffset]::Now

}
#EndRegion '.\Public\Import-TeslaContext.ps1' 41
#Region '.\Public\Invoke-TeslaAPI.ps1' 0
function Invoke-TeslaAPI {
    [CmdletBinding()]
    param (
        [string]
        $Fragment,

        [string]
        $Method,

        [object]
        $Body,

        [switch]
        $Auth,

        # Calls Resume-TeslaVehicle to wake the car up before involing API. Will not wake up if we called the API within the last minutes.
        [switch]
        $WakeUp,

        # Parameter help description
        [Parameter(DontShow)]
        [string]
        $BaseUri = 'https://owner-api.teslamotors.com'
    )

    $Fragment = $Fragment -replace '^/+|/+$'
    $BaseUri = $BaseUri -replace '^/+|/+$'
    
    if ($WakeUp.IsPresent) {
        $Now = [System.DateTimeOffset]::Now
        $LastSeenSince = ($Now - $Script:TeslaConfiguration['LastSeen']).TotalMinutes
        if ($LastSeenSince -gt 1) {
            $Vehicle = Get-TeslaVehicle
            switch ($Vehicle.state) {
                'asleep' {
                    Write-Verbose -Message 'Waking up car...'
                    $ResumeParams = @{
                        Wait = $true
                    }
                    if ($Fragment -match '^api\/1\/vehicles\/(\d+)') {
                        $ResumeParams['Id'] = $Matches[1]
                    }
                    $null = Resume-TeslaVehicle @ResumeParams
                    Write-Verbose -Message 'Car is woken up'
                    break
                }
                'online' {
                    Write-Verbose -Message 'Car is online'
                    break
                }
                Default {
                    throw "Unknown state: $($Vehicle.state)"
                }
            }
        }
        else {
            Write-Verbose -Message "Car seen $LastSeenSince minutes ago ($($Script:TeslaConfiguration['LastSeen'])), no need to wake up"
        }
    }

    $Params = @{
        Uri     = '{0}/{1}' -f $BaseUri, $Fragment
        Method  = $Method
        Headers = @{
            'Content-Type' = 'application/json'
        }
    }

    if ($PSBoundParameters.ContainsKey('Body')) {
        if ($Body -is [hashtable]) {
            $Params['Body'] = $Body | ConvertTo-Json
        } 
        elseif ($Body -is [string]) {
            $Params['Body'] = $Body
        }
        else {
            throw "Type $($Body.GetType().Name) is not supported as Body parameter."
        }
    }

    if ($Auth.IsPresent) {
        $Token = Get-TeslaToken -Type 'Bearer'
        $Params['Headers']['Authorization'] = $Token
    }

    Invoke-RestMethod @Params -ErrorAction 'Stop'

    $Script:TeslaConfiguration['LastSeen'] = [System.DateTimeOffset]::Now
}
#EndRegion '.\Public\Invoke-TeslaAPI.ps1' 90
#Region '.\Public\Invoke-TeslaHorn.ps1' 0
function Invoke-TeslaHorn {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/honk_horn"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Invoke-TeslaHorn.ps1' 23
#Region '.\Public\Invoke-TeslaLightFlash.ps1' 0
function Invoke-TeslaLightFlash {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/flash_lights"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Invoke-TeslaLightFlash.ps1' 23
#Region '.\Public\Lock-TeslaDoor.ps1' 0
function Lock-TeslaDoor {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/door_lock"
    $null = Resume-TeslaVehicle -Id $Id -Wait
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Lock-TeslaDoor.ps1' 24
#Region '.\Public\Move-TeslaSunroof.ps1' 0
function Move-TeslaSunroof {
    [CmdletBinding(ConfirmImpact = 'Low')]
    param (
        # Id of Tesla Vehicle
        [Parameter(ParameterSetName = '__AllParameterSets')]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        [Parmameter(Mandatory, ParameterSetName = 'State')]
        [ValidateSet('Open', 'Closed', 'Comfort', 'Vent')]
        [string]
        $State,
        
        [Parmameter(Mandatory, ParameterSetName = 'Percent')]
        [ValidateRange(0, 100)]
        [int]
        $Percent

    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    switch ($PSCmdlet.ParameterSetName) {
        'Percent' {
            $Body = @{
                State   = 'move'
                Percent = $Percent
            }
            break
        }
        'State' {
            $Body = @{
                State = $State.ToLower()
            }
            break
        }
        Default {
            throw 'Unknown parameter set'
        }
    }

    $Fragment = "api/1/vehicles/$Id/command/sun_roof_control"
    
    
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Move-TeslaSunroof.ps1' 55
#Region '.\Public\Open-TeslaChargePort.ps1' 0
function Open-TeslaChargePort {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    #Todo: Will this work without waking up the vehicle?
    $Fragment = "api/1/vehicles/$Id/command/charge_port_door_open"
    $null = Resume-TeslaVehicle -Id $Id -Wait
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Open-TeslaChargePort.ps1' 25
#Region '.\Public\Open-TeslaTrunk.ps1' 0
function Open-TeslaTrunk {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        # Select to upen front or rear trunk. Defaults to front (frunk)
        [ValidateSet('front', 'rear')]
        $WhichTrunk = 'front'
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/actuate_trunk"
    $Body = @{
        which_trunk = $WhichTrunk
    }
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Open-TeslaTrunk.ps1' 30
#Region '.\Public\Reset-TeslaValetModePIN.ps1' 0
function Reset-TeslaValetModePIN {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/reset_valet_pin"
    
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Reset-TeslaValetModePIN.ps1' 24
#Region '.\Public\Resume-TeslaVehicle.ps1' 0
# Uncertain of verb choice here.
# Could be any of
# Initialize
# Restore
# Enable
# Resume

function Resume-TeslaVehicle {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        [switch]
        $Wait,

        [Parameter(DontShow)]
        [int]
        $MaxNumberOfTries = 10
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/wake_up"
    
    Write-Verbose -Message "Waking up Tesla with Id: $Id..."
    $Response = Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
    
    if ($Wait.IsPresent) {
        $NumberOfTries = 1
        $SleepTime = 2
        $SleepAddition = 2
        while ($Response.state -ne 'online' -and $NumberOfTries -le $MaxNumberOfTries) {
            Write-Verbose -Message "Waiting for car to go online..."
            Start-Sleep -Seconds $SleepTime
            $SleepAddition++
            $SleepTime += $SleepAddition
            $Response = Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
        }
    }

    Write-Output $Response

}
#EndRegion '.\Public\Resume-TeslaVehicle.ps1' 55
#Region '.\Public\Revoke-TeslaToken.ps1' 0
function Revoke-TeslaToken {
    [CmdletBinding()]
    param (
        # Token to revoke
        [Parameter()]
        [securestring]
        $Token
    )
    
    $Body = @{
        token = Unprotect-SecureString -SecureString $Token
    }

    $Fragment = "oauth/revoke"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Revoke-TeslaToken.ps1' 17
#Region '.\Public\Select-TeslaVehicle.ps1' 0
function Select-TeslaVehicle {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    $Script:TeslaConfiguration['CurrentVehicleId'] = $Id

}
#EndRegion '.\Public\Select-TeslaVehicle.ps1' 15
#Region '.\Public\Set-TeslaChargeLimit.ps1' 0
function Set-TeslaChargeLimit {
    [CmdletBinding(DefaultParameterSetName='Standard')]
    param (
        # Set charge limit to given value in percent
        [Parameter(Mandatory, ParameterSetName='Limit')]
        [ValidateRange(50,100)]
        [Int]
        $LimitPercent,

        # Set charge limit to standard (90%)
        [Parameter(Mandatory, ParameterSetName='Standard')]
        [Switch]
        $Standard,

        # Set charge limit to max (100%)
        [Parameter(Mandatory, ParameterSetName='Max')]
        [Switch]
        $Max,

        # Id of Tesla Vehicle
        [Parameter(ParameterSetName='__AllParameterSets')]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Params = @{}
    switch ($PSCmdlet.ParameterSetName) {
        'Limit' {
            $Params['Fragment'] = "api/1/vehicles/$Id/command/set_charge_limit"
            $Params['Body'] = @{
                percent = $LimitPercent
            }
        }
        'Standard' {
            $Params['Fragment'] = "api/1/vehicles/$Id/command/charge_standard"
        }
        'Max' {
            $Params['Fragment'] = "api/1/vehicles/$Id/command/charge_max_range"
        }

        Default { throw 'This should not have happend.'}
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait

    Invoke-TeslaAPI @Params -Method 'POST' -Auth | Select-Object -ExpandProperty response

}
#EndRegion '.\Public\Set-TeslaChargeLimit.ps1' 59
#Region '.\Public\Set-TeslaDefrost.ps1' 0
function Set-TeslaDefrost {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        [Parameter(Mandatory)]
        [bool]
        $State
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    $Fragment = "api/1/vehicles/$Id/command/set_preconditioning_max"
    $Body = @{
        on = $State
    }
    Invoke-TeslaAPI -Fragment $Fragment -Body $Body -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Set-TeslaDefrost.ps1' 32
#Region '.\Public\Set-TeslaMediaFavourite.ps1' 0
# This verb could be Skip, Switch, Step or Set
function Set-TeslaMediaFavourite {
    [CmdletBinding(DefaultParameterSetName='Next')]
    param (
        # Id of Tesla Vehicle
        [Parameter(ParameterSetName='Next')]
        [Parameter(ParameterSetName='Prev')]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        # Change to next favourite radio channel
        [Parameter(ParameterSetName='Next')]
        [switch]
        $Next,

        # Change to previous favourite radio channel
        [Parameter(ParameterSetName='Prev')]
        [switch]
        $Previous

    )

    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }

    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    switch ($PSCmdlet.ParameterSetName) {
        'Next' {
            $Fragment = "api/1/vehicles/$Id/command/media_next_fav"
            break
        }
        'Prev' {
            $Fragment = "api/1/vehicles/$Id/command/media_prev_fav"
            break
        }
        Default {
            throw 'Unexpected parameter set name.'
        }
    }
    Write-Verbose -Message "Changing to $($PSCmdlet.ParameterSetName) favourite"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Set-TeslaMediaFavourite.ps1' 49
#Region '.\Public\Set-TeslaMediaTrack.ps1' 0
# This verb could be Skip, Switch, Step or Set
function Set-TeslaMediaTrack {
    [CmdletBinding(DefaultParameterSetName='Next')]
    param (
        # Id of Tesla Vehicle
        [Parameter(ParameterSetName='Next')]
        [Parameter(ParameterSetName='Prev')]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        # Play next track
        [Parameter(ParameterSetName='Next')]
        [switch]
        $Next,
        
        # Play Previous track
        [Parameter(ParameterSetName='Prev')]
        [switch]
        $Previous

    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    
    switch ($PSCmdlet.ParameterSetName) {
        'Next' {
            $Fragment = "api/1/vehicles/$Id/command/media_next_track"
            break
        }
        'Prev' {
            $Fragment = "api/1/vehicles/$Id/command/media_prev_track"
            break
        }
        Default {
            throw 'Unexpected parameter set name.'
        }
    }
    Write-Verbose -Message "Changing to $($PSCmdlet.ParameterSetName) track"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Set-TeslaMediaTrack.ps1' 49
#Region '.\Public\Set-TeslaMediaVolume.ps1' 0
function Set-TeslaMediaVolume {
    [CmdletBinding(DefaultParameterSetName='Up')]
    param (
        # Id of Tesla Vehicle
        [Parameter(ParameterSetName='Up')]
        [Parameter(ParameterSetName='Down')]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        # Turn volume up
        [Parameter(ParameterSetName='Up')]
        [switch]
        $Up,
        
        # Turn volume down
        [Parameter(ParameterSetName='Down')]
        [switch]
        $Down

    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    
    switch ($PSCmdlet.ParameterSetName) {
        'Up' {
            $Fragment = "api/1/vehicles/$Id/command/media_volume_up"
            break
        }
        'Down' {
            $Fragment = "api/1/vehicles/$Id/command/media_volume_down"
            break
        }
        Default {
            throw 'Unexpected parameter set name.'
        }
    }
    Write-Verbose -Message "Turning volume $($PSCmdlet.ParameterSetName)"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Set-TeslaMediaVolume.ps1' 48
#Region '.\Public\Set-TeslaNavigation.ps1' 0
function Set-TeslaNavigation {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        # Destination to share
        [string]
        $Destination
    )

    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }

    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    
    [long]$Timestamp = [System.DateTimeOffset]::now - [System.DateTimeOffset]::UnixEpoch |
    Select-Object -ExpandProperty TotalMilliseconds
    
    $Body = @{
        "type"         = "share_ext_content_raw"
        "locale"       = "en-US"
        "value"        = @{
            "android.intent.extra.TEXT" = $Destination
        }
        "timestamp_ms" = $Timestamp
    }
    $Fragment = "/api/1/vehicles/${id}/command/share"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Set-TeslaNavigation.ps1' 37
#Region '.\Public\Set-TeslaSeatHeater.ps1' 0
function Set-TeslaSeatHeater {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        [Parameter(Mandatory)]
        [TeslaSeat]
        $Seat,

        [Parameter(Mandatory)]
        [ValidateRange(0,3)]
        [ValidateSet(0,1,2,3)]
        [int]
        $Level
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    $Fragment = "api/1/vehicles/$Id/command/remote_seat_heater_request"
    $Body = @{
        heater = $Seat.value__
        level = $Level
    }
    Invoke-TeslaAPI -Fragment $Fragment -Body $Body -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Set-TeslaSeatHeater.ps1' 39
#Region '.\Public\Set-TeslaTemperature.ps1' 0
function Set-TeslaTemperature {
    [CmdletBinding(DefaultParameterSetName = 'SetTemp')]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11, 200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        [Parameter(Mandatory, ParameterSetName = 'SetTemp')]
        [ValidateRange(15.5, 27.5)]
        [float]
        $Temperature,
        
        [Parameter(Mandatory, ParameterSetName = 'High')]
        [Alias('Max')]
        [Switch]
        $High,
        
        [Parameter(Mandatory, ParameterSetName = 'Low')]
        [Alias('Min')]
        [Switch]
        $Low
    )
    
    if (-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if ([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    switch ($PScmdlet.ParameterSetName) {
        'SetTemp' { 
            $SetTemperature = '{0:N1}' -f $Temperature
        }
        'High' { 
            $SetTemperature = '{0:N1}' -f 28
        }
        'Low' { 
            $SetTemperature = '{0:N1}' -f 15
        }
        Default {
            throw "Invalid parameter set: $($PScmdlet.ParameterSetName)"
        }
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    $Fragment = "api/1/vehicles/$Id/command/set_temps"
    $Body = @{
        driver_temp = $SetTemperature
        passenger_temp = $SetTemperature
    }
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Set-TeslaTemperature.ps1' 59
#Region '.\Public\Start-TeslaCharging.ps1' 0
function Start-TeslaCharging {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    $Fragment = "api/1/vehicles/$Id/command/charge_start"
    
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Start-TeslaCharging.ps1' 26
#Region '.\Public\Start-TeslaClimate.ps1' 0
function Start-TeslaClimate {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    $Fragment = "api/1/vehicles/$Id/command/auto_conditioning_start"
    
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Start-TeslaClimate.ps1' 26
#Region '.\Public\Start-TeslaDefrost.ps1' 0
function Start-TeslaDefrost {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    Set-TeslaDefrost -Id $Id -State $true
}
#EndRegion '.\Public\Start-TeslaDefrost.ps1' 24
#Region '.\Public\Start-TeslaSentryMode.ps1' 0
function Start-TeslaSentryMode {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    
    $Fragment = "api/1/vehicles/$Id/command/set_sentry_mode"
    $Body = @{
        'on' = $true
    }
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Start-TeslaSentryMode.ps1' 26
#Region '.\Public\Start-TeslaSteeringWheelHeater.ps1' 0
function Stop-TeslaSteeringWheelHeater {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    
    $Fragment = "api/1/vehicles/$Id/command/remote_steering_wheel_heater_request"
    $Body = @{
        'on' = $false
    }
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Start-TeslaSteeringWheelHeater.ps1' 26
#Region '.\Public\Start-TeslaUpdate.ps1' 0
function Start-TeslaUpdate {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        # Number of minutes before the updating starts, use 0 for immediate install
        [Parameter()]
        [int]
        $Minutes = 10
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "/api/1/vehicles/${id}/command/schedule_software_update"
    $Body = @{
        offset_sec = $Minutes * 60
    }
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Start-TeslaUpdate.ps1' 31
#Region '.\Public\Start-TeslaValetMode.ps1' 0
function Start-TeslaValetMode {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        # Optional four digit PIN code used to stop valet mode.
        [Parameter()]
        [ValidateScript({if($_ -match '^\d{4}$'){return $true}else{throw 'Invalid PIN, need to be exact four digits.'}})]
        [string]
        $PIN
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/set_valet_mode"
    
    $Body = @{
        on = $true
    }
    if($PSBoundParameters.ContainsKey('PIN')) {
        $Body['password'] = $PIN
    }

    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp -Body $Body | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Start-TeslaValetMode.ps1' 37
#Region '.\Public\Start-TeslaVehicle.ps1' 0
function Start-TeslaVehicle {
    [CmdletBinding(ConfirmImpact='Low')]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        [Parmameter(Mandatory)]
        [securestring]
        $Password
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/remote_start_drive"
    $Body = @{
        password = Unprotect-SecureString -SecureString $Password
    }
    
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Start-TeslaVehicle.ps1' 31
#Region '.\Public\Stop-TeslaCharging.ps1' 0
function Stop-TeslaCharging {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    $Fragment = "api/1/vehicles/$Id/command/charge_stop"
    
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Stop-TeslaCharging.ps1' 26
#Region '.\Public\Stop-TeslaClimate.ps1' 0
function Stop-TeslaClimate {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    $Fragment = "api/1/vehicles/$Id/command/auto_conditioning_stop"
    
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Stop-TeslaClimate.ps1' 26
#Region '.\Public\Stop-TeslaDefrost.ps1' 0
function Stop-TeslaDefrost {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $null = Resume-TeslaVehicle -Id $Id -Wait
    
    Set-TeslaDefrost -Id $Id -State $false
}
#EndRegion '.\Public\Stop-TeslaDefrost.ps1' 24
#Region '.\Public\Stop-TeslaSentryMode.ps1' 0
function Stop-TeslaSentryMode {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    
    $Fragment = "api/1/vehicles/$Id/command/set_sentry_mode"
    $Body = @{
        'on' = $false
    }
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Stop-TeslaSentryMode.ps1' 26
#Region '.\Public\Stop-TeslaSteeringWheelHeater.ps1' 0
function Start-TeslaSteeringWheelHeater {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    
    $Fragment = "api/1/vehicles/$Id/command/remote_steering_wheel_heater_request"
    $Body = @{
        'on' = $true
    }
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Stop-TeslaSteeringWheelHeater.ps1' 26
#Region '.\Public\Stop-TeslaValetMode.ps1' 0
function Stop-TeslaValetMode {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id,

        # Optional four digit PIN code used to stop valet mode.
        [Parameter()]
        [ValidatePattern('^\d{4}$', ErrorMessage = '{0} is not a valid PIN. Needs to be exact four digits.')]
        [string]
        $PIN
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/set_valet_mode"
    
    $Body = @{
        on = $false
    }
    if($PSBoundParameters.ContainsKey('PIN')) {
        $Body['password'] = $PIN
    }

    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp -Body $Body | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Stop-TeslaValetMode.ps1' 37
#Region '.\Public\Suspend-TeslaUpdate.ps1' 0
function Suspend-TeslaUpdate {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "/api/1/vehicles/${id}/command/cancel_software_update"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Suspend-TeslaUpdate.ps1' 23
#Region '.\Public\Switch-TeslaMediaPlayback.ps1' 0
function Switch-TeslaMediaPlayback {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }
    
    $Fragment = "api/1/vehicles/$Id/command/media_toggle_playback"
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Switch-TeslaMediaPlayback.ps1' 23
#Region '.\Public\Unlock-TeslaDoor.ps1' 0
function Unlock-TeslaDoor {
    [CmdletBinding()]
    param (
        # Id of Tesla Vehicle
        [Parameter()]
        [ValidateLength(11,200)]
        [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')]
        [string]
        $Id
    )
    
    if(-not $PSBoundParameters.ContainsKey('Id')) {
        $Id = $Script:TeslaConfiguration['CurrentVehicleId']
    }
    
    if([string]::IsNullOrWhiteSpace($Id)) {
        throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle'
    }

    $Fragment = "api/1/vehicles/$Id/command/door_unlock"
    $null = Resume-TeslaVehicle -Id $Id -Wait
    Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response
}
#EndRegion '.\Public\Unlock-TeslaDoor.ps1' 24
#Region '.\Public\Wait-TeslaUserPresent.ps1' 0
function Wait-TeslaUserPresent {
    param (
        [Parameter(Mandatory)]
        [int]
        $TimeoutSeconds,

        [int]
        $Interval = 30
    )

    $TotalTime = 0
    while(($TotalTime -lt $TimeoutSeconds) -and (-not (Get-TeslaVehicleState).is_user_present)) {
        Start-Sleep -Seconds $Interval
        $TotalTime += $Interval
    }
}
#EndRegion '.\Public\Wait-TeslaUserPresent.ps1' 17