tesla_api 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.travis.yml +11 -0
- data/{LICENSE.txt → LICENSE} +0 -0
- data/README.md +9 -4
- data/Rakefile +6 -0
- data/lib/tesla_api.rb +5 -171
- data/lib/tesla_api/client.rb +28 -0
- data/lib/tesla_api/vehicle.rb +145 -0
- data/lib/tesla_api/version.rb +1 -1
- data/spec/cassettes/client-initialize.yml +48 -0
- data/spec/cassettes/client-vehicles.yml +89 -0
- data/spec/lib/tesla_api/client_spec.rb +24 -0
- data/spec/spec_helper.rb +33 -0
- data/tesla_api.gemspec +7 -0
- metadata +100 -7
- data/logs/login.GET.log +0 -44
- data/logs/login.POST.log +0 -29
- data/logs/root.GET.log +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45741a5c5b2afc5cd2e0eb6b2ecafa1115847868
|
4
|
+
data.tar.gz: 79c210bf38a56293ff3fc7261780eb84fcd87684
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aec12640955ab16d5a77611e4bb66d34ff3084b362caf572372d59c8a2a7b484d8a46224d0a4f2dff0360d912b1e49f5eaf6c99a7c31ac4e4204814e10b207a7
|
7
|
+
data.tar.gz: 87ae5a2fac0c7ade54796a83d0c0eb6b87a3eddc20415f2c67c2c3959dda4e08ae38abe64e4052be0f3f4d5ee9656659a4dbc63aad79f8a5744880c65232444e
|
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
[View Documentation](http://docs.timdorr.apiary.io/)
|
4
4
|
|
5
|
-
This is unofficial documentation of the Tesla
|
5
|
+
This is unofficial documentation of the Tesla JSON API used by the iOS and Android apps.
|
6
6
|
The API provides functionality to monitor and control the Model S (and future Tesla vehicles) remotely.
|
7
7
|
The project provides both a documention of the API and a Ruby library to for accessing it.
|
8
8
|
|
9
|
-
## Ruby
|
9
|
+
## Ruby Gem [](http://rubygems.org/gems/tesla_api) [](https://travis-ci.org/timdorr/model-s-api)
|
10
10
|
|
11
11
|
This gem provides a basic wrapper around the API to easily query and command the car remotely.
|
12
12
|
It also provides access to the streaming API and a means to process data coming from it.
|
@@ -30,8 +30,8 @@ Here's a quick example:
|
|
30
30
|
```ruby
|
31
31
|
require 'tesla_api'
|
32
32
|
|
33
|
-
tesla_api = TeslaApi.new(email, password, client_id, client_secret)
|
34
|
-
model_s = tesla_api.vehicles.first
|
33
|
+
tesla_api = TeslaApi::Client.new(email, password, client_id, client_secret)
|
34
|
+
model_s = tesla_api.vehicles.first # => <TeslaApi::Vehicle>
|
35
35
|
|
36
36
|
model_s.wake_up
|
37
37
|
model_s.auto_conditioning_start unless model_s.climate_state["is_auto_conditioning_on"]
|
@@ -44,3 +44,8 @@ puts "Your Model S is #{charge_state["charging_state"]} " +
|
|
44
44
|
"with a SOC of #{charge_state["battery_level"]}% " +
|
45
45
|
"and an estimate range of #{charge_state["est_battery_range"]} miles"
|
46
46
|
```
|
47
|
+
|
48
|
+
## Copyright
|
49
|
+
|
50
|
+
Ruby portions are Copyright (c) 2014 Tim Dorr. Released under the terms of the
|
51
|
+
MIT license. See LICENSE for details.
|
data/Rakefile
CHANGED
data/lib/tesla_api.rb
CHANGED
@@ -1,172 +1,6 @@
|
|
1
|
-
require "
|
2
|
-
|
3
|
-
class TeslaApi
|
4
|
-
include HTTParty
|
5
|
-
base_uri "https://owner-api.teslamotors.com/api/1"
|
6
|
-
format :json
|
7
|
-
|
8
|
-
attr_reader :email
|
9
|
-
|
10
|
-
def initialize(email, password, client_id, client_secret)
|
11
|
-
@email = email
|
12
|
-
response = self.class.post(
|
13
|
-
"https://owner-api.teslamotors.com/oauth/token",
|
14
|
-
body: {
|
15
|
-
"grant_type" => "password",
|
16
|
-
"client_id" => client_id,
|
17
|
-
"client_secret" => client_secret,
|
18
|
-
"email" => email,
|
19
|
-
"password" => password
|
20
|
-
}
|
21
|
-
)
|
22
|
-
self.class.headers "Authorization" => "Bearer #{response["access_token"]}"
|
23
|
-
end
|
24
|
-
|
25
|
-
def vehicles
|
26
|
-
self.class.get("/vehicles")["response"].map { |v| Vehicle.new(self.class, email, v["id"], v) }
|
27
|
-
end
|
28
|
-
|
29
|
-
class Vehicle
|
30
|
-
attr_reader :api, :email, :id, :vehicle
|
31
|
-
|
32
|
-
def initialize(api, email, id, vehicle)
|
33
|
-
@api = api
|
34
|
-
@email = email
|
35
|
-
@id = id
|
36
|
-
@vehicle = vehicle
|
37
|
-
end
|
38
|
-
|
39
|
-
def [](key)
|
40
|
-
vehicle[key]
|
41
|
-
end
|
42
|
-
|
43
|
-
# State
|
44
|
-
|
45
|
-
def mobile_enabled
|
46
|
-
api.get("/vehicles/#{id}/mobile_enabled")["response"]
|
47
|
-
end
|
48
|
-
|
49
|
-
def charge_state
|
50
|
-
api.get("/vehicles/#{id}/data_request/charge_state")["response"]
|
51
|
-
end
|
52
|
-
|
53
|
-
def climate_state
|
54
|
-
api.get("/vehicles/#{id}/data_request/climate_state")["response"]
|
55
|
-
end
|
56
|
-
|
57
|
-
def drive_state
|
58
|
-
api.get("/vehicles/#{id}/data_request/drive_state")["response"]
|
59
|
-
end
|
60
|
-
|
61
|
-
def gui_settings
|
62
|
-
api.get("/vehicles/#{id}/data_request/gui_settings")["response"]
|
63
|
-
end
|
64
|
-
|
65
|
-
def vehicle_state
|
66
|
-
api.get("/vehicles/#{id}/data_request/vehicle_state")["response"]
|
67
|
-
end
|
68
|
-
|
69
|
-
# Commands
|
70
|
-
|
71
|
-
def wake_up
|
72
|
-
api.post("/vehicles/#{id}/wake_up")
|
73
|
-
end
|
74
|
-
|
75
|
-
def charge_port_door_open
|
76
|
-
api.post("/vehicles/#{id}/command/charge_port_door_open")
|
77
|
-
end
|
78
|
-
|
79
|
-
def charge_standard
|
80
|
-
api.post("/vehicles/#{id}/command/charge_standard")
|
81
|
-
end
|
1
|
+
require "httparty"
|
2
|
+
require "em-http-request"
|
82
3
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
def set_charge_limit(percent)
|
88
|
-
api.post("/vehicles/#{id}/command/set_charge_limit", query: {state: "set", percent: percent})
|
89
|
-
end
|
90
|
-
|
91
|
-
def charge_start
|
92
|
-
api.post("/vehicles/#{id}/command/charge_start")
|
93
|
-
end
|
94
|
-
|
95
|
-
def charge_stop
|
96
|
-
api.post("/vehicles/#{id}/command/charge_stop")
|
97
|
-
end
|
98
|
-
|
99
|
-
def flash_lights
|
100
|
-
api.post("/vehicles/#{id}/command/flash_lights")
|
101
|
-
end
|
102
|
-
|
103
|
-
def honk_horn
|
104
|
-
api.post("/vehicles/#{id}/command/honk_horn")
|
105
|
-
end
|
106
|
-
|
107
|
-
def door_unlock
|
108
|
-
api.post("/vehicles/#{id}/command/door_unlock")
|
109
|
-
end
|
110
|
-
|
111
|
-
def door_lock
|
112
|
-
api.post("/vehicles/#{id}/command/door_lock")
|
113
|
-
end
|
114
|
-
|
115
|
-
def set_temps(driver_temp, passenger_temp)
|
116
|
-
api.post("/vehicles/#{id}/command/set_temps", query: {driver_temp: driver_temp, passenger_temp: passenger_temp})
|
117
|
-
end
|
118
|
-
|
119
|
-
def auto_conditioning_start
|
120
|
-
api.post("/vehicles/#{id}/command/auto_conditioning_start")
|
121
|
-
end
|
122
|
-
|
123
|
-
def auto_conditioning_stop
|
124
|
-
api.post("/vehicles/#{id}/command/auto_conditioning_stop")
|
125
|
-
end
|
126
|
-
|
127
|
-
def sun_roof_control(state)
|
128
|
-
api.post("/vehicles/#{id}/command/sun_roof_control", query: {state: state})
|
129
|
-
end
|
130
|
-
|
131
|
-
def sun_roof_move(percent)
|
132
|
-
api.post("/vehicles/#{id}/command/sun_roof_control", query: {state: "move", percent: percent})
|
133
|
-
end
|
134
|
-
|
135
|
-
# Streaming
|
136
|
-
|
137
|
-
def stream(&reciever)
|
138
|
-
EventMachine.run do
|
139
|
-
request = EventMachine::HttpRequest.new(
|
140
|
-
"https://streaming.vn.teslamotors.com/stream/#{self["vehicle_id"]}/" +
|
141
|
-
"?values=speed,odometer,soc,elevation,est_heading,est_lat,est_lng,power")
|
142
|
-
|
143
|
-
http = request.get(
|
144
|
-
head: {
|
145
|
-
"authorization" => [
|
146
|
-
email,
|
147
|
-
self["tokens"].first
|
148
|
-
]
|
149
|
-
},
|
150
|
-
inactivity_timeout: 15)
|
151
|
-
|
152
|
-
http.stream do |chunk|
|
153
|
-
attributes = chunk.split(",")
|
154
|
-
reciever.call({
|
155
|
-
time: DateTime.strptime((attributes[0].to_i/1000).to_s, "%s"),
|
156
|
-
speed: attributes[1].to_f,
|
157
|
-
odometer: attributes[2].to_f,
|
158
|
-
soc: attributes[3].to_f,
|
159
|
-
elevation: attributes[4].to_f,
|
160
|
-
est_heading: attributes[5].to_f,
|
161
|
-
est_lat: attributes[6].to_f,
|
162
|
-
est_lng: attributes[7].to_f,
|
163
|
-
power: attributes[8].to_f
|
164
|
-
})
|
165
|
-
end
|
166
|
-
|
167
|
-
http.callback { EventMachine.stop }
|
168
|
-
http.errback { EventMachine.stop }
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
4
|
+
require "tesla_api/version"
|
5
|
+
require "tesla_api/client"
|
6
|
+
require "tesla_api/vehicle"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module TeslaApi
|
2
|
+
class Client
|
3
|
+
include HTTParty
|
4
|
+
base_uri "https://owner-api.teslamotors.com/api/1"
|
5
|
+
format :json
|
6
|
+
|
7
|
+
attr_reader :email
|
8
|
+
|
9
|
+
def initialize(email, password, client_id, client_secret)
|
10
|
+
@email = email
|
11
|
+
response = self.class.post(
|
12
|
+
"https://owner-api.teslamotors.com/oauth/token",
|
13
|
+
body: {
|
14
|
+
"grant_type" => "password",
|
15
|
+
"client_id" => client_id,
|
16
|
+
"client_secret" => client_secret,
|
17
|
+
"email" => email,
|
18
|
+
"password" => password
|
19
|
+
}
|
20
|
+
)
|
21
|
+
self.class.headers "Authorization" => "Bearer #{response["access_token"]}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def vehicles
|
25
|
+
self.class.get("/vehicles")["response"].map { |v| Vehicle.new(self.class, email, v["id"], v) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module TeslaApi
|
2
|
+
class Vehicle
|
3
|
+
attr_reader :api, :email, :id, :vehicle
|
4
|
+
|
5
|
+
def initialize(api, email, id, vehicle)
|
6
|
+
@api = api
|
7
|
+
@email = email
|
8
|
+
@id = id
|
9
|
+
@vehicle = vehicle
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
vehicle[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
# State
|
17
|
+
|
18
|
+
def mobile_enabled
|
19
|
+
api.get("/vehicles/#{id}/mobile_enabled")["response"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def charge_state
|
23
|
+
api.get("/vehicles/#{id}/data_request/charge_state")["response"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def climate_state
|
27
|
+
api.get("/vehicles/#{id}/data_request/climate_state")["response"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def drive_state
|
31
|
+
api.get("/vehicles/#{id}/data_request/drive_state")["response"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def gui_settings
|
35
|
+
api.get("/vehicles/#{id}/data_request/gui_settings")["response"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def vehicle_state
|
39
|
+
api.get("/vehicles/#{id}/data_request/vehicle_state")["response"]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Commands
|
43
|
+
|
44
|
+
def wake_up
|
45
|
+
api.post("/vehicles/#{id}/wake_up")
|
46
|
+
end
|
47
|
+
|
48
|
+
def charge_port_door_open
|
49
|
+
api.post("/vehicles/#{id}/command/charge_port_door_open")
|
50
|
+
end
|
51
|
+
|
52
|
+
def charge_standard
|
53
|
+
api.post("/vehicles/#{id}/command/charge_standard")
|
54
|
+
end
|
55
|
+
|
56
|
+
def charge_max_range
|
57
|
+
api.post("/vehicles/#{id}/command/charge_max_range")
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_charge_limit(percent)
|
61
|
+
api.post("/vehicles/#{id}/command/set_charge_limit", query: {state: "set", percent: percent})
|
62
|
+
end
|
63
|
+
|
64
|
+
def charge_start
|
65
|
+
api.post("/vehicles/#{id}/command/charge_start")
|
66
|
+
end
|
67
|
+
|
68
|
+
def charge_stop
|
69
|
+
api.post("/vehicles/#{id}/command/charge_stop")
|
70
|
+
end
|
71
|
+
|
72
|
+
def flash_lights
|
73
|
+
api.post("/vehicles/#{id}/command/flash_lights")
|
74
|
+
end
|
75
|
+
|
76
|
+
def honk_horn
|
77
|
+
api.post("/vehicles/#{id}/command/honk_horn")
|
78
|
+
end
|
79
|
+
|
80
|
+
def door_unlock
|
81
|
+
api.post("/vehicles/#{id}/command/door_unlock")
|
82
|
+
end
|
83
|
+
|
84
|
+
def door_lock
|
85
|
+
api.post("/vehicles/#{id}/command/door_lock")
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_temps(driver_temp, passenger_temp)
|
89
|
+
api.post("/vehicles/#{id}/command/set_temps", query: {driver_temp: driver_temp, passenger_temp: passenger_temp})
|
90
|
+
end
|
91
|
+
|
92
|
+
def auto_conditioning_start
|
93
|
+
api.post("/vehicles/#{id}/command/auto_conditioning_start")
|
94
|
+
end
|
95
|
+
|
96
|
+
def auto_conditioning_stop
|
97
|
+
api.post("/vehicles/#{id}/command/auto_conditioning_stop")
|
98
|
+
end
|
99
|
+
|
100
|
+
def sun_roof_control(state)
|
101
|
+
api.post("/vehicles/#{id}/command/sun_roof_control", query: {state: state})
|
102
|
+
end
|
103
|
+
|
104
|
+
def sun_roof_move(percent)
|
105
|
+
api.post("/vehicles/#{id}/command/sun_roof_control", query: {state: "move", percent: percent})
|
106
|
+
end
|
107
|
+
|
108
|
+
# Streaming
|
109
|
+
|
110
|
+
def stream(&reciever)
|
111
|
+
EventMachine.run do
|
112
|
+
request = EventMachine::HttpRequest.new(
|
113
|
+
"https://streaming.vn.teslamotors.com/stream/#{self["vehicle_id"]}/" +
|
114
|
+
"?values=speed,odometer,soc,elevation,est_heading,est_lat,est_lng,power")
|
115
|
+
|
116
|
+
http = request.get(
|
117
|
+
head: {
|
118
|
+
"authorization" => [
|
119
|
+
email,
|
120
|
+
self["tokens"].first
|
121
|
+
]
|
122
|
+
},
|
123
|
+
inactivity_timeout: 15)
|
124
|
+
|
125
|
+
http.stream do |chunk|
|
126
|
+
attributes = chunk.split(",")
|
127
|
+
reciever.call({
|
128
|
+
time: DateTime.strptime((attributes[0].to_i/1000).to_s, "%s"),
|
129
|
+
speed: attributes[1].to_f,
|
130
|
+
odometer: attributes[2].to_f,
|
131
|
+
soc: attributes[3].to_f,
|
132
|
+
elevation: attributes[4].to_f,
|
133
|
+
est_heading: attributes[5].to_f,
|
134
|
+
est_lat: attributes[6].to_f,
|
135
|
+
est_lng: attributes[7].to_f,
|
136
|
+
power: attributes[8].to_f
|
137
|
+
})
|
138
|
+
end
|
139
|
+
|
140
|
+
http.callback { EventMachine.stop }
|
141
|
+
http.errback { EventMachine.stop }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/lib/tesla_api/version.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://owner-api.teslamotors.com/oauth/token
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: grant_type=password&client_id=<TESLA_CLIENT_ID>&client_secret=<TESLA_CLIENT_SECRET>&email=<TESLA_EMAIL>&password=<TESLA_PASS>
|
9
|
+
headers:
|
10
|
+
Accept-Encoding:
|
11
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
12
|
+
Accept:
|
13
|
+
- "*/*"
|
14
|
+
User-Agent:
|
15
|
+
- Ruby
|
16
|
+
response:
|
17
|
+
status:
|
18
|
+
code: 200
|
19
|
+
message: OK
|
20
|
+
headers:
|
21
|
+
Server:
|
22
|
+
- nginx
|
23
|
+
Date:
|
24
|
+
- Mon, 15 Dec 2014 03:09:22 GMT
|
25
|
+
Content-Type:
|
26
|
+
- application/json; charset=utf-8
|
27
|
+
Transfer-Encoding:
|
28
|
+
- chunked
|
29
|
+
Connection:
|
30
|
+
- keep-alive
|
31
|
+
Status:
|
32
|
+
- 200 OK
|
33
|
+
Cache-Control:
|
34
|
+
- no-store
|
35
|
+
Pragma:
|
36
|
+
- no-cache
|
37
|
+
X-Ua-Compatible:
|
38
|
+
- IE=Edge,chrome=1
|
39
|
+
X-Request-Id:
|
40
|
+
- 349d563d345a9694c610770b743d3006
|
41
|
+
X-Runtime:
|
42
|
+
- '0.416152'
|
43
|
+
body:
|
44
|
+
encoding: UTF-8
|
45
|
+
string: '{"access_token":"1cba4845a8653d4b731440e9911d84304a179bd16a9ecbc9b649f2d8e0f6947e","token_type":"bearer","expires_in":7776000}'
|
46
|
+
http_version:
|
47
|
+
recorded_at: Mon, 15 Dec 2014 03:09:22 GMT
|
48
|
+
recorded_with: VCR 2.9.3
|
@@ -0,0 +1,89 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://owner-api.teslamotors.com/oauth/token
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: grant_type=password&client_id=<TESLA_CLIENT_ID>&client_secret=<TESLA_CLIENT_SECRET>&email=<TESLA_EMAIL>&password=<TESLA_PASS>
|
9
|
+
headers:
|
10
|
+
Accept-Encoding:
|
11
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
12
|
+
Accept:
|
13
|
+
- "*/*"
|
14
|
+
User-Agent:
|
15
|
+
- Ruby
|
16
|
+
response:
|
17
|
+
status:
|
18
|
+
code: 200
|
19
|
+
message: OK
|
20
|
+
headers:
|
21
|
+
Server:
|
22
|
+
- nginx
|
23
|
+
Date:
|
24
|
+
- Mon, 15 Dec 2014 03:28:30 GMT
|
25
|
+
Content-Type:
|
26
|
+
- application/json; charset=utf-8
|
27
|
+
Transfer-Encoding:
|
28
|
+
- chunked
|
29
|
+
Connection:
|
30
|
+
- keep-alive
|
31
|
+
Status:
|
32
|
+
- 200 OK
|
33
|
+
Cache-Control:
|
34
|
+
- no-store
|
35
|
+
Pragma:
|
36
|
+
- no-cache
|
37
|
+
X-Ua-Compatible:
|
38
|
+
- IE=Edge,chrome=1
|
39
|
+
X-Request-Id:
|
40
|
+
- 2716f7fd5f4ee1239d044ddfba838d70
|
41
|
+
X-Runtime:
|
42
|
+
- '0.372092'
|
43
|
+
body:
|
44
|
+
encoding: UTF-8
|
45
|
+
string: '{"access_token":"53514fcfea416c9dd77b50d0a1c1dc681960002d23c22b1a9cf2681e8cbec8da","token_type":"bearer","expires_in":7776000}'
|
46
|
+
http_version:
|
47
|
+
recorded_at: Mon, 15 Dec 2014 03:28:30 GMT
|
48
|
+
- request:
|
49
|
+
method: get
|
50
|
+
uri: https://owner-api.teslamotors.com/api/1/vehicles
|
51
|
+
body:
|
52
|
+
encoding: US-ASCII
|
53
|
+
string: ''
|
54
|
+
headers:
|
55
|
+
Authorization:
|
56
|
+
- Bearer 53514fcfea416c9dd77b50d0a1c1dc681960002d23c22b1a9cf2681e8cbec8da
|
57
|
+
response:
|
58
|
+
status:
|
59
|
+
code: 200
|
60
|
+
message: OK
|
61
|
+
headers:
|
62
|
+
Server:
|
63
|
+
- nginx
|
64
|
+
Date:
|
65
|
+
- Mon, 15 Dec 2014 03:28:30 GMT
|
66
|
+
Content-Type:
|
67
|
+
- application/json; charset=utf-8
|
68
|
+
Content-Length:
|
69
|
+
- '446'
|
70
|
+
Connection:
|
71
|
+
- keep-alive
|
72
|
+
Status:
|
73
|
+
- 200 OK
|
74
|
+
X-Ua-Compatible:
|
75
|
+
- IE=Edge,chrome=1
|
76
|
+
Etag:
|
77
|
+
- '"4a35974adbecef7554d775e098548913"'
|
78
|
+
Cache-Control:
|
79
|
+
- max-age=0, private, must-revalidate
|
80
|
+
X-Request-Id:
|
81
|
+
- 59523c1d2611a93fa82e277ae4d97e87
|
82
|
+
X-Runtime:
|
83
|
+
- '0.289093'
|
84
|
+
body:
|
85
|
+
encoding: UTF-8
|
86
|
+
string: '{"response":[{"color":null,"display_name":"Nikola","id":1514029006966957156,"option_codes":"MS01,RENA,TM00,DRLH,PF00,BT85,PBCW,RFPO,WT19,IBMB,IDPB,TR00,SU01,SC01,TP01,AU01,CH00,HP00,PA00,PS00,AD02,X020,X025,X001,X003,X007,X011,X013,COUS","vehicle_id":490215852,"vin":"5YJSA1CN5CFP01657","tokens":["74bd32f47d3c8d9e","c244a90fbad06a65"],"state":"online","remote_start_enabled":true,"calendar_enabled":true,"notifications_enabled":true}],"count":1}'
|
87
|
+
http_version:
|
88
|
+
recorded_at: Mon, 15 Dec 2014 03:28:30 GMT
|
89
|
+
recorded_with: VCR 2.9.3
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe TeslaApi::Client do
|
4
|
+
subject(:tesla_api) { TeslaApi::Client.new(ENV["TESLA_EMAIL"], ENV["TESLA_PASS"], ENV["TESLA_CLIENT_ID"], ENV["TESLA_CLIENT_SECRET"]) }
|
5
|
+
|
6
|
+
describe "#initialize", vcr: { cassette_name: "client-initialize" } do
|
7
|
+
it { is_expected.to be_a(TeslaApi::Client) }
|
8
|
+
|
9
|
+
it "logs into the API" do
|
10
|
+
base_uri = URI.parse(tesla_api.class.base_uri)
|
11
|
+
expect(a_request(:post, "https://#{base_uri.host}/oauth/token")).to have_been_made.once
|
12
|
+
end
|
13
|
+
|
14
|
+
it "sets a Bearer token" do
|
15
|
+
expect(tesla_api.class.headers).to include({"Authorization" => /Bearer [a-z0-9]{32}/})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#vehicles", vcr: {cassette_name: "client-vehicles"} do
|
20
|
+
it "lists the vehicles on the account" do
|
21
|
+
expect(tesla_api.vehicles).to include(TeslaApi::Vehicle)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'dotenv'
|
2
|
+
require 'vcr'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
|
5
|
+
require 'tesla_api'
|
6
|
+
|
7
|
+
Dotenv.load
|
8
|
+
|
9
|
+
VCR.configure do |c|
|
10
|
+
c.cassette_library_dir = "spec/cassettes"
|
11
|
+
c.hook_into :webmock
|
12
|
+
c.default_cassette_options = {:record => :once}
|
13
|
+
c.configure_rspec_metadata!
|
14
|
+
|
15
|
+
c.define_cassette_placeholder("<TESLA_EMAIL>") { CGI::escape(ENV["TESLA_EMAIL"]) }
|
16
|
+
c.define_cassette_placeholder("<TESLA_PASS>") { ENV["TESLA_PASS"] }
|
17
|
+
c.define_cassette_placeholder("<TESLA_CLIENT_ID>") { ENV["TESLA_CLIENT_ID"] }
|
18
|
+
c.define_cassette_placeholder("<TESLA_CLIENT_SECRET>") { ENV["TESLA_CLIENT_SECRET"] }
|
19
|
+
end
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.expect_with :rspec do |expectations|
|
23
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
24
|
+
end
|
25
|
+
|
26
|
+
config.mock_with :rspec do |mocks|
|
27
|
+
mocks.verify_partial_doubles = true
|
28
|
+
end
|
29
|
+
|
30
|
+
config.disable_monkey_patching!
|
31
|
+
config.order = :random
|
32
|
+
Kernel.srand config.seed
|
33
|
+
end
|
data/tesla_api.gemspec
CHANGED
@@ -17,6 +17,13 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
+
spec.add_dependency "httparty"
|
21
|
+
spec.add_dependency "em-http-request"
|
22
|
+
|
20
23
|
spec.add_development_dependency "bundler", "~> 1.7"
|
21
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.1"
|
26
|
+
spec.add_development_dependency "vcr", "~> 2.9"
|
27
|
+
spec.add_development_dependency "webmock", "~> 1.20"
|
28
|
+
spec.add_development_dependency "dotenv", "~> 1.0"
|
22
29
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tesla_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Dorr
|
@@ -10,6 +10,34 @@ bindir: bin
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2014-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httparty
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: em-http-request
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: bundler
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +66,62 @@ dependencies:
|
|
38
66
|
- - "~>"
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: vcr
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.9'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.9'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.20'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.20'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: dotenv
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.0'
|
41
125
|
description: Check the state of your Tesla Model S and issue basic commands. Stream
|
42
126
|
data from the car's telematics system.
|
43
127
|
email:
|
@@ -47,16 +131,21 @@ extensions: []
|
|
47
131
|
extra_rdoc_files: []
|
48
132
|
files:
|
49
133
|
- ".gitignore"
|
134
|
+
- ".rspec"
|
135
|
+
- ".travis.yml"
|
50
136
|
- Gemfile
|
51
|
-
- LICENSE
|
137
|
+
- LICENSE
|
52
138
|
- README.md
|
53
139
|
- Rakefile
|
54
140
|
- apiary.apib
|
55
141
|
- lib/tesla_api.rb
|
142
|
+
- lib/tesla_api/client.rb
|
143
|
+
- lib/tesla_api/vehicle.rb
|
56
144
|
- lib/tesla_api/version.rb
|
57
|
-
-
|
58
|
-
-
|
59
|
-
-
|
145
|
+
- spec/cassettes/client-initialize.yml
|
146
|
+
- spec/cassettes/client-vehicles.yml
|
147
|
+
- spec/lib/tesla_api/client_spec.rb
|
148
|
+
- spec/spec_helper.rb
|
60
149
|
- tesla_api.gemspec
|
61
150
|
homepage: https://github.com/timdorr/model-s-api
|
62
151
|
licenses:
|
@@ -78,8 +167,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
167
|
version: '0'
|
79
168
|
requirements: []
|
80
169
|
rubyforge_project:
|
81
|
-
rubygems_version: 2.
|
170
|
+
rubygems_version: 2.4.3
|
82
171
|
signing_key:
|
83
172
|
specification_version: 4
|
84
173
|
summary: A wrapper for the Tesla API
|
85
|
-
test_files:
|
174
|
+
test_files:
|
175
|
+
- spec/cassettes/client-initialize.yml
|
176
|
+
- spec/cassettes/client-vehicles.yml
|
177
|
+
- spec/lib/tesla_api/client_spec.rb
|
178
|
+
- spec/spec_helper.rb
|
data/logs/login.GET.log
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
GET /login HTTP/1.1
|
2
|
-
Host: portal.vn.teslamotors.com
|
3
|
-
Connection: Keep-Alive
|
4
|
-
Accept-Encoding: gzip deflate
|
5
|
-
|
6
|
-
|
7
|
-
HTTP/1.1 200 OK
|
8
|
-
Content-Type: text/html; charset=utf-8
|
9
|
-
X-UA-Compatible: IE=Edge,chrome=1
|
10
|
-
ETag: "68693d0c80ee503a112cafc18a273525"
|
11
|
-
Cache-Control: max-age=0, private, must-revalidate
|
12
|
-
Set-Cookie: _s_portal_session=x; path=/; secure; HttpOnly
|
13
|
-
X-Request-Id: 80985fcba166592ed09cce956fd395d6
|
14
|
-
X-Runtime: 0.006161
|
15
|
-
Date: Sun, 03 Feb 2013 03:01:52 GMT
|
16
|
-
X-Rack-Cache: miss
|
17
|
-
Server: thin 1.4.1 codename Chromeo
|
18
|
-
Transfer-Encoding: chunked
|
19
|
-
|
20
|
-
205
|
21
|
-
<!DOCTYPE html>
|
22
|
-
<html>
|
23
|
-
<head>
|
24
|
-
<title>Portal</title>
|
25
|
-
<script src="/assets/application-a5592d882a1bdc39d6b26c108a8ffbe0.js" type="text/javascript"></script>
|
26
|
-
<meta content="authenticity_token" name="csrf-param" />
|
27
|
-
<meta content="kOHBAsesbSMy0AJKsPC7f9aI/QgE9g4SxbUcfZAhvdg=" name="csrf-token" />
|
28
|
-
<meta name="csrf-token" content="kOHBAsesbSMy0AJKsPC7f9aI/QgE9g4SxbUcfZAhvdg=">
|
29
|
-
|
30
|
-
<link href='/images/favicon.ico' rel='shortcut icon'>
|
31
|
-
|
32
|
-
</head>
|
33
|
-
<body>
|
34
|
-
<header>
|
35
|
-
<li><a href="/login">Login</a></li>
|
36
|
-
</header>
|
37
|
-
|
38
|
-
|
39
|
-
</body>
|
40
|
-
</html>
|
41
|
-
|
42
|
-
0
|
43
|
-
|
44
|
-
|
data/logs/login.POST.log
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
POST /login HTTP/1.1
|
2
|
-
Content-Length: 81
|
3
|
-
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
|
4
|
-
Host: portal.vn.teslamotors.com
|
5
|
-
Connection: Keep-Alive
|
6
|
-
Cookie: _s_portal_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWI2YzM0ZTJkMzZkMTk4ZWVkYjkzYzBiYWNmMzI3MTZkBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMWtPSEJBc2VzYlNNeTBBSktzUEM3ZjlhSS9RZ0U5ZzRTeGJVY2ZaQWh2ZGc9BjsARg%3D%3D--da659682afba93065edfe2932903edc6eb16d0ac
|
7
|
-
Cookie2: $Version=1
|
8
|
-
Accept-Encoding: gzip deflate
|
9
|
-
|
10
|
-
user_session%5Bpassword%5D=xxx&user_session%5Bemail%5D=xxx%40xxx.com
|
11
|
-
HTTP/1.1 302 Found
|
12
|
-
Location: https://portal.vn.teslamotors.com/
|
13
|
-
Content-Type: text/html; charset=utf-8
|
14
|
-
X-UA-Compatible: IE=Edge,chrome=1
|
15
|
-
Cache-Control: no-cache
|
16
|
-
Set-Cookie: user_credentials=x; path=/; expires=Fri, 03-May-2013 03:01:54 GMT; secure; HttpOnly
|
17
|
-
Set-Cookie: _s_portal_session=x; path=/; secure; HttpOnly
|
18
|
-
X-Request-Id: a1c6e1dc7d2ba9c1c8cdec6ad536b2e4
|
19
|
-
X-Runtime: 1.129245
|
20
|
-
Date: Sun, 03 Feb 2013 03:01:54 GMT
|
21
|
-
X-Rack-Cache: invalidate, pass
|
22
|
-
Server: thin 1.4.1 codename Chromeo
|
23
|
-
Transfer-Encoding: chunked
|
24
|
-
|
25
|
-
64
|
26
|
-
<html><body>You are being <a href="https://portal.vn.teslamotors.com/">redirected</a>.</body></html>
|
27
|
-
0
|
28
|
-
|
29
|
-
|
data/logs/root.GET.log
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
GET / HTTP/1.1
|
2
|
-
Host: portal.vn.teslamotors.com
|
3
|
-
Connection: Keep-Alive
|
4
|
-
Cookie: _s_portal_session=x; user_credentials=x
|
5
|
-
Cookie2: $Version=1
|
6
|
-
Accept-Encoding: gzip deflate
|
7
|
-
|
8
|
-
|
9
|
-
HTTP/1.1 200 OK
|
10
|
-
Content-Type: text/html; charset=utf-8
|
11
|
-
X-UA-Compatible: IE=Edge,chrome=1
|
12
|
-
ETag: "389b2980d80c9fac569172b31c06dddb"
|
13
|
-
Cache-Control: must-revalidate, private, max-age=0
|
14
|
-
Set-Cookie: _s_portal_session=x; path=/; secure; HttpOnly
|
15
|
-
X-Request-Id: 2c54d0edded8b93de51a3cbf8a5dd3e9
|
16
|
-
X-Runtime: 0.007121
|
17
|
-
Date: Sun, 03 Feb 2013 03:01:54 GMT
|
18
|
-
X-Rack-Cache: miss
|
19
|
-
Server: thin 1.4.1 codename Chromeo
|
20
|
-
Transfer-Encoding: chunked
|
21
|
-
|
22
|
-
249
|
23
|
-
<!DOCTYPE html>
|
24
|
-
<html>
|
25
|
-
<head>
|
26
|
-
<title>Portal</title>
|
27
|
-
<script src="/assets/application-a5592d882a1bdc39d6b26c108a8ffbe0.js" type="text/javascript"></script>
|
28
|
-
<meta content="authenticity_token" name="csrf-param" />
|
29
|
-
<meta content="6/1QjgCk7xTyDJGAK8xIpCNAShD8a6NSco4rbEyz6bw=" name="csrf-token" />
|
30
|
-
<meta name="csrf-token" content="6/1QjgCk7xTyDJGAK8xIpCNAShD8a6NSco4rbEyz6bw=">
|
31
|
-
|
32
|
-
<link href='/images/favicon.ico' rel='shortcut icon'>
|
33
|
-
|
34
|
-
</head>
|
35
|
-
<body>
|
36
|
-
<header>
|
37
|
-
|
38
|
-
<li>
|
39
|
-
<a href="/logout" data-method="delete" rel="nofollow">Logout</a>
|
40
|
-
</li>
|
41
|
-
</header>
|
42
|
-
|
43
|
-
Welcome to the portal app.
|
44
|
-
|
45
|
-
</body>
|
46
|
-
</html>
|
47
|
-
|
48
|
-
0
|
49
|
-
|
50
|
-
|