tesla_api 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitbook.yml +4 -0
- data/.travis.yml +3 -4
- data/LICENSE +1 -1
- data/README.md +7 -3
- data/Rakefile +27 -1
- data/apiary.apib +21 -14
- data/docs/README.md +26 -0
- data/docs/SUMMARY.md +39 -0
- data/docs/api-basics/authentication.md +40 -0
- data/docs/api-basics/vehicles.md +77 -0
- data/docs/vehicle/autopark.md +4 -0
- data/docs/vehicle/commands/README.md +22 -0
- data/docs/vehicle/commands/alerts.md +5 -0
- data/docs/vehicle/commands/calendar.md +3 -0
- data/docs/vehicle/commands/charging.md +3 -0
- data/docs/vehicle/commands/climate.md +3 -0
- data/docs/vehicle/commands/doors.md +3 -0
- data/docs/vehicle/commands/media.md +3 -0
- data/docs/vehicle/commands/navigation.md +3 -0
- data/docs/vehicle/commands/remotestart.md +3 -0
- data/docs/vehicle/commands/softwareupdate.md +3 -0
- data/docs/vehicle/commands/speedlimit.md +3 -0
- data/docs/vehicle/commands/sunroof.md +3 -0
- data/docs/vehicle/commands/trunk.md +3 -0
- data/docs/vehicle/commands/valet.md +3 -0
- data/docs/vehicle/commands/wake.md +3 -0
- data/docs/vehicle/optioncodes.md +253 -0
- data/docs/vehicle/state/README.md +34 -0
- data/docs/vehicle/state/chargestate.md +56 -0
- data/docs/vehicle/state/climatestate.md +43 -0
- data/docs/vehicle/state/data.md +181 -0
- data/docs/vehicle/state/drivestate.md +27 -0
- data/docs/vehicle/state/guisettings.md +21 -0
- data/docs/vehicle/state/mobileenabled.md +14 -0
- data/docs/vehicle/state/vehiclestate.md +47 -0
- data/docs/vehicle/streaming.md +4 -0
- data/lib/tesla_api.rb +7 -6
- data/lib/tesla_api/autopark.rb +62 -0
- data/lib/tesla_api/client.rb +32 -11
- data/lib/tesla_api/stream.rb +11 -7
- data/lib/tesla_api/vehicle.rb +50 -30
- data/lib/tesla_api/version.rb +1 -1
- data/spec/cassettes/client-login.yml +1 -1
- data/spec/cassettes/vehicle-activate_speed_limit.yml +95 -0
- data/spec/cassettes/vehicle-clear_speed_limit_pin.yml +95 -0
- data/spec/cassettes/vehicle-deactivate_speed_limit.yml +95 -0
- data/spec/cassettes/vehicle-open_frunk.yml +40 -73
- data/spec/cassettes/vehicle-open_trunk.yml +40 -73
- data/spec/cassettes/vehicle-set_speed_limit.yml +95 -0
- data/spec/lib/tesla_api/client_spec.rb +42 -15
- data/spec/lib/tesla_api/vehicle_spec.rb +160 -136
- data/spec/spec_helper.rb +6 -18
- data/tesla_api.gemspec +20 -24
- metadata +67 -42
- data/circle.yml +0 -10
@@ -0,0 +1,27 @@
|
|
1
|
+
# Drive State
|
2
|
+
|
3
|
+
## GET `/api/1/vehicles/{id}/data_request/drive_state`
|
4
|
+
|
5
|
+
Returns the driving and position state of the vehicle.
|
6
|
+
|
7
|
+
### Response
|
8
|
+
|
9
|
+
```json
|
10
|
+
{
|
11
|
+
"response": {
|
12
|
+
"shift_state": null,
|
13
|
+
"speed": null,
|
14
|
+
"power": 0,
|
15
|
+
"latitude": 33.111111,
|
16
|
+
"longitude": -88.111111,
|
17
|
+
"heading": 5,
|
18
|
+
"gps_as_of": 1538365363,
|
19
|
+
"native_location_supported": 1,
|
20
|
+
"native_latitude": 33.111111,
|
21
|
+
"native_longitude": -88.111111,
|
22
|
+
"native_type": "wgs",
|
23
|
+
"timestamp": 1538365436098
|
24
|
+
}
|
25
|
+
}
|
26
|
+
```
|
27
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# GUI Settings
|
2
|
+
|
3
|
+
## GET `/api/1/vehicles/{id}/data_request/gui_settings`
|
4
|
+
|
5
|
+
Returns various information about the GUI settings of the car, such as unit format and range display.
|
6
|
+
|
7
|
+
### Response
|
8
|
+
|
9
|
+
```json
|
10
|
+
{
|
11
|
+
"response": {
|
12
|
+
"gui_distance_units": "mi/hr",
|
13
|
+
"gui_temperature_units": "F",
|
14
|
+
"gui_charge_rate_units": "mi/hr",
|
15
|
+
"gui_24_hour_time": false,
|
16
|
+
"gui_range_display": "Rated",
|
17
|
+
"timestamp": 1538365490671
|
18
|
+
}
|
19
|
+
}
|
20
|
+
```
|
21
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Vehicle State
|
2
|
+
|
3
|
+
## GET `/api/1/vehicles/{id}/data_request/vehicle_state`
|
4
|
+
|
5
|
+
Returns the vehicle's physical state, such as which doors are open.
|
6
|
+
|
7
|
+
### Response
|
8
|
+
|
9
|
+
```json
|
10
|
+
{
|
11
|
+
"response": {
|
12
|
+
"api_version": 3,
|
13
|
+
"autopark_state_v2": "standby",
|
14
|
+
"autopark_style": "standard",
|
15
|
+
"calendar_supported": true,
|
16
|
+
"car_version": "2018.34.1 3dd3072",
|
17
|
+
"center_display_state": 0,
|
18
|
+
"df": 0,
|
19
|
+
"dr": 0,
|
20
|
+
"ft": 0,
|
21
|
+
"homelink_nearby": true,
|
22
|
+
"last_autopark_error": "no_error",
|
23
|
+
"locked": true,
|
24
|
+
"notifications_supported": true,
|
25
|
+
"odometer": 33561.422505,
|
26
|
+
"parsed_calendar_supported": true,
|
27
|
+
"pf": 0,
|
28
|
+
"pr": 0,
|
29
|
+
"remote_start": false,
|
30
|
+
"remote_start_supported": true,
|
31
|
+
"rt": 0,
|
32
|
+
"speed_limit_mode": {
|
33
|
+
"active": false,
|
34
|
+
"current_limit_mph": 75.0,
|
35
|
+
"max_limit_mph": 90,
|
36
|
+
"min_limit_mph": 50,
|
37
|
+
"pin_code_set": false
|
38
|
+
},
|
39
|
+
"sun_roof_percent_open": 0,
|
40
|
+
"sun_roof_state": "unknown",
|
41
|
+
"timestamp": 1538365559247,
|
42
|
+
"valet_mode": false,
|
43
|
+
"vehicle_name": "Nikola 2.0"
|
44
|
+
}
|
45
|
+
}
|
46
|
+
```
|
47
|
+
|
data/lib/tesla_api.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'httparty'
|
2
|
+
require 'em-http-request'
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
4
|
+
require 'tesla_api/version'
|
5
|
+
require 'tesla_api/client'
|
6
|
+
require 'tesla_api/stream'
|
7
|
+
require 'tesla_api/autopark'
|
8
|
+
require 'tesla_api/vehicle'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'faye/websocket'
|
2
|
+
|
3
|
+
module TeslaApi
|
4
|
+
module Autopark
|
5
|
+
def start_autopark(&handler)
|
6
|
+
EventMachine.run do
|
7
|
+
socket.on(:message) do |event|
|
8
|
+
message = if event.data.is_a?(Array)
|
9
|
+
JSON.parse(event.data.map(&:chr).join)
|
10
|
+
else
|
11
|
+
JSON.parse(event.data)
|
12
|
+
end
|
13
|
+
|
14
|
+
default_handler(message)
|
15
|
+
handler.call(message.delete('msg_type'), message)
|
16
|
+
end
|
17
|
+
|
18
|
+
socket.on(:close) do |_|
|
19
|
+
@socket = nil
|
20
|
+
@heartbeat && @heartbeat.cancel
|
21
|
+
EventMachine.stop
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def default_handler(message)
|
29
|
+
case message['msg_type']
|
30
|
+
when 'control:hello'
|
31
|
+
interval = message['autopark']['heartbeat_frequency'] / 1000.0
|
32
|
+
@heartbeat = EventMachine::Timer.new(interval) do
|
33
|
+
beat = {
|
34
|
+
msg_type: 'autopark:heartbeat_app',
|
35
|
+
timestamp: Time.now.to_i
|
36
|
+
}
|
37
|
+
socket.send(beat.to_json)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def socket
|
43
|
+
@socket ||= Faye::WebSocket::Client.new(
|
44
|
+
socket_endpoint,
|
45
|
+
nil,
|
46
|
+
{
|
47
|
+
headers: {
|
48
|
+
'Authorization' => "Basic #{socket_auth}"
|
49
|
+
}
|
50
|
+
}
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def socket_auth
|
55
|
+
Base64.strict_encode64("#{email}:#{self['tokens'].first}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def socket_endpoint
|
59
|
+
"wss://streaming.vn.teslamotors.com/connect/#{self['vehicle_id']}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/tesla_api/client.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module TeslaApi
|
2
2
|
class Client
|
3
3
|
include HTTParty
|
4
|
-
base_uri
|
4
|
+
base_uri 'https://owner-api.teslamotors.com/api/1'
|
5
|
+
headers 'User-Agent' => "github.com/timdorr/tesla-api v:#{VERSION}"
|
5
6
|
format :json
|
6
7
|
|
7
8
|
attr_reader :email, :token, :client_id, :client_secret
|
8
9
|
|
9
|
-
def initialize(email, client_id = ENV[
|
10
|
+
def initialize(email, client_id = ENV['TESLA_CLIENT_ID'], client_secret = ENV['TESLA_CLIENT_SECRET'])
|
10
11
|
@email = email
|
11
12
|
@client_id = client_id
|
12
13
|
@client_secret = client_secret
|
@@ -14,26 +15,46 @@ module TeslaApi
|
|
14
15
|
|
15
16
|
def token=(token)
|
16
17
|
@token = token
|
17
|
-
self.class.headers
|
18
|
+
self.class.headers 'Authorization' => "Bearer #{token}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def expires_in=(seconds)
|
22
|
+
@expires_in = seconds.to_f
|
23
|
+
end
|
24
|
+
|
25
|
+
def created_at=(timestamp)
|
26
|
+
@created_at = Time.at(timestamp.to_f).to_datetime
|
27
|
+
end
|
28
|
+
|
29
|
+
def expired_at
|
30
|
+
return nil unless defined?(@created_at)
|
31
|
+
(@created_at.to_time + @expires_in.to_f).to_datetime
|
32
|
+
end
|
33
|
+
|
34
|
+
def expired?
|
35
|
+
return true unless defined?(@created_at)
|
36
|
+
expired_at <= DateTime.now
|
18
37
|
end
|
19
38
|
|
20
39
|
def login!(password)
|
21
40
|
response = self.class.post(
|
22
|
-
|
41
|
+
'https://owner-api.teslamotors.com/oauth/token',
|
23
42
|
body: {
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
43
|
+
grant_type: 'password',
|
44
|
+
client_id: client_id,
|
45
|
+
client_secret: client_secret,
|
46
|
+
email: email,
|
47
|
+
password: password
|
29
48
|
}
|
30
49
|
)
|
31
50
|
|
32
|
-
self.
|
51
|
+
self.expires_in = response['expires_in']
|
52
|
+
self.created_at = response['created_at']
|
53
|
+
self.token = response['access_token']
|
33
54
|
end
|
34
55
|
|
35
56
|
def vehicles
|
36
|
-
self.class.get(
|
57
|
+
self.class.get('/vehicles')['response'].map { |v| Vehicle.new(self.class, v['id'], v) }
|
37
58
|
end
|
38
59
|
end
|
39
60
|
end
|
data/lib/tesla_api/stream.rb
CHANGED
@@ -6,7 +6,7 @@ module TeslaApi
|
|
6
6
|
attributes = chunk.split(",")
|
7
7
|
|
8
8
|
reciever.call({
|
9
|
-
time: DateTime.strptime((attributes[0].to_i/1000).to_s,
|
9
|
+
time: DateTime.strptime((attributes[0].to_i/1000).to_s, '%s'),
|
10
10
|
speed: attributes[1].to_f,
|
11
11
|
odometer: attributes[2].to_f,
|
12
12
|
soc: attributes[3].to_f,
|
@@ -14,7 +14,11 @@ module TeslaApi
|
|
14
14
|
est_heading: attributes[5].to_f,
|
15
15
|
est_lat: attributes[6].to_f,
|
16
16
|
est_lng: attributes[7].to_f,
|
17
|
-
power: attributes[8].to_f
|
17
|
+
power: attributes[8].to_f,
|
18
|
+
shift_state: attributes[9].to_s,
|
19
|
+
range: attributes[10].to_f,
|
20
|
+
est_range: attributes[11].to_f,
|
21
|
+
heading: attributes[12].to_f
|
18
22
|
})
|
19
23
|
end
|
20
24
|
|
@@ -27,23 +31,23 @@ module TeslaApi
|
|
27
31
|
|
28
32
|
def request
|
29
33
|
@request ||= EventMachine::HttpRequest.new(
|
30
|
-
"#{stream_endpoint}/stream/#{self[
|
34
|
+
"#{stream_endpoint}/stream/#{self['vehicle_id']}/?values=#{stream_params}")
|
31
35
|
end
|
32
36
|
|
33
37
|
def http
|
34
|
-
|
38
|
+
request.get(
|
35
39
|
head: {
|
36
|
-
|
40
|
+
'authorization' => [email, self['tokens'].first]
|
37
41
|
},
|
38
42
|
inactivity_timeout: 15)
|
39
43
|
end
|
40
44
|
|
41
45
|
def stream_endpoint
|
42
|
-
|
46
|
+
'https://streaming.vn.teslamotors.com'
|
43
47
|
end
|
44
48
|
|
45
49
|
def stream_params
|
46
|
-
|
50
|
+
'speed,odometer,soc,elevation,est_heading,est_lat,est_lng,power,shift_state,range,est_range,heading'
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
data/lib/tesla_api/vehicle.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module TeslaApi
|
2
2
|
class Vehicle
|
3
3
|
include Stream
|
4
|
-
|
4
|
+
include Autopark
|
5
|
+
attr_reader :api, :id, :vehicle
|
5
6
|
|
6
|
-
def initialize(api,
|
7
|
+
def initialize(api, id, vehicle)
|
7
8
|
@api = api
|
8
|
-
@email = email
|
9
9
|
@id = id
|
10
10
|
@vehicle = vehicle
|
11
11
|
end
|
@@ -26,114 +26,134 @@ module TeslaApi
|
|
26
26
|
|
27
27
|
# State
|
28
28
|
|
29
|
+
def data
|
30
|
+
api.get("/vehicles/#{id}/data")['response']
|
31
|
+
end
|
32
|
+
|
29
33
|
def mobile_enabled
|
30
|
-
api.get("/vehicles/#{id}/mobile_enabled")[
|
34
|
+
api.get("/vehicles/#{id}/mobile_enabled")['response']
|
31
35
|
end
|
32
36
|
|
33
37
|
def gui_settings
|
34
|
-
data_request(
|
38
|
+
data_request('gui_settings')['response']
|
35
39
|
end
|
36
40
|
|
37
41
|
def charge_state
|
38
|
-
data_request(
|
42
|
+
data_request('charge_state')['response']
|
39
43
|
end
|
40
44
|
|
41
45
|
def climate_state
|
42
|
-
data_request(
|
46
|
+
data_request('climate_state')['response']
|
43
47
|
end
|
44
48
|
|
45
49
|
def drive_state
|
46
|
-
data_request(
|
50
|
+
data_request('drive_state')['response']
|
47
51
|
end
|
48
52
|
|
49
53
|
def vehicle_state
|
50
|
-
data_request(
|
54
|
+
data_request('vehicle_state')['response']
|
51
55
|
end
|
52
56
|
|
53
57
|
# Commands
|
54
58
|
|
55
59
|
def wake_up
|
56
|
-
@vehicle = api.post("/vehicles/#{id}/wake_up")[
|
60
|
+
@vehicle = api.post("/vehicles/#{id}/wake_up")['response']
|
57
61
|
end
|
58
62
|
|
59
63
|
def set_valet_mode(on, password=nil)
|
60
|
-
command(
|
64
|
+
command('set_valet_mode', body: {on: on, password: password})['response']
|
61
65
|
end
|
62
66
|
|
63
67
|
def reset_valet_pin
|
64
|
-
command(
|
68
|
+
command('reset_valet_pin')['response']
|
65
69
|
end
|
66
70
|
|
67
71
|
def charge_port_door_open
|
68
|
-
command(
|
72
|
+
command('charge_port_door_open')['response']
|
69
73
|
end
|
70
74
|
|
71
75
|
def charge_standard
|
72
|
-
command(
|
76
|
+
command('charge_standard')['response']
|
73
77
|
end
|
74
78
|
|
75
79
|
def charge_max_range
|
76
|
-
command(
|
80
|
+
command('charge_max_range')['response']
|
77
81
|
end
|
78
82
|
|
79
83
|
def set_charge_limit(percent)
|
80
|
-
command(
|
84
|
+
command('set_charge_limit', body: {percent: percent})['response']
|
81
85
|
end
|
82
86
|
|
83
87
|
def charge_start
|
84
|
-
command(
|
88
|
+
command('charge_start')['response']
|
85
89
|
end
|
86
90
|
|
87
91
|
def charge_stop
|
88
|
-
command(
|
92
|
+
command('charge_stop')['response']
|
89
93
|
end
|
90
94
|
|
91
95
|
def flash_lights
|
92
|
-
command(
|
96
|
+
command('flash_lights')['response']
|
93
97
|
end
|
94
98
|
|
95
99
|
def honk_horn
|
96
|
-
command(
|
100
|
+
command('honk_horn')['response']
|
97
101
|
end
|
98
102
|
|
99
103
|
def door_unlock
|
100
|
-
command(
|
104
|
+
command('door_unlock')['response']
|
101
105
|
end
|
102
106
|
|
103
107
|
def door_lock
|
104
|
-
command(
|
108
|
+
command('door_lock')['response']
|
105
109
|
end
|
106
110
|
|
107
111
|
def set_temps(driver_temp, passenger_temp)
|
108
|
-
command(
|
112
|
+
command('set_temps', body: {driver_temp: driver_temp, passenger_temp: passenger_temp})['response']
|
109
113
|
end
|
110
114
|
|
111
115
|
def auto_conditioning_start
|
112
|
-
command(
|
116
|
+
command('auto_conditioning_start')['response']
|
113
117
|
end
|
114
118
|
|
115
119
|
def auto_conditioning_stop
|
116
|
-
command(
|
120
|
+
command('auto_conditioning_stop')['response']
|
117
121
|
end
|
118
122
|
|
119
123
|
def sun_roof_control(state)
|
120
|
-
command(
|
124
|
+
command('sun_roof_control', body: {state: state})['response']
|
121
125
|
end
|
122
126
|
|
123
127
|
def sun_roof_move(percent)
|
124
|
-
command(
|
128
|
+
command('sun_roof_control', body: {state: 'move', percent: percent})['response']
|
125
129
|
end
|
126
130
|
|
127
131
|
def remote_start_drive(password)
|
128
|
-
command(
|
132
|
+
command('remote_start_drive', body: {password: password})['response']
|
129
133
|
end
|
130
134
|
|
131
135
|
def open_trunk
|
132
|
-
command(
|
136
|
+
command('actuate_trunk', body: {which_trunk: 'rear'})['response']
|
133
137
|
end
|
134
138
|
|
135
139
|
def open_frunk
|
136
|
-
command(
|
140
|
+
command('actuate_trunk', body: {which_trunk: 'front'})['response']
|
141
|
+
end
|
142
|
+
|
143
|
+
def activate_speed_limit(pin)
|
144
|
+
command('speed_limit_activate', body: {pin: pin})['response']
|
145
|
+
end
|
146
|
+
|
147
|
+
def deactivate_speed_limit(pin)
|
148
|
+
command('speed_limit_deactivate', body: {pin: pin})['response']
|
149
|
+
end
|
150
|
+
|
151
|
+
def set_speed_limit(limit_mph)
|
152
|
+
command('speed_limit_set_limit', body: {limit_mph: limit_mph})['response']
|
153
|
+
end
|
154
|
+
|
155
|
+
def clear_speed_limit_pin(pin)
|
156
|
+
command('speed_limit_clear_pin', body: {pin: pin})['response']
|
137
157
|
end
|
138
158
|
|
139
159
|
private
|