tesla_api 1.2.0 → 1.3.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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.gitbook.yml +4 -0
  3. data/.travis.yml +3 -4
  4. data/LICENSE +1 -1
  5. data/README.md +7 -3
  6. data/Rakefile +27 -1
  7. data/apiary.apib +21 -14
  8. data/docs/README.md +26 -0
  9. data/docs/SUMMARY.md +39 -0
  10. data/docs/api-basics/authentication.md +40 -0
  11. data/docs/api-basics/vehicles.md +77 -0
  12. data/docs/vehicle/autopark.md +4 -0
  13. data/docs/vehicle/commands/README.md +22 -0
  14. data/docs/vehicle/commands/alerts.md +5 -0
  15. data/docs/vehicle/commands/calendar.md +3 -0
  16. data/docs/vehicle/commands/charging.md +3 -0
  17. data/docs/vehicle/commands/climate.md +3 -0
  18. data/docs/vehicle/commands/doors.md +3 -0
  19. data/docs/vehicle/commands/media.md +3 -0
  20. data/docs/vehicle/commands/navigation.md +3 -0
  21. data/docs/vehicle/commands/remotestart.md +3 -0
  22. data/docs/vehicle/commands/softwareupdate.md +3 -0
  23. data/docs/vehicle/commands/speedlimit.md +3 -0
  24. data/docs/vehicle/commands/sunroof.md +3 -0
  25. data/docs/vehicle/commands/trunk.md +3 -0
  26. data/docs/vehicle/commands/valet.md +3 -0
  27. data/docs/vehicle/commands/wake.md +3 -0
  28. data/docs/vehicle/optioncodes.md +253 -0
  29. data/docs/vehicle/state/README.md +34 -0
  30. data/docs/vehicle/state/chargestate.md +56 -0
  31. data/docs/vehicle/state/climatestate.md +43 -0
  32. data/docs/vehicle/state/data.md +181 -0
  33. data/docs/vehicle/state/drivestate.md +27 -0
  34. data/docs/vehicle/state/guisettings.md +21 -0
  35. data/docs/vehicle/state/mobileenabled.md +14 -0
  36. data/docs/vehicle/state/vehiclestate.md +47 -0
  37. data/docs/vehicle/streaming.md +4 -0
  38. data/lib/tesla_api.rb +7 -6
  39. data/lib/tesla_api/autopark.rb +62 -0
  40. data/lib/tesla_api/client.rb +32 -11
  41. data/lib/tesla_api/stream.rb +11 -7
  42. data/lib/tesla_api/vehicle.rb +50 -30
  43. data/lib/tesla_api/version.rb +1 -1
  44. data/spec/cassettes/client-login.yml +1 -1
  45. data/spec/cassettes/vehicle-activate_speed_limit.yml +95 -0
  46. data/spec/cassettes/vehicle-clear_speed_limit_pin.yml +95 -0
  47. data/spec/cassettes/vehicle-deactivate_speed_limit.yml +95 -0
  48. data/spec/cassettes/vehicle-open_frunk.yml +40 -73
  49. data/spec/cassettes/vehicle-open_trunk.yml +40 -73
  50. data/spec/cassettes/vehicle-set_speed_limit.yml +95 -0
  51. data/spec/lib/tesla_api/client_spec.rb +42 -15
  52. data/spec/lib/tesla_api/vehicle_spec.rb +160 -136
  53. data/spec/spec_helper.rb +6 -18
  54. data/tesla_api.gemspec +20 -24
  55. metadata +67 -42
  56. 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,14 @@
1
+ # Mobile Enabled
2
+
3
+ ## GET `/api/1/vehicles/{id}/mobile_enabled`
4
+
5
+ Lets you know if the Mobile Access setting is enabled in the car.
6
+
7
+ ### Response
8
+
9
+ ```json
10
+ {
11
+ "response": true
12
+ }
13
+ ```
14
+
@@ -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
+
@@ -0,0 +1,4 @@
1
+ # Streaming
2
+
3
+
4
+
data/lib/tesla_api.rb CHANGED
@@ -1,7 +1,8 @@
1
- require "httparty"
2
- require "em-http-request"
1
+ require 'httparty'
2
+ require 'em-http-request'
3
3
 
4
- require "tesla_api/version"
5
- require "tesla_api/client"
6
- require "tesla_api/stream"
7
- require "tesla_api/vehicle"
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
@@ -1,12 +1,13 @@
1
1
  module TeslaApi
2
2
  class Client
3
3
  include HTTParty
4
- base_uri "https://owner-api.teslamotors.com/api/1"
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["TESLA_CLIENT_ID"], client_secret = ENV["TESLA_CLIENT_SECRET"])
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 "Authorization" => "Bearer #{token}"
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
- "https://owner-api.teslamotors.com/oauth/token",
41
+ 'https://owner-api.teslamotors.com/oauth/token',
23
42
  body: {
24
- "grant_type" => "password",
25
- "client_id" => client_id,
26
- "client_secret" => client_secret,
27
- "email" => email,
28
- "password" => password
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.token = response["access_token"]
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("/vehicles")["response"].map { |v| Vehicle.new(self.class, email, v["id"], v) }
57
+ self.class.get('/vehicles')['response'].map { |v| Vehicle.new(self.class, v['id'], v) }
37
58
  end
38
59
  end
39
60
  end
@@ -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, "%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["vehicle_id"]}/?values=#{stream_params}")
34
+ "#{stream_endpoint}/stream/#{self['vehicle_id']}/?values=#{stream_params}")
31
35
  end
