tesla 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 +7 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +31 -0
- data/CONTRIBUTING.md +45 -0
- data/{LICENSE.txt → LICENSE.md} +1 -1
- data/README.md +366 -0
- data/SECURITY.md +38 -0
- data/lib/tesla/api/alert_endpoints.rb +35 -0
- data/lib/tesla/api/charging_endpoints.rb +115 -0
- data/lib/tesla/api/climate_endpoints.rb +180 -0
- data/lib/tesla/api/closure_endpoints.rb +142 -0
- data/lib/tesla/api/command_endpoints.rb +63 -0
- data/lib/tesla/api/media_endpoints.rb +114 -0
- data/lib/tesla/api/navigation_endpoints.rb +51 -0
- data/lib/tesla/api/oauth_endpoints.rb +118 -0
- data/lib/tesla/api/partner_endpoints.rb +62 -0
- data/lib/tesla/api/security_endpoints.rb +123 -0
- data/lib/tesla/api/software_endpoints.rb +38 -0
- data/lib/tesla/api/user_endpoints.rb +40 -0
- data/lib/tesla/api/vehicle_endpoints.rb +174 -0
- data/lib/tesla/api.rb +39 -0
- data/lib/tesla/authenticator.rb +33 -0
- data/lib/tesla/bearer_token_authenticator.rb +38 -0
- data/lib/tesla/certificate_options.rb +51 -0
- data/lib/tesla/charge_state.rb +111 -0
- data/lib/tesla/charging_site.rb +67 -0
- data/lib/tesla/client.rb +336 -0
- data/lib/tesla/client_credentials.rb +230 -0
- data/lib/tesla/climate_state.rb +116 -0
- data/lib/tesla/configuration.rb +483 -0
- data/lib/tesla/connection.rb +361 -0
- data/lib/tesla/connection_pool.rb +186 -0
- data/lib/tesla/deep_copy.rb +41 -0
- data/lib/tesla/drive_state.rb +80 -0
- data/lib/tesla/errors/bad_gateway.rb +9 -0
- data/lib/tesla/errors/bad_request.rb +9 -0
- data/lib/tesla/errors/client_error.rb +9 -0
- data/lib/tesla/errors/command_failed.rb +42 -0
- data/lib/tesla/errors/device_unexpected_response.rb +12 -0
- data/lib/tesla/errors/error.rb +7 -0
- data/lib/tesla/errors/forbidden.rb +14 -0
- data/lib/tesla/errors/gateway_timeout.rb +9 -0
- data/lib/tesla/errors/http_error.rb +89 -0
- data/lib/tesla/errors/internal_server_error.rb +9 -0
- data/lib/tesla/errors/invalid_response.rb +33 -0
- data/lib/tesla/errors/locked.rb +12 -0
- data/lib/tesla/errors/method_not_allowed.rb +9 -0
- data/lib/tesla/errors/misdirected_request.rb +13 -0
- data/lib/tesla/errors/network_error.rb +30 -0
- data/lib/tesla/errors/not_acceptable.rb +9 -0
- data/lib/tesla/errors/not_found.rb +9 -0
- data/lib/tesla/errors/oauth_error.rb +53 -0
- data/lib/tesla/errors/payment_required.rb +12 -0
- data/lib/tesla/errors/precondition_failed.rb +13 -0
- data/lib/tesla/errors/request_timeout.rb +13 -0
- data/lib/tesla/errors/server_error.rb +9 -0
- data/lib/tesla/errors/service_unavailable.rb +9 -0
- data/lib/tesla/errors/too_many_redirects.rb +9 -0
- data/lib/tesla/errors/too_many_requests.rb +9 -0
- data/lib/tesla/errors/unauthorized.rb +9 -0
- data/lib/tesla/errors/unavailable_for_legal_reasons.rb +12 -0
- data/lib/tesla/errors/unprocessable_content.rb +12 -0
- data/lib/tesla/errors/vehicle_asleep.rb +32 -0
- data/lib/tesla/gui_settings.rb +60 -0
- data/lib/tesla/idempotence.rb +30 -0
- data/lib/tesla/identifiers.rb +27 -0
- data/lib/tesla/json_parsing.rb +38 -0
- data/lib/tesla/nearby_charging_sites.rb +35 -0
- data/lib/tesla/path_escaping.rb +30 -0
- data/lib/tesla/redacted_output.rb +161 -0
- data/lib/tesla/redirect_handler.rb +196 -0
- data/lib/tesla/region.rb +27 -0
- data/lib/tesla/request_builder.rb +138 -0
- data/lib/tesla/resource.rb +403 -0
- data/lib/tesla/response_parser.rb +92 -0
- data/lib/tesla/retry_after.rb +34 -0
- data/lib/tesla/retry_handler.rb +252 -0
- data/lib/tesla/settings.rb +115 -0
- data/lib/tesla/url_validation.rb +131 -0
- data/lib/tesla/user.rb +36 -0
- data/lib/tesla/vehicle.rb +90 -0
- data/lib/tesla/vehicle_config.rb +95 -0
- data/lib/tesla/vehicle_data.rb +67 -0
- data/lib/tesla/vehicle_state.rb +125 -0
- data/lib/tesla/version.rb +6 -0
- data/lib/tesla.rb +139 -0
- data/sig/manifest.yaml +10 -0
- data/sig/tesla.rbs +989 -0
- metadata +134 -118
- data/.document +0 -5
- data/.rvmrc +0 -1
- data/Gemfile +0 -13
- data/Gemfile.lock +0 -20
- data/README.rdoc +0 -19
- data/Rakefile +0 -47
- data/test/helper.rb +0 -19
- data/test/test_tesla.rb +0 -7
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
module API
|
|
5
|
+
# The climate commands, which control the temperature of the cabin and the seats of a vehicle
|
|
6
|
+
# @api public
|
|
7
|
+
module ClimateEndpoints
|
|
8
|
+
# The seats of a vehicle, by the number the Fleet API knows each by
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
SEATS = {front_left: 0, front_right: 1, rear_left: 2, rear_left_back: 3, rear_center: 4, rear_right: 5,
|
|
12
|
+
rear_right_back: 6, third_row_left: 7, third_row_right: 8}.freeze
|
|
13
|
+
|
|
14
|
+
# The Climate Keeper modes, by the number the Fleet API knows each by
|
|
15
|
+
#
|
|
16
|
+
# Dog Mode and Camp Mode keep the climate control on after the driver has left the vehicle.
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
CLIMATE_KEEPER_MODES = {off: 0, on: 1, dog: 2, camp: 3}.freeze
|
|
20
|
+
|
|
21
|
+
# Turn on the climate control of a vehicle
|
|
22
|
+
#
|
|
23
|
+
# The cabin is heated or cooled to the temperature it is set to (see {#set_temperature}).
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
# @authenticated true
|
|
27
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
28
|
+
# @return [true] once the vehicle has carried out the command
|
|
29
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
30
|
+
# @example
|
|
31
|
+
# Tesla.start_climate "5YJSA11111111111"
|
|
32
|
+
def start_climate(vehicle)
|
|
33
|
+
command(vehicle, "auto_conditioning_start")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Turn off the climate control of a vehicle
|
|
37
|
+
#
|
|
38
|
+
# @api public
|
|
39
|
+
# @authenticated true
|
|
40
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
41
|
+
# @return [true] once the vehicle has carried out the command
|
|
42
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
43
|
+
# @example
|
|
44
|
+
# Tesla.stop_climate "5YJSA11111111111"
|
|
45
|
+
def stop_climate(vehicle)
|
|
46
|
+
command(vehicle, "auto_conditioning_stop")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Set the temperature the climate control of a vehicle heats or cools the cabin to
|
|
50
|
+
#
|
|
51
|
+
# The temperatures are in degrees Celsius, whatever unit the vehicle displays. The climate control is not turned
|
|
52
|
+
# on (see {#start_climate}).
|
|
53
|
+
#
|
|
54
|
+
# @api public
|
|
55
|
+
# @authenticated true
|
|
56
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
57
|
+
# @param driver [Numeric] The temperature of the driver's side in degrees Celsius.
|
|
58
|
+
# @param passenger [Numeric] The temperature of the passenger's side in degrees Celsius, which defaults to
|
|
59
|
+
# the driver's.
|
|
60
|
+
# @return [true] once the vehicle has carried out the command
|
|
61
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
62
|
+
# @example
|
|
63
|
+
# Tesla.set_temperature "5YJSA11111111111", 21.5
|
|
64
|
+
# @example
|
|
65
|
+
# Tesla.set_temperature "5YJSA11111111111", 21.5, 19
|
|
66
|
+
def set_temperature(vehicle, driver, passenger = driver)
|
|
67
|
+
command(vehicle, "set_temps", driver_temp: driver, passenger_temp: passenger)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Turn the maximum defrost of a vehicle on or off
|
|
71
|
+
#
|
|
72
|
+
# @api public
|
|
73
|
+
# @authenticated true
|
|
74
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
75
|
+
# @param on [Boolean] Whether to turn it on.
|
|
76
|
+
# @return [true] once the vehicle has carried out the command
|
|
77
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
78
|
+
# @example
|
|
79
|
+
# Tesla.set_max_defrost "5YJSA11111111111", on: true
|
|
80
|
+
def set_max_defrost(vehicle, on:)
|
|
81
|
+
command(vehicle, "set_preconditioning_max", on:)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Set the heater of a seat of a vehicle
|
|
85
|
+
#
|
|
86
|
+
# @api public
|
|
87
|
+
# @authenticated true
|
|
88
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
89
|
+
# @param seat [Symbol, String] The seat, which is one of the seats {SEATS} names.
|
|
90
|
+
# @param level [Integer] The level of the heater, from 0, which is off, to 3.
|
|
91
|
+
# @return [true] once the vehicle has carried out the command
|
|
92
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
93
|
+
# @raise [ArgumentError] if the seat is not one the API defines
|
|
94
|
+
# @example
|
|
95
|
+
# Tesla.set_seat_heater "5YJSA11111111111", :front_left, 3
|
|
96
|
+
def set_seat_heater(vehicle, seat, level)
|
|
97
|
+
command(vehicle, "remote_seat_heater_request", seat_position: number_of(SEATS, seat, "seat"), level:)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Set the cooler of a seat of a vehicle
|
|
101
|
+
#
|
|
102
|
+
# @api public
|
|
103
|
+
# @authenticated true
|
|
104
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
105
|
+
# @param seat [Symbol, String] The seat, which is one of the seats {SEATS} names.
|
|
106
|
+
# @param level [Integer] The level of the cooler, from 0, which is off, to 3.
|
|
107
|
+
# @return [true] once the vehicle has carried out the command
|
|
108
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
109
|
+
# @raise [ArgumentError] if the seat is not one the API defines
|
|
110
|
+
# @example
|
|
111
|
+
# Tesla.set_seat_cooler "5YJSA11111111111", :front_left, 3
|
|
112
|
+
def set_seat_cooler(vehicle, seat, level)
|
|
113
|
+
command(vehicle, "remote_seat_cooler_request", seat_position: number_of(SEATS, seat, "seat"), seat_cooler_level: level)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Turn the heater of the steering wheel of a vehicle on or off
|
|
117
|
+
#
|
|
118
|
+
# @api public
|
|
119
|
+
# @authenticated true
|
|
120
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
121
|
+
# @param on [Boolean] Whether to turn it on.
|
|
122
|
+
# @return [true] once the vehicle has carried out the command
|
|
123
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
124
|
+
# @example
|
|
125
|
+
# Tesla.set_steering_wheel_heater "5YJSA11111111111", on: true
|
|
126
|
+
def set_steering_wheel_heater(vehicle, on:)
|
|
127
|
+
command(vehicle, "remote_steering_wheel_heater_request", on:)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Set the Climate Keeper mode of a vehicle
|
|
131
|
+
#
|
|
132
|
+
# @api public
|
|
133
|
+
# @authenticated true
|
|
134
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
135
|
+
# @param mode [Symbol, String] The mode, which is one of the modes {CLIMATE_KEEPER_MODES} names.
|
|
136
|
+
# @return [true] once the vehicle has carried out the command
|
|
137
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
138
|
+
# @raise [ArgumentError] if the mode is not one the API defines
|
|
139
|
+
# @example
|
|
140
|
+
# Tesla.set_climate_keeper_mode "5YJSA11111111111", :dog
|
|
141
|
+
def set_climate_keeper_mode(vehicle, mode)
|
|
142
|
+
mode = number_of(CLIMATE_KEEPER_MODES, mode, "climate keeper mode")
|
|
143
|
+
command(vehicle, "set_climate_keeper_mode", climate_keeper_mode: mode)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Turn the Bioweapon Defense Mode of a vehicle on or off
|
|
147
|
+
#
|
|
148
|
+
# @api public
|
|
149
|
+
# @authenticated true
|
|
150
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
151
|
+
# @param on [Boolean] Whether to turn it on.
|
|
152
|
+
# @param manual_override [Boolean] Whether to turn it on although the vehicle would not, such as with a low
|
|
153
|
+
# battery.
|
|
154
|
+
# @return [true] once the vehicle has carried out the command
|
|
155
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
156
|
+
# @example
|
|
157
|
+
# Tesla.set_bioweapon_mode "5YJSA11111111111", on: true
|
|
158
|
+
def set_bioweapon_mode(vehicle, on:, manual_override: false)
|
|
159
|
+
command(vehicle, "set_bioweapon_mode", on:, manual_override:)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Turn the Cabin Overheat Protection of a vehicle on or off
|
|
163
|
+
#
|
|
164
|
+
# @api public
|
|
165
|
+
# @authenticated true
|
|
166
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
167
|
+
# @param on [Boolean] Whether to turn it on.
|
|
168
|
+
# @param fan_only [Boolean] Whether to cool the cabin with the fan alone, without the air conditioning.
|
|
169
|
+
# @return [true] once the vehicle has carried out the command
|
|
170
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
171
|
+
# @example
|
|
172
|
+
# Tesla.set_cabin_overheat_protection "5YJSA11111111111", on: true
|
|
173
|
+
# @example
|
|
174
|
+
# Tesla.set_cabin_overheat_protection "5YJSA11111111111", on: true, fan_only: true
|
|
175
|
+
def set_cabin_overheat_protection(vehicle, on:, fan_only: false)
|
|
176
|
+
command(vehicle, "set_cabin_overheat_protection", on:, fan_only:)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
module API
|
|
5
|
+
# The closure commands, which lock, open, and close the doors, trunks, windows, and sunroof of a vehicle
|
|
6
|
+
# @api public
|
|
7
|
+
module ClosureEndpoints
|
|
8
|
+
# Lock the doors of a vehicle
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
# @authenticated true
|
|
12
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
13
|
+
# @return [true] once the vehicle has carried out the command
|
|
14
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
15
|
+
# @example
|
|
16
|
+
# Tesla.lock_doors "5YJSA11111111111"
|
|
17
|
+
def lock_doors(vehicle)
|
|
18
|
+
command(vehicle, "door_lock")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Unlock the doors of a vehicle
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
# @authenticated true
|
|
25
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
26
|
+
# @return [true] once the vehicle has carried out the command
|
|
27
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
28
|
+
# @example
|
|
29
|
+
# Tesla.unlock_doors "5YJSA11111111111"
|
|
30
|
+
def unlock_doors(vehicle)
|
|
31
|
+
command(vehicle, "door_unlock")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Open the rear trunk of a vehicle, or close it
|
|
35
|
+
#
|
|
36
|
+
# A vehicle with a powered liftgate closes a trunk that is open, so this is the command that pops the trunk and
|
|
37
|
+
# the one that closes it again. A trunk without one is opened and stays open.
|
|
38
|
+
#
|
|
39
|
+
# @api public
|
|
40
|
+
# @authenticated true
|
|
41
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
42
|
+
# @return [true] once the vehicle has carried out the command
|
|
43
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
44
|
+
# @example
|
|
45
|
+
# Tesla.actuate_trunk "5YJSA11111111111"
|
|
46
|
+
def actuate_trunk(vehicle)
|
|
47
|
+
command(vehicle, "actuate_trunk", which_trunk: "rear")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Open the front trunk of a vehicle
|
|
51
|
+
#
|
|
52
|
+
# The front trunk is closed by hand on the vehicles that do not close it themselves.
|
|
53
|
+
#
|
|
54
|
+
# @api public
|
|
55
|
+
# @authenticated true
|
|
56
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
57
|
+
# @return [true] once the vehicle has carried out the command
|
|
58
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
59
|
+
# @example
|
|
60
|
+
# Tesla.open_frunk "5YJSA11111111111"
|
|
61
|
+
def open_frunk(vehicle)
|
|
62
|
+
command(vehicle, "actuate_trunk", which_trunk: "front")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Vent the windows of a vehicle
|
|
66
|
+
#
|
|
67
|
+
# @api public
|
|
68
|
+
# @authenticated true
|
|
69
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
70
|
+
# @return [true] once the vehicle has carried out the command
|
|
71
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
72
|
+
# @example
|
|
73
|
+
# Tesla.vent_windows "5YJSA11111111111"
|
|
74
|
+
def vent_windows(vehicle)
|
|
75
|
+
command(vehicle, "window_control", command: "vent", lat: 0, lon: 0)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Close the windows of a vehicle
|
|
79
|
+
#
|
|
80
|
+
# A vehicle that takes the command over the Fleet API rather than through the vehicle command proxy closes its
|
|
81
|
+
# windows only for a user who is near it, so the position of the user is sent along.
|
|
82
|
+
#
|
|
83
|
+
# @api public
|
|
84
|
+
# @authenticated true
|
|
85
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
86
|
+
# @param latitude [Numeric] The latitude of the user.
|
|
87
|
+
# @param longitude [Numeric] The longitude of the user.
|
|
88
|
+
# @return [true] once the vehicle has carried out the command
|
|
89
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
90
|
+
# @example
|
|
91
|
+
# Tesla.close_windows "5YJSA11111111111"
|
|
92
|
+
# @example
|
|
93
|
+
# Tesla.close_windows "5YJSA11111111111", latitude: 37.4929, longitude: -121.9453
|
|
94
|
+
def close_windows(vehicle, latitude: 0, longitude: 0)
|
|
95
|
+
command(vehicle, "window_control", command: "close", lat: latitude, lon: longitude)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Vent the sunroof of a vehicle
|
|
99
|
+
#
|
|
100
|
+
# @api public
|
|
101
|
+
# @authenticated true
|
|
102
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
103
|
+
# @return [true] once the vehicle has carried out the command
|
|
104
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
105
|
+
# @example
|
|
106
|
+
# Tesla.vent_sunroof "5YJSA11111111111"
|
|
107
|
+
def vent_sunroof(vehicle)
|
|
108
|
+
command(vehicle, "sun_roof_control", state: "vent")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Close the sunroof of a vehicle
|
|
112
|
+
#
|
|
113
|
+
# @api public
|
|
114
|
+
# @authenticated true
|
|
115
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
116
|
+
# @return [true] once the vehicle has carried out the command
|
|
117
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
118
|
+
# @example
|
|
119
|
+
# Tesla.close_sunroof "5YJSA11111111111"
|
|
120
|
+
def close_sunroof(vehicle)
|
|
121
|
+
command(vehicle, "sun_roof_control", state: "close")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Open or close the garage door a vehicle is paired with over HomeLink
|
|
125
|
+
#
|
|
126
|
+
# The vehicle triggers HomeLink only for a user who is near it, so the position of the user is sent along.
|
|
127
|
+
#
|
|
128
|
+
# @api public
|
|
129
|
+
# @authenticated true
|
|
130
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
131
|
+
# @param latitude [Numeric] The latitude of the user.
|
|
132
|
+
# @param longitude [Numeric] The longitude of the user.
|
|
133
|
+
# @return [true] once the vehicle has carried out the command
|
|
134
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
135
|
+
# @example
|
|
136
|
+
# Tesla.trigger_homelink "5YJSA11111111111", latitude: 37.4929, longitude: -121.9453
|
|
137
|
+
def trigger_homelink(vehicle, latitude:, longitude:)
|
|
138
|
+
command(vehicle, "trigger_homelink", lat: latitude, lon: longitude)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../errors/command_failed"
|
|
4
|
+
require_relative "../identifiers"
|
|
5
|
+
require_relative "../json_parsing"
|
|
6
|
+
require_relative "../path_escaping"
|
|
7
|
+
|
|
8
|
+
module Tesla
|
|
9
|
+
module API
|
|
10
|
+
# The command endpoint, which every command of the other mixins is sent through
|
|
11
|
+
#
|
|
12
|
+
# A vehicle has to be online to take a command (see {VehicleEndpoints#wake_up}), and most vehicles take only
|
|
13
|
+
# commands signed with the Tesla Vehicle Command Protocol, which the vehicle command proxy does: a client whose
|
|
14
|
+
# `host` is the proxy sends its commands through it.
|
|
15
|
+
#
|
|
16
|
+
# @api public
|
|
17
|
+
module CommandEndpoints
|
|
18
|
+
include Identifiers
|
|
19
|
+
include JSONParsing
|
|
20
|
+
include PathEscaping
|
|
21
|
+
|
|
22
|
+
# Send a command to a vehicle
|
|
23
|
+
#
|
|
24
|
+
# The commands of the other mixins are sent with this, which also sends the commands the library has no
|
|
25
|
+
# method for.
|
|
26
|
+
#
|
|
27
|
+
# @api public
|
|
28
|
+
# @authenticated true
|
|
29
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
30
|
+
# @param name [String, Symbol] The name of the command, as the Fleet API names it.
|
|
31
|
+
# @param params [Hash] The parameters of the command.
|
|
32
|
+
# @return [true] once the vehicle has carried out the command
|
|
33
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
34
|
+
# @raise [RequestTimeout] if the vehicle is asleep or offline
|
|
35
|
+
# @raise [InvalidResponse] if the response does not say whether the vehicle carried out the command
|
|
36
|
+
# @example
|
|
37
|
+
# Tesla.command "5YJSA11111111111", :set_vehicle_name, vehicle_name: "Nikola 2.0"
|
|
38
|
+
def command(vehicle, name, **params)
|
|
39
|
+
body = post("/api/1/vehicles/#{escape(tag_of(vehicle))}/command/#{escape(name)}", params)
|
|
40
|
+
result, reason = parse_response(body) { |response| [response.fetch("result"), response["reason"]] }
|
|
41
|
+
raise CommandFailed.new(command: name.to_s, reason:) unless result
|
|
42
|
+
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Look up the number the Fleet API knows a named option by
|
|
49
|
+
#
|
|
50
|
+
# @api private
|
|
51
|
+
# @param options [Hash{Symbol => Integer}] the numbers of the options, by name
|
|
52
|
+
# @param name [Symbol, String] the name of the option
|
|
53
|
+
# @param kind [String] what the option is, which the message names
|
|
54
|
+
# @return [Integer] the number
|
|
55
|
+
# @raise [ArgumentError] if the name is not one of the options
|
|
56
|
+
def number_of(options, name, kind)
|
|
57
|
+
options.fetch(name.to_sym) do
|
|
58
|
+
raise ArgumentError, "Unknown #{kind}: #{name}. The #{kind}s the API defines are: #{options.keys.join(", ")}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
module API
|
|
5
|
+
# The media commands, which control what a vehicle plays
|
|
6
|
+
# @api public
|
|
7
|
+
module MediaEndpoints
|
|
8
|
+
# Play or pause the media of a vehicle
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
# @authenticated true
|
|
12
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
13
|
+
# @return [true] once the vehicle has carried out the command
|
|
14
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
15
|
+
# @example
|
|
16
|
+
# Tesla.toggle_playback "5YJSA11111111111"
|
|
17
|
+
def toggle_playback(vehicle)
|
|
18
|
+
command(vehicle, "media_toggle_playback")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Skip to the next track of the media of a vehicle
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
# @authenticated true
|
|
25
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
26
|
+
# @return [true] once the vehicle has carried out the command
|
|
27
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
28
|
+
# @example
|
|
29
|
+
# Tesla.next_track "5YJSA11111111111"
|
|
30
|
+
def next_track(vehicle)
|
|
31
|
+
command(vehicle, "media_next_track")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Skip to the previous track of the media of a vehicle
|
|
35
|
+
#
|
|
36
|
+
# @api public
|
|
37
|
+
# @authenticated true
|
|
38
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
39
|
+
# @return [true] once the vehicle has carried out the command
|
|
40
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
41
|
+
# @example
|
|
42
|
+
# Tesla.previous_track "5YJSA11111111111"
|
|
43
|
+
def previous_track(vehicle)
|
|
44
|
+
command(vehicle, "media_prev_track")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Skip to the next favorite of the media of a vehicle
|
|
48
|
+
#
|
|
49
|
+
# @api public
|
|
50
|
+
# @authenticated true
|
|
51
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
52
|
+
# @return [true] once the vehicle has carried out the command
|
|
53
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
54
|
+
# @example
|
|
55
|
+
# Tesla.next_favorite "5YJSA11111111111"
|
|
56
|
+
def next_favorite(vehicle)
|
|
57
|
+
command(vehicle, "media_next_fav")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Skip to the previous favorite of the media of a vehicle
|
|
61
|
+
#
|
|
62
|
+
# @api public
|
|
63
|
+
# @authenticated true
|
|
64
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
65
|
+
# @return [true] once the vehicle has carried out the command
|
|
66
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
67
|
+
# @example
|
|
68
|
+
# Tesla.previous_favorite "5YJSA11111111111"
|
|
69
|
+
def previous_favorite(vehicle)
|
|
70
|
+
command(vehicle, "media_prev_fav")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Turn up the volume of the media of a vehicle
|
|
74
|
+
#
|
|
75
|
+
# @api public
|
|
76
|
+
# @authenticated true
|
|
77
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
78
|
+
# @return [true] once the vehicle has carried out the command
|
|
79
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
80
|
+
# @example
|
|
81
|
+
# Tesla.volume_up "5YJSA11111111111"
|
|
82
|
+
def volume_up(vehicle)
|
|
83
|
+
command(vehicle, "media_volume_up")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Turn down the volume of the media of a vehicle
|
|
87
|
+
#
|
|
88
|
+
# @api public
|
|
89
|
+
# @authenticated true
|
|
90
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
91
|
+
# @return [true] once the vehicle has carried out the command
|
|
92
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
93
|
+
# @example
|
|
94
|
+
# Tesla.volume_down "5YJSA11111111111"
|
|
95
|
+
def volume_down(vehicle)
|
|
96
|
+
command(vehicle, "media_volume_down")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Set the volume of the media of a vehicle
|
|
100
|
+
#
|
|
101
|
+
# @api public
|
|
102
|
+
# @authenticated true
|
|
103
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
104
|
+
# @param volume [Numeric] The volume, from 0 to 11.
|
|
105
|
+
# @return [true] once the vehicle has carried out the command
|
|
106
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
107
|
+
# @example
|
|
108
|
+
# Tesla.set_volume "5YJSA11111111111", 4.5
|
|
109
|
+
def set_volume(vehicle, volume)
|
|
110
|
+
command(vehicle, "adjust_volume", volume:)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
module API
|
|
5
|
+
# The navigation commands, which send a destination to a vehicle
|
|
6
|
+
# @api public
|
|
7
|
+
module NavigationEndpoints
|
|
8
|
+
# The type of a navigation request, which is the one the Tesla app shares a destination with
|
|
9
|
+
NAVIGATION_REQUEST_TYPE = "share_ext_content_raw"
|
|
10
|
+
# The key of the destination of a navigation request
|
|
11
|
+
NAVIGATION_REQUEST_TEXT = "android.intent.extra.TEXT"
|
|
12
|
+
private_constant :NAVIGATION_REQUEST_TYPE, :NAVIGATION_REQUEST_TEXT
|
|
13
|
+
|
|
14
|
+
# Send a destination to the navigation of a vehicle
|
|
15
|
+
#
|
|
16
|
+
# The vehicle searches for the destination as it would for one typed into its navigation, so an address and
|
|
17
|
+
# the name of a place both work.
|
|
18
|
+
#
|
|
19
|
+
# @api public
|
|
20
|
+
# @authenticated true
|
|
21
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
22
|
+
# @param destination [String] The address or the name of the place to navigate to.
|
|
23
|
+
# @param locale [String] The locale the destination is written in.
|
|
24
|
+
# @return [true] once the vehicle has carried out the command
|
|
25
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
26
|
+
# @example
|
|
27
|
+
# Tesla.navigate_to "5YJSA11111111111", "3500 Deer Creek Road, Palo Alto, CA"
|
|
28
|
+
def navigate_to(vehicle, destination, locale: "en-US")
|
|
29
|
+
command(vehicle, "navigation_request", type: NAVIGATION_REQUEST_TYPE, locale:,
|
|
30
|
+
timestamp_ms: Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond),
|
|
31
|
+
value: {NAVIGATION_REQUEST_TEXT => destination})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Send a position to the navigation of a vehicle
|
|
35
|
+
#
|
|
36
|
+
# @api public
|
|
37
|
+
# @authenticated true
|
|
38
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
39
|
+
# @param latitude [Numeric] The latitude of the destination.
|
|
40
|
+
# @param longitude [Numeric] The longitude of the destination.
|
|
41
|
+
# @param order [Integer] The place of the destination among the ones the vehicle is navigating to.
|
|
42
|
+
# @return [true] once the vehicle has carried out the command
|
|
43
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
44
|
+
# @example
|
|
45
|
+
# Tesla.navigate_to_coordinates "5YJSA11111111111", 37.3947, -122.1503
|
|
46
|
+
def navigate_to_coordinates(vehicle, latitude, longitude, order: 1)
|
|
47
|
+
command(vehicle, "navigation_gps_request", lat: latitude, lon: longitude, order:)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
module API
|
|
5
|
+
# The OAuth 2.0 endpoints of Tesla's authorization server, which issue the tokens requests are authorized with
|
|
6
|
+
#
|
|
7
|
+
# An application is created at developer.tesla.com, which issues its client ID and client secret, and is
|
|
8
|
+
# registered in a region once (see {PartnerEndpoints#register_partner}). A user is then sent to
|
|
9
|
+
# {#authorization_url}, and comes back to the redirect URI with a code that {#exchange_code} exchanges for an
|
|
10
|
+
# access token and a refresh token. The access token lasts eight hours, and the client asks for another with
|
|
11
|
+
# the refresh token when it runs out (see {#refresh_access_token}).
|
|
12
|
+
#
|
|
13
|
+
# The requests are built by the simple_oauth gem, and the tokens are its `SimpleOAuth::OAuth2::Token`.
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
16
|
+
module OAuthEndpoints
|
|
17
|
+
# The scopes a user is asked for by default, which are the ones the endpoints of the library need
|
|
18
|
+
#
|
|
19
|
+
# @api public
|
|
20
|
+
SCOPES = %w[openid offline_access user_data vehicle_device_data vehicle_location vehicle_cmds
|
|
21
|
+
vehicle_charging_cmds].freeze
|
|
22
|
+
|
|
23
|
+
# The scopes a partner token is asked for by default
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
PARTNER_SCOPES = %w[openid vehicle_device_data vehicle_cmds vehicle_charging_cmds].freeze
|
|
27
|
+
|
|
28
|
+
# The URL a user is sent to in order to authorize the application
|
|
29
|
+
#
|
|
30
|
+
# The state is a value of your own that the authorization server hands back with the code, to be compared
|
|
31
|
+
# with the one sent (see `SimpleOAuth::OAuth2::AuthorizationResponse.parse`).
|
|
32
|
+
#
|
|
33
|
+
# @api public
|
|
34
|
+
# @authenticated false
|
|
35
|
+
# @param state [String] A random value that ties the response of the authorization server to this request.
|
|
36
|
+
# @param pkce [SimpleOAuth::OAuth2::PKCE, nil] A PKCE challenge, whose verifier is given to {#exchange_code}.
|
|
37
|
+
# @param scope [Array<String>, String] The scopes to ask the user for.
|
|
38
|
+
# @param params [Hash] Other parameters of the authorization request, such as `prompt_missing_scopes`.
|
|
39
|
+
# @return [String] the URL
|
|
40
|
+
# @raise [ArgumentError] if the client has no client ID, or the state is empty
|
|
41
|
+
# @example
|
|
42
|
+
# Tesla.authorization_url(state: SecureRandom.hex)
|
|
43
|
+
def authorization_url(state:, pkce: nil, scope: SCOPES, **params)
|
|
44
|
+
oauth_client.authorization_url(redirect_uri: required_redirect_uri, pkce:, state:, scope:, params:)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Exchange an authorization code for an access token and a refresh token
|
|
48
|
+
#
|
|
49
|
+
# The code is the one the authorization server sent the user back to the redirect URI with.
|
|
50
|
+
#
|
|
51
|
+
# The client keeps the tokens, and authorizes its requests with them from then on.
|
|
52
|
+
#
|
|
53
|
+
# @api public
|
|
54
|
+
# @authenticated false
|
|
55
|
+
# @param code [String] The code the authorization server sent the user back to the redirect URI with.
|
|
56
|
+
# @param code_verifier [String, nil] The verifier of the PKCE challenge the authorization URL was built with.
|
|
57
|
+
# @return [SimpleOAuth::OAuth2::Token] the token
|
|
58
|
+
# @raise [OAuthError] if the token endpoint turns the request away
|
|
59
|
+
# @raise [ArgumentError] if the client has no client ID or no redirect URI
|
|
60
|
+
# @example
|
|
61
|
+
# token = Tesla.exchange_code(params[:code])
|
|
62
|
+
# token.refresh_token
|
|
63
|
+
def exchange_code(code, code_verifier: nil)
|
|
64
|
+
request = oauth_client.authorization_code_request(code:, redirect_uri: required_redirect_uri, code_verifier:,
|
|
65
|
+
params: {audience:})
|
|
66
|
+
token = request_token(request)
|
|
67
|
+
exclusively { store_token(token) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Ask for a new access token with the refresh token
|
|
71
|
+
#
|
|
72
|
+
# The client does this itself when the Fleet API answers that its access token is no longer good, so this is
|
|
73
|
+
# for refreshing ahead of time. The client keeps the tokens it is answered with, the refresh token among
|
|
74
|
+
# them, since a refresh token is used once.
|
|
75
|
+
#
|
|
76
|
+
# @api public
|
|
77
|
+
# @authenticated false
|
|
78
|
+
# @return [SimpleOAuth::OAuth2::Token] the token
|
|
79
|
+
# @raise [OAuthError] if the token endpoint turns the request away
|
|
80
|
+
# @raise [ArgumentError] if the client has no client ID or no refresh token
|
|
81
|
+
# @example
|
|
82
|
+
# token = Tesla.refresh_access_token
|
|
83
|
+
# token.expires_at
|
|
84
|
+
def refresh_access_token
|
|
85
|
+
exclusively { refresh }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Ask for a partner token
|
|
89
|
+
#
|
|
90
|
+
# A partner token authorizes the endpoints that act for the application rather than for a user.
|
|
91
|
+
#
|
|
92
|
+
# The client does not keep the token, since its own requests are authorized by a user.
|
|
93
|
+
#
|
|
94
|
+
# @api public
|
|
95
|
+
# @authenticated false
|
|
96
|
+
# @param scope [Array<String>, String] The scopes to ask for.
|
|
97
|
+
# @return [SimpleOAuth::OAuth2::Token] the token
|
|
98
|
+
# @raise [OAuthError] if the token endpoint turns the request away
|
|
99
|
+
# @raise [ArgumentError] if the client has no client ID or no client secret
|
|
100
|
+
# @example
|
|
101
|
+
# Tesla.partner_token.access_token
|
|
102
|
+
def partner_token(scope: PARTNER_SCOPES)
|
|
103
|
+
request_token(oauth_client.client_credentials_request(scope:, params: {audience:}))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# The redirect URI of the client, which has to have one
|
|
109
|
+
#
|
|
110
|
+
# @api private
|
|
111
|
+
# @return [String] the redirect URI
|
|
112
|
+
# @raise [ArgumentError] if the client has no redirect URI
|
|
113
|
+
def required_redirect_uri
|
|
114
|
+
redirect_uri || raise(ArgumentError, "A redirect_uri is required to authorize a user")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|