Tesla.psm1
# credit to HJespers for https://github.com/hjespers/teslams #region Globals $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 [string]$TeslaPSModule_Uri = 'https://owner-api.teslamotors.com' [string]$apiUri = "$TeslaPSModule_Uri/api/1" [string]$TeslaPSModule_VehicleId = $null [Hashtable]$headers = $null [string]$activity = 'TeslaPSModule' # emulate the android mobile app $version = '2.1.79'; $model = 'SM-G900V'; $codename = 'REL'; $release = '4.4.4'; $locale = 'en_US'; [string]$user_agent = "Model S $version ($model; Android $codename $release; $locale)" #endregion Globals #region Utility function Status { [CmdletBinding()] param( [string][parameter(Position=0,Mandatory=$true)]$Status ) Write-Verbose -Message "$activity`: $Status" Write-Progress -Activity $activity -Status $Status } function CtoF([double][parameter(Mandatory=$true)]$celsius) { return [Math]::Round($celsius * (9.0 / 5.0) + 32) } #endregion Utility #region Invoke function InvokeCarCommand { [CmdletBinding()] param( [string][parameter(Mandatory=$true)]$command ) GetConnection Status "Sending $command to vehicle..." $uri = "$apiUri/vehicles/$TeslaPSModule_VehicleId/command/$Command" Write-Verbose -Message $uri $resp = Invoke-RestMethod -Uri $uri ` -Method Post ` -Headers $headers ` -UserAgent $user_agent ` -ContentType 'application/json' Write-Debug -Message $resp Write-Debug -Message $resp.response if ($resp.response.result -ne "true") { throw "Error calling $command. Reason returned: ""$($resp.response.reason)""" } $script:m_delayTime = 5 } function InvokeTeslaDataRequest { [CmdletBinding()] param( [string][parameter(Mandatory=$true)]$Command ) if (-not $activity) { $activity = "$($MyInvocation.InvocationName): $Command" } GetConnection $uri = "$apiUri/vehicles/$TeslaPSModule_VehicleId/data_request/$Command" Write-Verbose -Message "Sending command $uri" Status "Invoking $Command" $resp = Invoke-RestMethod -Uri $uri ` -Method Get ` -Headers $headers ` -UserAgent $user_agent ` -ContentType 'application/json' Write-Debug -Message $resp Write-Debug -Message $resp.response Write-Output -InputObject $resp.response # CODEWORK still need to implement this $script:m_delayTime = 5 } #endregion Invoke #region Connect function Connect-Tesla { <# .SYNOPSIS Connect to a vehicle .DESCRIPTION Connect to one Tesla vehicle. You must specify the credentials you use to connect with the Tesla website, email address and password. The credentials will be cached securely, so it should only be necessary to call this once for any computer+user. However you may need to invoke this again if you change either your Tesla password or your Windows password. .PARAMETER Credential Specify the credentials you use to connect with the Tesla website, email address and password. .PARAMETER VehicleIndex Specify this if you have more than one vehicle and want to connect with a different vehicle than the first. .PARAMETER VIN Specify this if you have more than one vehicle and want to specify the VIN of the specific vehicle. .PARAMETER NoPersist This will prevent your credentials from being cached. They will only be effective for this PowerShell session. .LINK Get-Tesla Set-Tesla #> [CmdletBinding(DefaultParameterSetName='VehicleIndex')] param( [PSCredential][parameter(Mandatory=$true,Position=0)]$Credential, [int][parameter(ParameterSetName='VehicleIndex')]$VehicleIndex = 0, [string][parameter(ParameterSetName='VIN')]$VIN = '', [switch]$NoPersist ) $activity = $MyInvocation.InvocationName $passwordBstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.Password) $plaintextpwd = [Runtime.InteropServices.Marshal]::PtrToStringAuto($passwordBstr) [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($passwordBstr); $encr = '76492d1116743f0423413b16050a5345MgB8AFcATgBuAFcASABCAE8AWQA5AHUAbwArAFoASQBBAEwAOQBLAGwATgAwAHcAPQA9AHwAMQA5ADEAOAA0 AGEAMwBmAGEAMgAzAGUAMAAwAGQAMwAwAGEAMQBhADAAMwBhAGIAYwBlAGUAYwAzADAAMwAxADQANgBjADYAMgAxADkAMwBiADcAOAA3ADQAZQBkADQAMwA5ADkAZQA3A GUAZQA4AGEAZQAwADIAYQAwADEAYgA5ADkAMwAyAGYAZAA4ADgAMQAxADMAYQA0ADEAOAA3ADAAZABjADIAOQA3AGUAMwA5ADUAMwA4ADIANAA0ADIANwAwAGEAYgBhAG YAMgA2ADIAZQBjADUANgA1AGYAMAAwADMANwBmADgAOQAwAGEAMABhADAANABlAGYAOAA2ADIAZgA1ADEANwAyADIAZAAxAGEAYQBjADcAYQBiADUAYQBhADUAMgBlADM AMQA4ADkAYgA4ADMAZgAzADYAZgBlAGUANwA4ADUANQA5ADIAMwBlADIAYwAyADQAMQAxAGYANwBhAGIAZQAwADgAZAA0AGUAZgAzAGYAYQAxADQAMQA5ADYAMQAwADcA OABiADEAZAA5AGUANwAyADcAZgA0ADkAMABkAGUANwA0AGIANwBhADMAMAA2ADUAOQBiADQAOAA5AGYAOABiAGEAZABiADkAZgBhAGYAZQA0ADQAMQA1AGYANwBkAGQAN gBlAGMAYQA5ADUANAA3ADEAMwAxADQAYwAxADQAMgA0AGMAOABjADAAZABiADgANwA4ADUAMABmADkANgBiADIAYQA5AGIAOQA3ADkAZQBmADQAMABiADIAYQAxAGEAYQ BlADEAZgBhAGMANgAzAGEAMgA0ADkAYgA3AGMAMgBlAGIAZgA3ADAAZQA2AGUAOABiADgAZAAxADAAOAA4AGQAMgA4ADEAMwAxAGIAMwAyAGEAMwBjADMAOQAyAGIAMgA wADYAYwBkADkANgBkADUAYQA3AGUAMgBiAGIANAAwADUANAA3ADQAZAA0AGEAYwA0ADEANwBlADYAOABkADgANgA0AGYANQBhADIAZAA5AGYAZQAzAGEANQBmADAAZQA3 ADQAZgA4AGEAZQA4AGUANwAzAGYAYwAyAGQAMgA0AGUAZgBkAGMAMQBhADkANAA1ADQAMwAyAGEAYQBkAGEAYgAwADAANgAxADAAMwBkADkAYwBjAGUAZAA2ADkAZQAzA DcAMwBiAGMAMQA0AGIAMAA3ADcAMgBiAGIAOQA2AGQANQBlADcANwBiADYAYQA2ADMAMAA1ADQANQAyADcANQA3ADEAMABlADUANQAzAGMAZgBjADUAZAA1ADQAMQA5AG IANQA0ADIAYgA4ADQANwAxADgAZQA1AGMAMwBmAGMAMQAzADIAMAAwADQAYgBkADAAMgA0AGQANgA0AGMAOABmAGYAMgAyAGMAOAAzADcANgA3AGIAMgBiAGYAYwBjADQ ANQA5AGQAYgA2AGQAYgA=' [byte[]]$k = ('236 231 222 136 19 9 157 113 158 51 236 240 116 17 176 100 91 179 20 162 238 103 10 192 113 251 135 59 95 82 109 114'.Split(' ')) -as [byte[]] $ps = ConvertTo-SecureString -String $encr -Key $k $b = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ps) $p = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($b) $loginBody = @{ 'grant_type' = 'password' 'client_id' = ($p.Split(',')[0]) 'client_secret' = ($p.Split(',')[1]) 'email' = $Credential.UserName 'password' = $plaintextpwd } Status "Login GET" $r = Invoke-RestMethod -Uri "$TeslaPSModule_Uri/oauth/token" ` -Method Post ` -Body $loginBody $r | Out-String | Write-Debug $access_token = $r.access_token $script:headers = @{ 'Authorization' = "Bearer $access_token" 'Accept-Encoding' = 'gzip,deflate' } Status "Get vehicle list" $vehicles = @(Invoke-RestMethod -Uri "$apiUri/vehicles" ` -Method Get ` -UserAgent $user_agent ` -Headers $headers) if ($VIN) { for ($i = 0; $i -lt $vehicles.Count; $i++) { $vehicle = $vehicles[$i].response if ($vehicle.VIN -eq $VIN) { $VehicleIndex = $i; break } } if ($i -ge $vehicles.Count) { throw "$activity`: Vehicle with VIN $VIN not found" } } $vehicle = $vehicles[$VehicleIndex].response $vehicleId = $vehicle.id if ($vehicle.state -ne 'online') { if ($vehicle.state -ne 'asleep') { throw "$activity`: Current vehicle state is $($vehicle.state). Please try again later." } # The wake_up command will return error 408 (vehicle unavailable). try { Status "Waking vehicle" InvokeCarCommand wake_up } catch { Write-Debug -Message "$activity`: Exception $_" # Do nothing } # Check vehicle state periodically and continue when it's "online" do { Start-Sleep -Seconds 5 Status "Checking whether vehicle woke up yet" $vehicle = Invoke-RestMethod -Uri "$apiUri/vehicles" ` -Method Get ` -UserAgent $user_agent ` -Headers $headers $vehicle = $vehicle | Where-Object id -eq $vehicleId Write-Verbose -Message "Vehicle state is $($vehicle.state)." } while ($vehicle.state -ne 'online') } Status "Caching connection" $script:TeslaPSModule_VehicleId = $vehicleId if (-not $NoPersist) { $fileName = Join-Path -Path $env:APPDATA -ChildPath 'TeslaPSModule_CachedConnection.xml' Status "Persisting cached connection to $fileName" $connection = New-Object -TypeName PSObject -Property @{ Email = $Credential.UserName Password = $plaintextpwd VIN = $vehicle.VIN } $xmlContent = ConvertTo-Xml -InputObject $connection -As String Write-Verbose -Message "$activity`: Writing connection to file $fileName" $xmlContent = ConvertTo-SecureString -String $xmlContent -AsPlainText -Force | ConvertFrom-SecureString Set-Content -Path $fileName -Value $xmlContent -ErrorAction Stop } Write-Progress -Activity $activity -Status 'Completed' -Completed } function GetConnection { [CmdletBinding()] param( ) if ($headers -and $TeslaPSModule_VehicleId) { Write-Verbose -Message "Connection already cached" return } $path = Join-Path -Path $env:APPDATA -ChildPath 'TeslaPSModule_CachedConnection.xml' if (-not (Test-Path -Path $path -ErrorAction SilentlyContinue)) { Status "You must first call Connect-Tesla" throw "You must first call Connect-Tesla" } Status "Reading cached connection from $path" try { $fileContent = Get-Content -Path $path -ErrorAction Stop $secureString = ConvertTo-SecureString -String $fileContent -ErrorAction SilentlyContinue $fileContentDecrypted = $secureString ` | ForEach-Object {[Runtime.InteropServices.Marshal]::PtrToStringAuto( ` [Runtime.InteropServices.Marshal]::SecureStringToBSTR($_))} $xmlContent = [xml]$fileContentDecrypted $email = ($xmlContent.Objects.Object.Property | Where-Object Name -eq "Email").'#text' $password = ($xmlContent.Objects.Object.Property | Where-Object Name -eq "Password").'#text' $VIN = ($xmlContent.Objects.Object.Property | Where-Object Name -eq "VIN").'#text' $securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force $credential = New-Object -TypeName PSCredential -ArgumentList $email,$securePassword } catch { throw "Error reading cached connection; retry Connect-Tesla. Error is: $_" } Connect-Tesla -Credential $credential -VIN $VIN -NoPersist } #endregion Connect function Get-Tesla { <# .SYNOPSIS Retrieve information about a Tesla vehicle .DESCRIPTION Retrieve information about a Tesla vehicle in a specific category. You must first call Connect-Tesla once to cache connection information for this computer+user. Connection information will be encrypted and cached in your user profile. .PARAMETER Command Specify the category of information you want to retrieve. .LINK Connect-Tesla Set-Tesla #> [CmdletBinding()] param( [parameter(Mandatory=$true,Position=0)] [ValidateSet('charge_state', 'climate_state', 'drive_state', 'gui_settings', 'vehicle_state', 'vehicles' )] [string]$Command ) $activity = "$($MyInvocation.InvocationName): $Command" GetConnection if ($Command -eq 'vehicles') { Status "Reading vehicle list" $result = Invoke-RestMethod -Uri "$apiUri/vehicles" ` -Method Get ` -UserAgent $user_agent ` -Headers $headers return $result.response } InvokeTeslaDataRequest -Command $Command Write-Progress -Activity $activity -Status 'Completed' -Completed } function Set-Tesla { <# .SYNOPSIS Change one setting of a Tesla vehicle .DESCRIPTION Change one setting of a Tesla vehicle. You must first call Connect-Tesla once to cache connection information for this computer+user. Connection information will be encrypted and cached in your user profile. .PARAMETER Command Specify the command you want to issue. .NOTES Not yet implemented: set_charge_limit <percent> set_temps <driver_temp> <passenger_temp> sun_roof_control (open | close | comfort | vent | move <percent>) sun_roof_control move <percent> streaming response from https://streaming.vn.teslamotors.com/stream/... .LINK Connect-Tesla Get-Tesla #> [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Low')] param( [parameter(Mandatory=$true,Position=0)] [ValidateSet('auto_conditioning_start', 'auto_conditioning_stop', 'door_lock', 'door_unlock', 'charge_port_door_open', 'charge_max_range', 'charge_standard', 'charge_start', 'charge_stop', 'flash_lights', 'honk_horn', 'wake_up' )] [string]$Command ) $activity = "$($MyInvocation.InvocationName): $Command" if ($PSCmdlet.ShouldProcess($Command)) { InvokeCarCommand $Command } Write-Progress -Activity $activity -Status 'Completed' -Completed } Export-ModuleMember Connect-Tesla,Get-Tesla,Set-Tesla |