32
36
 
33
37
  def http
34
- @http ||= request.get(
38
+ request.get(
35
39
  head: {
36
- "authorization" => [email, self["tokens"].first]
40
+ 'authorization' => [email, self['tokens'].first]
37
41
  },
38
42
  inactivity_timeout: 15)
39
43
  end
40
44
 
41
45
  def stream_endpoint
42
- "https://streaming.vn.teslamotors.com"
46
+ 'https://streaming.vn.teslamotors.com'
43
47
  end
44
48
 
45
49
  def stream_params
46
- "speed,odometer,soc,elevation,est_heading,est_lat,est_lng,power"
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
@@ -1,11 +1,11 @@
1
1
  module TeslaApi
2
2
  class Vehicle
3
3
  include Stream
4
- attr_reader :api, :email, :id, :vehicle
4
+ include Autopark
5
+ attr_reader :api, :id, :vehicle
5
6
 
6
- def initialize(api, email, id, vehicle)
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")["response"]
34
+ api.get("/vehicles/#{id}/mobile_enabled")['response']
31
35
  end
32
36
 
33
37
  def gui_settings
34
- data_request("gui_settings")["response"]
38
+ data_request('gui_settings')['response']
35
39
  end
36
40
 
37
41
  def charge_state
38
- data_request("charge_state")["response"]
42
+ data_request('charge_state')['response']
39
43
  end
40
44
 
41
45
  def climate_state
42
- data_request("climate_state")["response"]
46
+ data_request('climate_state')['response']
43
47
  end
44
48
 
45
49
  def drive_state
46
- data_request("drive_state")["response"]
50
+ data_request('drive_state')['response']
47
51
  end
48
52
 
49
53
  def vehicle_state
50
- data_request("vehicle_state")["response"]
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")["response"]
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("set_valet_mode", body: {on: on, password: password})["response"]
64
+ command('set_valet_mode', body: {on: on, password: password})['response']
61
65
  end
62
66
 
63
67
  def reset_valet_pin
64
- command("reset_valet_pin")["response"]
68
+ command('reset_valet_pin')['response']
65
69
  end
66
70
 
67
71
  def charge_port_door_open
68
- command("charge_port_door_open")["response"]
72
+ command('charge_port_door_open')['response']
69
73
  end
70
74
 
71
75
  def charge_standard
72
- command("charge_standard")["response"]
76
+ command('charge_standard')['response']
73
77
  end
74
78
 
75
79
  def charge_max_range
76
- command("charge_max_range")["response"]
80
+ command('charge_max_range')['response']
77
81
  end
78
82
 
79
83
  def set_charge_limit(percent)
80
- command("set_charge_limit", body: {percent: percent})["response"]
84
+ command('set_charge_limit', body: {percent: percent})['response']
81
85
  end
82
86
 
83
87
  def charge_start
84
- command("charge_start")["response"]
88
+ command('charge_start')['response']
85
89
  end
86
90
 
87
91
  def charge_stop
88
- command("charge_stop")["response"]
92
+ command('charge_stop')['response']
89
93
  end
90
94
 
91
95
  def flash_lights
92
- command("flash_lights")["response"]
96
+ command('flash_lights')['response']
93
97
  end
94
98
 
95
99
  def honk_horn
96
- command("honk_horn")["response"]
100
+ command('honk_horn')['response']
97
101
  end
98
102
 
99
103
  def door_unlock
100
- command("door_unlock")["response"]
104
+ command('door_unlock')['response']
101
105
  end
102
106
 
103
107
  def door_lock
104
- command("door_lock")["response"]
108
+ command('door_lock')['response']
105
109
  end
106
110
 
107
111
  def set_temps(driver_temp, passenger_temp)
108
- command("set_temps", body: {driver_temp: driver_temp, passenger_temp: passenger_temp})["response"]
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("auto_conditioning_start")["response"]
116
+ command('auto_conditioning_start')['response']
113
117
  end
114
118
 
115
119
  def auto_conditioning_stop
116
- command("auto_conditioning_stop")["response"]
120
+ command('auto_conditioning_stop')['response']
117
121
  end
118
122
 
119
123
  def sun_roof_control(state)
120
- command("sun_roof_control", body: {state: state})["response"]
124
+ command('sun_roof_control', body: {state: state})['response']
121
125
  end
122
126
 
123
127
  def sun_roof_move(percent)
124
- command("sun_roof_control", body: {state: "move", percent: percent})["response"]
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("remote_start_drive", body: {password: password})["response"]
132
+ command('remote_start_drive', body: {password: password})['response']
129
133
  end
130
134
 
131
135
  def open_trunk
132
- command("trunk_open", body: {which_trunk: "rear"})
136
+ command('actuate_trunk', body: {which_trunk: 'rear'})['response']
133
137
  end
134
138
 
135
139
  def open_frunk
136
- command("trunk_open", body: {which_trunk: "rear"})
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