tesla-api 0.0.1

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.
@@ -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
+