tesla-api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ require 'json'
2
+ require 'httpclient'
3
+
4
+ require "tesla-api/tesla_api"
5
+ require "tesla-api/version"
6
+ require "tesla-api/data"
7
+ require "tesla-api/private_api"
8
+ require "tesla-api/connection"
9
+ require "tesla-api/charge_state"
10
+ require "tesla-api/climate_state"
11
+ require "tesla-api/drive_state"
12
+ require "tesla-api/errors"
13
+ require "tesla-api/gui_settings"
14
+ require "tesla-api/vehicle"
15
+ require "tesla-api/vehicle_state"
16
+
@@ -0,0 +1,87 @@
1
+ module TeslaAPI
2
+
3
+ # Defines the current charge state of the vehicle
4
+ class ChargeState < Data
5
+ ##
6
+ # :method: charging_state
7
+ # Charging state ("Complete", "Charging")
8
+
9
+ ##
10
+ # :method charging_to_max?
11
+ # true if currently performing a range charge
12
+
13
+ ##
14
+ # :method: battery_range_miles
15
+ # Rated miles for the current charge
16
+
17
+ ##
18
+ # :method: estimated_battry_range_miles
19
+ # Range estimated from current driving
20
+
21
+ ##
22
+ # :method: ideal_battery_range_miles
23
+ # Ideal range for the current charge
24
+
25
+ ##
26
+ # :method: battery_percentage
27
+ # Percentage of battery charge
28
+
29
+ ##
30
+ # :method: battery_current_flow
31
+ # Current flowing into the battery
32
+
33
+ ##
34
+ # :method: charger_voltage
35
+ # Current voltage being used to charge battery
36
+
37
+ ##
38
+ # :method: charger_pilot_amperage
39
+ # Max amperage allowed by the charger
40
+
41
+ ##
42
+ # :method: charger_actual_amperage
43
+ # Current amperage being drawn into battery
44
+
45
+ ##
46
+ # :method: charger_power
47
+ # Kilowatt of charger (rounded down)
48
+
49
+ ##
50
+ # :method: hours_to_full_charge
51
+ # Hours remaining until the vehicle is fully charged
52
+
53
+ ##
54
+ # :method: charge_rate_miles_per_hour
55
+ # Miles of range being added per hour
56
+
57
+ ##
58
+ # :method: charge_port_open?
59
+ # true if the charge port is open
60
+
61
+ ##
62
+ # :method: supercharging?
63
+ # true if charging via a Tesla SuperCharger
64
+
65
+ def initialize(data) # :nodoc:
66
+ ivar_from_data("charging_state", "charging_state", data)
67
+ ivar_from_data("charging_to_max", "charge_to_max_range", data)
68
+ ivar_from_data("battery_range_miles", "battery_range", data)
69
+ ivar_from_data("estimated_battry_range_miles", "est_battery_range", data)
70
+ ivar_from_data("ideal_battery_range_miles", "ideal_battery_range", data)
71
+ ivar_from_data("battery_percentage", "battery_level", data)
72
+ ivar_from_data("battery_current_flow", "battery_current", data)
73
+ ivar_from_data("charger_voltage", "charger_voltage", data)
74
+ ivar_from_data("charger_pilot_amperage", "charger_pilot_current", data)
75
+ ivar_from_data("charger_actual_amperage", "charger_actual_current", data)
76
+ ivar_from_data("charger_power", "charger_power", data)
77
+ ivar_from_data("hours_to_full_charge", "time_to_full_charge", data)
78
+ ivar_from_data("charge_rate_miles_per_hour", "charge_rate", data)
79
+ ivar_from_data("charge_port_open", "charge_port_door_open", data)
80
+ ivar_from_data("supercharging", "fast_charger_present", data)
81
+
82
+ @charging = charging_state == "Charging"
83
+ @charge_complete = charging_state == "Complete"
84
+ end
85
+ end
86
+ end
87
+
@@ -0,0 +1,55 @@
1
+ module TeslaAPI
2
+ # Defines the climate state of the vehicle.
3
+ #
4
+ class ClimateState < Data
5
+ ##
6
+ # :method: inside_temp_celcius
7
+ # Temperature (celcius) inside the vehicle
8
+
9
+ ##
10
+ # :method: outside_temp_celcius
11
+ # Temperature (celcius) outside the vehicle
12
+
13
+ ##
14
+ # :method: driver_temp_setting_celcius
15
+ # Temperature (celcius) the driver has set
16
+
17
+ ##
18
+ # :method: passenger_temp_setting_celcius
19
+ # Temperature (celcius) the passenger has set
20
+
21
+ ##
22
+ # :method: fan_speed
23
+ # 0 to 6 (or nil)
24
+
25
+ ##
26
+ # :method: auto_conditioning_on?
27
+ # true if auto air conditioning is on
28
+
29
+ ##
30
+ # :method: front_defroster_on?
31
+ # true if the front defroster is on
32
+
33
+ ##
34
+ # :method: rear_defroster_on?
35
+ # true if the rear defroster is on
36
+
37
+ ##
38
+ # :method: fan_on?
39
+ # true if the fan is on
40
+
41
+ def initialize(data) # :nodoc:
42
+ ivar_from_data("inside_temp_celcius", "inside_temp", data)
43
+ ivar_from_data("outside_temp_celcius", "outside_temp", data)
44
+ ivar_from_data("driver_temp_setting_celcius", "driver_temp_setting", data)
45
+ ivar_from_data("passenger_temp_setting_celcius", "passenger_temp_setting", data)
46
+ ivar_from_data("fan_speed", "fan_speed", data)
47
+
48
+ @auto_conditioning_on = !!data["is_auto_conditioning_on"]
49
+ @front_defroster_on = !!data["is_front_defroster_on"]
50
+ @rear_defroster_on = !!data["is_rear_defroster_on"]
51
+ @fan_on = !data["fan_status"].nil?
52
+ end
53
+ end
54
+ end
55
+
@@ -0,0 +1,58 @@
1
+ module TeslaAPI
2
+ # Connection object to the Tesla Model S HTTP API endpoint.
3
+ class Connection
4
+ include PrivateAPI
5
+
6
+ # email address of logged in user
7
+ attr_reader :email
8
+
9
+ # password of logged in user
10
+ attr_reader :password
11
+
12
+ # Host for status/command related API
13
+ HOST = "https://portal.vn.teslamotors.com"
14
+
15
+ # Host for streaming API
16
+ STREAMING_HOST = "https://streaming.vn.teslamotors.com"
17
+
18
+ # Supply the email and password for login to teslamotors.com
19
+ def initialize(email, password)
20
+ @email = email
21
+ @password = password
22
+
23
+ @client = HTTPClient.new
24
+ @client.set_cookie_store("cookie.dat")
25
+
26
+ login(email, password)
27
+ end
28
+
29
+ # Call to see all HTTP traffic to and from the API
30
+ def debug!
31
+ @client.debug_dev = STDOUT
32
+ end
33
+
34
+ # Convenience method to return the first Vehicle
35
+ def vehicle
36
+ vehicles.first
37
+ end
38
+
39
+ # Returns Vehicle objects for all vehicles the account contains
40
+ def vehicles
41
+ @vehicles ||= begin
42
+ _, json = get_json("/vehicles")
43
+ json.map { |data| Vehicle.new(self, data) }
44
+ end
45
+ end
46
+
47
+ # Force the vehicles to reload (in case some of the data has changed)
48
+ def reload!
49
+ @vehicles = nil
50
+ end
51
+
52
+ # Logged into the API
53
+ def logged_in?
54
+ @logged_in
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,52 @@
1
+ module TeslaAPI
2
+ # Base class for all data responses from the HTTP API
3
+ #
4
+ # Defines an instance_variable? method for each instance_variable defined allowing for
5
+ # methods such as __________?
6
+ #
7
+ # Also overrides #inspect to elimiante the back reference to the connection object
8
+ class Data
9
+ def method_missing(method_name, *args, &block) # :nodoc:
10
+ if has_query_ivar_method?(method_name)
11
+ instance_variable_get(ivar_for_method_name(method_name))
12
+ else
13
+ super(symbol, *args, &block)
14
+ end
15
+ end
16
+
17
+ def respond_to_missing?(method_name, include_private = false) # :nodoc:
18
+ has_query_ivar_method?(method_name) || super
19
+ end
20
+
21
+ def inspect # :nodoc:
22
+ "#<#{self.class.name}:0x#{self.object_id.to_s(16)} #{inspect_ivars}>"
23
+ end
24
+
25
+ def ivar_from_data(name, data_key, data) # :nodoc:
26
+ instance_variable_set("@#{name}".to_sym, data[data_key])
27
+
28
+ self.class.send(:attr_reader, name.to_sym)
29
+ end
30
+
31
+ protected
32
+
33
+ def ivar_for_method_name(method_name) # :nodoc:
34
+ "@#{method_name.to_s.gsub(/\?$/,"")}".to_sym
35
+ end
36
+
37
+ def has_query_ivar_method?(method_name) # :nodoc:
38
+ method = method_name.to_s
39
+
40
+ method =~ /(.+)\?/ && instance_variables.include?(ivar_for_method_name(method_name))
41
+ end
42
+
43
+ def inspect_ivars # :nodoc:
44
+ ivars_for_inspect.map { |ivar| "#{ivar}=#{instance_variable_get(ivar)}" }.join(" ")
45
+ end
46
+
47
+ def ivars_for_inspect # :nodoc:
48
+ (instance_variables - [:@tesla])
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,37 @@
1
+ module TeslaAPI
2
+ # Data defining the driving state of the the vehicle
3
+ class DriveState < Data
4
+ # Time when GPS data was recorded
5
+ attr_reader :gps_timestamp
6
+
7
+ ##
8
+ # :method: shift_state
9
+ # Unknown
10
+
11
+ ##
12
+ # :method: speed
13
+ # Vehicle speed (units?)
14
+
15
+ ##
16
+ # :method: latitude
17
+ # Lattitude of vehicle
18
+
19
+ ##
20
+ # :method: longitude
21
+ # Longitude of vehicle
22
+
23
+ ##
24
+ # :method: heading
25
+ # Compass heading (0 to 360) degrees
26
+
27
+ def initialize(data) # :nodoc:
28
+ ivar_from_data("shift_state", "shift_state", data)
29
+ ivar_from_data("speed", "speed", data)
30
+ ivar_from_data("latitude", "latitude", data)
31
+ ivar_from_data("longitude", "longitude", data)
32
+ ivar_from_data("heading", "heading", data)
33
+ @gps_timestamp = Time.at(data["gps_as_of"])
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,41 @@
1
+ module TeslaAPI
2
+ # Exceptions thrown by the TeslaAPI
3
+ module Errors
4
+ # JSON cannot be parsed
5
+ class InvalidJSON < StandardError
6
+ # JSON parsing error details
7
+ attr_reader :error
8
+
9
+ def initialize(error) # :nodoc:
10
+ @error = error
11
+ end
12
+
13
+ def to_s # :nodoc:
14
+ error.to_s
15
+ end
16
+ end
17
+
18
+ # Thrown when the action requires a logged in connection
19
+ class NotLoggedIn < StandardError
20
+ end
21
+
22
+ # Thrown when the API returns a failure state
23
+ class APIFailure < StandardError
24
+ end
25
+
26
+ # Thrown when the response is invalid (non 200-OK HTTP result)
27
+ class InvalidResponse < StandardError
28
+ # Response object from httpclient
29
+ attr_reader :response
30
+
31
+ def initialize(response) # :nodoc:
32
+ @response = response
33
+ end
34
+
35
+ def to_s # :nodoc:
36
+ "Invalid Response: #{response.inspect}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,34 @@
1
+ module TeslaAPI
2
+ # Defines the current user settings for the vehicle's graphical display
3
+ class GUISettings < Data
4
+ ##
5
+ # :method: gui_distance_units
6
+ # Units ("mi/hr") for showing range
7
+
8
+ ##
9
+ # :method: gui_temperature_units
10
+ # Units ("F", "C") for showing temperaturs
11
+
12
+ ##
13
+ # :method: gui_charge_rate_units
14
+ # Units ("kW") for showing charge rage
15
+
16
+ ##
17
+ # :method: gui_range_display
18
+ # Units ("Rated", "Ideal") for showing range
19
+
20
+ # true if the UI show 24 hour time (e.g. 17:45)
21
+ def gui_24_hour_time?
22
+ @gui_24_hour_time
23
+ end
24
+
25
+ def initialize(data) # :nodoc:
26
+ ivar_from_data("gui_distance_units", "gui_distance_units", data)
27
+ ivar_from_data("gui_temperature_units", "gui_temperature_units", data)
28
+ ivar_from_data("gui_charge_rate_units", "gui_charge_rate_units", data)
29
+ ivar_from_data("gui_24_hour_time", "gui_24_hour_time", data)
30
+ ivar_from_data("gui_range_display", "gui_range_display", data)
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,157 @@
1
+ module TeslaAPI
2
+ module PrivateAPI # :nodoc:
3
+ def login(email, password)
4
+ params = { "user_session[email]" => email,
5
+ "user_session[password]" => password }
6
+
7
+ response = @client.post(Connection::HOST + "/login", params)
8
+
9
+ @logged_in = (response.status_code == HTTP::Status::FOUND)
10
+ end
11
+
12
+ def stream(vehicle)
13
+ raise "Doesn't work yet"
14
+
15
+ client = HTTPClient.new
16
+
17
+ uri = Connection::STREAMING_HOST + "/stream/#{vehicle.vehicle_id}/?values=speed,odometer,soc,elevation,est_heading,est_lat,est_lng,power,shift_state"
18
+
19
+ client.set_basic_auth(uri, streaming_login, vehicle.tokens.first)
20
+ client.get_content(uri) do |chunk|
21
+ p chunk
22
+ end
23
+ end
24
+
25
+ def set_temperature!(vehicle, driver_degrees_celcius, passenger_degrees_celcius)
26
+ command!(vehicle, "set_temps", :query => { :driver_degrees_celcius => driver_degrees_celcius,
27
+ :passenger_degrees_celcius => passenger_degrees_celcius })
28
+ end
29
+
30
+ def open_roof!(vehicle, state)
31
+ command!(vehicle, "sun_roof_control", :query => { :state => state })
32
+ end
33
+
34
+ def auto_conditioning_stop!(vehicle)
35
+ command!(vehicle, "auto_conditioning_stop")
36
+ end
37
+
38
+ def auto_conditioning_start!(vehicle)
39
+ command!(vehicle, "auto_conditioning_start")
40
+ end
41
+
42
+ def lock_door!(vehicle)
43
+ command!(vehicle, "door_lock")
44
+ end
45
+
46
+ def unlock_door!(vehicle)
47
+ command!(vehicle, "door_unlock")
48
+ end
49
+
50
+ def honk_horn!(vehicle)
51
+ command!(vehicle, "honk_horn")
52
+ end
53
+
54
+ def flash_lights!(vehicle)
55
+ command!(vehicle, "flash_lights")
56
+ end
57
+
58
+ def charge_stop!(vehicle)
59
+ command!(vehicle, "charge_stop")
60
+ end
61
+
62
+ def charge_start!(vehicle)
63
+ command!(vehicle, "charge_start")
64
+ end
65
+
66
+ def charge_max_range!(vehicle)
67
+ command!(vehicle, "charge_max_range")
68
+ end
69
+
70
+ def charge_standard!(vehicle)
71
+ command!(vehicle, "charge_standard")
72
+ end
73
+
74
+ def wake_up!(vehicle)
75
+ command!(vehicle, "wake_up")
76
+ end
77
+
78
+ def open_charge_port!(vehicle)
79
+ command!(vehicle, "charge_port_open")
80
+ end
81
+
82
+ def api_mobile_access?(vehicle)
83
+ _, json = get_json(command_url(vehicle, "mobile_enabled"))
84
+
85
+ json["result"] == true
86
+ end
87
+
88
+ def api_get_vehicle_state_for_vehicle(vehicle)
89
+ _, json = get_json(command_url(vehicle, "vehicle_state"))
90
+
91
+ VehicleState.new(json)
92
+ end
93
+
94
+ def api_gui_settings_for_vehicle(vehicle)
95
+ _, json = get_json(command_url(vehicle, "gui_settings"))
96
+
97
+ GUISettings.new(json)
98
+ end
99
+
100
+ def api_drive_state_for_vehicle(vehicle)
101
+ _, json = get_json(command_url(vehicle, "drive_state"))
102
+
103
+ DriveState.new(json)
104
+ end
105
+
106
+ def api_climate_state_for_vehicle(vehicle)
107
+ _, json = get_json(command_url(vehicle, "climate_state"))
108
+
109
+ ClimateState.new(json)
110
+ end
111
+
112
+ def api_charge_state_for_vehicle(vehicle)
113
+ _, json = get_json(command_url(vehicle, "charge_state"))
114
+
115
+ ChargeState.new(json)
116
+ end
117
+
118
+ private
119
+
120
+ def streaming_login
121
+ email.gsub("@","%")
122
+ end
123
+
124
+ def check_logged_in!
125
+ raise Errors::NotLoggedIn unless logged_in?
126
+ end
127
+
128
+ def command_url(vehicle, command_name)
129
+ "/vehicles/#{vehicle.id}/command/#{command_name}"
130
+ end
131
+
132
+ def command!(vehicle, command_name, options = {})
133
+ _, json = get_json(command_url(vehicle, command_name), options)
134
+
135
+ json["result"] ? json["result"] : raise(Errors::APIFailure.new(json["reason"]))
136
+ end
137
+
138
+ def get_json(uri, options = {})
139
+ check_logged_in!
140
+
141
+ response = get(uri, options)
142
+
143
+ [response, JSON.parse(response.body)]
144
+ rescue JSON::ParserError => e
145
+ raise Errors::InvalidJSON.new(e)
146
+ end
147
+
148
+ def get(uri, options = {})
149
+ response = @client.get(Connection::HOST + uri, options)
150
+
151
+ raise Errors::InvalidResponse.new(response) unless response.status_code == HTTP::Status::OK
152
+
153
+ response
154
+ end
155
+ end
156
+ end
157
+