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,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../json_parsing"
|
|
4
|
+
|
|
5
|
+
module Tesla
|
|
6
|
+
module API
|
|
7
|
+
# The partner endpoints, which act for the application rather than for a user
|
|
8
|
+
#
|
|
9
|
+
# They are authorized with a partner token, which each asks the token endpoint for with the client ID and the
|
|
10
|
+
# client secret of the client (see {OAuthEndpoints#partner_token}).
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
13
|
+
module PartnerEndpoints
|
|
14
|
+
include JSONParsing
|
|
15
|
+
|
|
16
|
+
# Register the application in the region of the host
|
|
17
|
+
#
|
|
18
|
+
# An application is registered once in each region it is used in, before which the Fleet API of that region
|
|
19
|
+
# answers its requests with a {PreconditionFailed}. The domain is the one the application was created with
|
|
20
|
+
# at developer.tesla.com, and has to serve the public key of the application at
|
|
21
|
+
# `/.well-known/appspecific/com.tesla.3p.public-key.pem`.
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
# @authenticated true
|
|
25
|
+
# @param domain [String] The domain of the application, without a scheme.
|
|
26
|
+
# @return [Hash{String => Object}] the account the application is registered as
|
|
27
|
+
# @raise [OAuthError] if the token endpoint turns the request for a partner token away
|
|
28
|
+
# @example
|
|
29
|
+
# Tesla.register_partner "example.com"
|
|
30
|
+
def register_partner(domain)
|
|
31
|
+
parse_response(post("/api/1/partner_accounts", {domain:}, headers: partner_headers))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# View the public key the application is registered with in the region of the host
|
|
35
|
+
#
|
|
36
|
+
# @api public
|
|
37
|
+
# @authenticated true
|
|
38
|
+
# @param domain [String] The domain of the application, without a scheme.
|
|
39
|
+
# @return [String] the public key, as a hexadecimal string
|
|
40
|
+
# @raise [OAuthError] if the token endpoint turns the request for a partner token away
|
|
41
|
+
# @raise [InvalidResponse] if the response carries no public key
|
|
42
|
+
# @example
|
|
43
|
+
# Tesla.partner_public_key "example.com"
|
|
44
|
+
def partner_public_key(domain)
|
|
45
|
+
body = get("/api/1/partner_accounts/public_key", {domain:}, headers: partner_headers)
|
|
46
|
+
parse_response(body) { |response| response.fetch("public_key") }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# The headers that authorize a request with a partner token
|
|
52
|
+
#
|
|
53
|
+
# They replace the header that carries the access token of the client.
|
|
54
|
+
#
|
|
55
|
+
# @api private
|
|
56
|
+
# @return [Hash{String => String}] the headers
|
|
57
|
+
def partner_headers
|
|
58
|
+
{"Authorization" => "Bearer #{partner_token.access_token}"}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
module API
|
|
5
|
+
# The security commands, which control who can drive a vehicle and how
|
|
6
|
+
# @api public
|
|
7
|
+
module SecurityEndpoints
|
|
8
|
+
# Turn the Sentry Mode of a vehicle on or off
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
# @authenticated true
|
|
12
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
13
|
+
# @param on [Boolean] Whether to turn it on.
|
|
14
|
+
# @return [true] once the vehicle has carried out the command
|
|
15
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
16
|
+
# @example
|
|
17
|
+
# Tesla.set_sentry_mode "5YJSA11111111111", on: true
|
|
18
|
+
def set_sentry_mode(vehicle, on:)
|
|
19
|
+
command(vehicle, "set_sentry_mode", on:)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Turn the Valet Mode of a vehicle on or off
|
|
23
|
+
#
|
|
24
|
+
# @api public
|
|
25
|
+
# @authenticated true
|
|
26
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
27
|
+
# @param on [Boolean] Whether to turn it on.
|
|
28
|
+
# @param password [String, nil] The four digit PIN that turns Valet Mode off again in the vehicle, or nil for
|
|
29
|
+
# none.
|
|
30
|
+
# @return [true] once the vehicle has carried out the command
|
|
31
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
32
|
+
# @example
|
|
33
|
+
# Tesla.set_valet_mode "5YJSA11111111111", on: true
|
|
34
|
+
# @example
|
|
35
|
+
# Tesla.set_valet_mode "5YJSA11111111111", on: true, password: "1234"
|
|
36
|
+
def set_valet_mode(vehicle, on:, password: nil)
|
|
37
|
+
command(vehicle, "set_valet_mode", **{on:, password:}.compact)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Clear the PIN that turns the Valet Mode of a vehicle off
|
|
41
|
+
#
|
|
42
|
+
# @api public
|
|
43
|
+
# @authenticated true
|
|
44
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
45
|
+
# @return [true] once the vehicle has carried out the command
|
|
46
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
47
|
+
# @example
|
|
48
|
+
# Tesla.reset_valet_pin "5YJSA11111111111"
|
|
49
|
+
def reset_valet_pin(vehicle)
|
|
50
|
+
command(vehicle, "reset_valet_pin")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Let a vehicle be driven without a key for the next two minutes
|
|
54
|
+
#
|
|
55
|
+
# @api public
|
|
56
|
+
# @authenticated true
|
|
57
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
58
|
+
# @return [true] once the vehicle has carried out the command
|
|
59
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
60
|
+
# @example
|
|
61
|
+
# Tesla.remote_start "5YJSA11111111111"
|
|
62
|
+
def remote_start(vehicle)
|
|
63
|
+
command(vehicle, "remote_start_drive")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Set the speed the Speed Limit Mode of a vehicle limits it to
|
|
67
|
+
#
|
|
68
|
+
# @api public
|
|
69
|
+
# @authenticated true
|
|
70
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
71
|
+
# @param mph [Numeric] The speed in miles per hour, from 50 to 120.
|
|
72
|
+
# @return [true] once the vehicle has carried out the command
|
|
73
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
74
|
+
# @example
|
|
75
|
+
# Tesla.set_speed_limit "5YJSA11111111111", 65
|
|
76
|
+
def set_speed_limit(vehicle, mph)
|
|
77
|
+
command(vehicle, "speed_limit_set_limit", limit_mph: mph)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Turn on the Speed Limit Mode of a vehicle
|
|
81
|
+
#
|
|
82
|
+
# @api public
|
|
83
|
+
# @authenticated true
|
|
84
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
85
|
+
# @param pin [String] The four digit PIN that turns Speed Limit Mode off again.
|
|
86
|
+
# @return [true] once the vehicle has carried out the command
|
|
87
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
88
|
+
# @example
|
|
89
|
+
# Tesla.activate_speed_limit "5YJSA11111111111", "1234"
|
|
90
|
+
def activate_speed_limit(vehicle, pin)
|
|
91
|
+
command(vehicle, "speed_limit_activate", pin:)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Turn off the Speed Limit Mode of a vehicle
|
|
95
|
+
#
|
|
96
|
+
# @api public
|
|
97
|
+
# @authenticated true
|
|
98
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
99
|
+
# @param pin [String] The four digit PIN Speed Limit Mode was turned on with.
|
|
100
|
+
# @return [true] once the vehicle has carried out the command
|
|
101
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
102
|
+
# @example
|
|
103
|
+
# Tesla.deactivate_speed_limit "5YJSA11111111111", "1234"
|
|
104
|
+
def deactivate_speed_limit(vehicle, pin)
|
|
105
|
+
command(vehicle, "speed_limit_deactivate", pin:)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Clear the PIN of the Speed Limit Mode of a vehicle
|
|
109
|
+
#
|
|
110
|
+
# @api public
|
|
111
|
+
# @authenticated true
|
|
112
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
113
|
+
# @param pin [String] The four digit PIN Speed Limit Mode was turned on with.
|
|
114
|
+
# @return [true] once the vehicle has carried out the command
|
|
115
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
116
|
+
# @example
|
|
117
|
+
# Tesla.clear_speed_limit_pin "5YJSA11111111111", "1234"
|
|
118
|
+
def clear_speed_limit_pin(vehicle, pin)
|
|
119
|
+
command(vehicle, "speed_limit_clear_pin", pin:)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
module API
|
|
5
|
+
# The software commands, which control the software updates of a vehicle
|
|
6
|
+
# @api public
|
|
7
|
+
module SoftwareEndpoints
|
|
8
|
+
# Install the software update a vehicle has downloaded
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
# @authenticated true
|
|
12
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
13
|
+
# @param offset [Integer] The seconds to wait before the update is installed, which is none by default.
|
|
14
|
+
# @return [true] once the vehicle has carried out the command
|
|
15
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
16
|
+
# @example
|
|
17
|
+
# Tesla.schedule_software_update "5YJSA11111111111"
|
|
18
|
+
# @example
|
|
19
|
+
# Tesla.schedule_software_update "5YJSA11111111111", offset: 7200
|
|
20
|
+
def schedule_software_update(vehicle, offset: 0)
|
|
21
|
+
command(vehicle, "schedule_software_update", offset_sec: offset)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Cancel the software update a vehicle is waiting to install
|
|
25
|
+
#
|
|
26
|
+
# @api public
|
|
27
|
+
# @authenticated true
|
|
28
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
29
|
+
# @return [true] once the vehicle has carried out the command
|
|
30
|
+
# @raise [CommandFailed] if the vehicle answers that it did not carry out the command
|
|
31
|
+
# @example
|
|
32
|
+
# Tesla.cancel_software_update "5YJSA11111111111"
|
|
33
|
+
def cancel_software_update(vehicle)
|
|
34
|
+
command(vehicle, "cancel_software_update")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../json_parsing"
|
|
4
|
+
require_relative "../region"
|
|
5
|
+
require_relative "../user"
|
|
6
|
+
|
|
7
|
+
module Tesla
|
|
8
|
+
module API
|
|
9
|
+
# The user endpoints, which describe the account an access token was issued for
|
|
10
|
+
# @api public
|
|
11
|
+
module UserEndpoints
|
|
12
|
+
include JSONParsing
|
|
13
|
+
|
|
14
|
+
# View the user the access token was issued for
|
|
15
|
+
#
|
|
16
|
+
# @api public
|
|
17
|
+
# @authenticated true
|
|
18
|
+
# @return [User]
|
|
19
|
+
# @example
|
|
20
|
+
# Tesla.me.email
|
|
21
|
+
def me
|
|
22
|
+
User.new(parse_response(get("/api/1/users/me")))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# View the region of the Fleet API the account is served by
|
|
26
|
+
#
|
|
27
|
+
# A request sent to the host of another region is answered with a {MisdirectedRequest}, so this is the
|
|
28
|
+
# endpoint that names the host to configure.
|
|
29
|
+
#
|
|
30
|
+
# @api public
|
|
31
|
+
# @authenticated true
|
|
32
|
+
# @return [Region]
|
|
33
|
+
# @example
|
|
34
|
+
# Tesla.host = Tesla.region.fleet_api_base_url
|
|
35
|
+
def region
|
|
36
|
+
Region.new(parse_response(get("/api/1/users/region")))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../errors/vehicle_asleep"
|
|
4
|
+
require_relative "../identifiers"
|
|
5
|
+
require_relative "../json_parsing"
|
|
6
|
+
require_relative "../nearby_charging_sites"
|
|
7
|
+
require_relative "../path_escaping"
|
|
8
|
+
require_relative "../vehicle"
|
|
9
|
+
require_relative "../vehicle_data"
|
|
10
|
+
|
|
11
|
+
module Tesla
|
|
12
|
+
module API
|
|
13
|
+
# The vehicle endpoints, which list the vehicles of an account, read their state, and wake them
|
|
14
|
+
# @api public
|
|
15
|
+
module VehicleEndpoints
|
|
16
|
+
include Identifiers
|
|
17
|
+
include JSONParsing
|
|
18
|
+
include PathEscaping
|
|
19
|
+
|
|
20
|
+
# The seconds between the requests that ask whether a vehicle being woken is online yet
|
|
21
|
+
DEFAULT_WAKE_INTERVAL = 2 # seconds
|
|
22
|
+
|
|
23
|
+
# List the vehicles of the account
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
# @authenticated true
|
|
27
|
+
# @param page [Integer, nil] The page of vehicles, from 1, or nil for the first.
|
|
28
|
+
# @param per_page [Integer, nil] The number of vehicles on a page, or nil for the default of the Fleet API.
|
|
29
|
+
# @return [Array<Vehicle>]
|
|
30
|
+
# @example
|
|
31
|
+
# Tesla.vehicles.map(&:display_name)
|
|
32
|
+
def vehicles(page: nil, per_page: nil)
|
|
33
|
+
Vehicle.list(parse_response(get("/api/1/vehicles", {page:, per_page:}.compact)))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# View a vehicle of the account
|
|
37
|
+
#
|
|
38
|
+
# The vehicle is answered with whatever state it is in, without waking it.
|
|
39
|
+
#
|
|
40
|
+
# @api public
|
|
41
|
+
# @authenticated true
|
|
42
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
43
|
+
# @return [Vehicle]
|
|
44
|
+
# @example
|
|
45
|
+
# Tesla.vehicle("5YJSA11111111111").state
|
|
46
|
+
def vehicle(vehicle)
|
|
47
|
+
Vehicle.new(parse_response(get(vehicle_path(vehicle))))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Read the state of a vehicle and its subsystems
|
|
51
|
+
#
|
|
52
|
+
# The vehicle has to be online to answer (see {#wake_up}).
|
|
53
|
+
#
|
|
54
|
+
# The Fleet API answers with the states it is asked for, and leaves the position of the vehicle out of the
|
|
55
|
+
# ones it answers with by default: it is asked for as "location_data", and answered for an access token with
|
|
56
|
+
# the vehicle_location scope.
|
|
57
|
+
#
|
|
58
|
+
# @api public
|
|
59
|
+
# @authenticated true
|
|
60
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
61
|
+
# @param endpoints [Array<String, Symbol>, String, Symbol, nil] The states to answer with, among
|
|
62
|
+
# "charge_state", "climate_state", "closures_state", "drive_state", "gui_settings", "location_data",
|
|
63
|
+
# "charge_schedule_data", "preconditioning_schedule_data", "vehicle_config", and "vehicle_state", or nil for
|
|
64
|
+
# the default of the Fleet API.
|
|
65
|
+
# @return [VehicleData]
|
|
66
|
+
# @raise [RequestTimeout] if the vehicle is asleep or offline
|
|
67
|
+
# @example
|
|
68
|
+
# Tesla.vehicle_data("5YJSA11111111111").charge_state.battery_level
|
|
69
|
+
# @example
|
|
70
|
+
# Tesla.vehicle_data("5YJSA11111111111", endpoints: %i[location_data drive_state]).drive_state.latitude
|
|
71
|
+
def vehicle_data(vehicle, endpoints: nil)
|
|
72
|
+
params = {endpoints: endpoints && [endpoints].join(";")}.compact
|
|
73
|
+
VehicleData.new(parse_response(get("#{vehicle_path(vehicle)}/vehicle_data", params)))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Wake a vehicle from sleep
|
|
77
|
+
#
|
|
78
|
+
# The Fleet API answers before the vehicle has woken, so the vehicle answered with is seldom online. With a
|
|
79
|
+
# timeout, the vehicle is asked for again every few seconds until it is online, and that is the vehicle
|
|
80
|
+
# answered with.
|
|
81
|
+
#
|
|
82
|
+
# @api public
|
|
83
|
+
# @authenticated true
|
|
84
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
85
|
+
# @param timeout [Numeric, nil] The seconds to wait for the vehicle to be online, or nil not to wait.
|
|
86
|
+
# @param interval [Numeric] The seconds between the requests that ask whether the vehicle is online yet.
|
|
87
|
+
# @return [Vehicle] the vehicle, which is online when a timeout was given
|
|
88
|
+
# @raise [VehicleAsleep] if the vehicle is not online once the timeout is over
|
|
89
|
+
# @example
|
|
90
|
+
# Tesla.wake_up "5YJSA11111111111"
|
|
91
|
+
# @example
|
|
92
|
+
# Tesla.wake_up "5YJSA11111111111", timeout: 60
|
|
93
|
+
def wake_up(vehicle, timeout: nil, interval: DEFAULT_WAKE_INTERVAL)
|
|
94
|
+
woken = Vehicle.new(parse_response(post("#{vehicle_path(vehicle)}/wake_up")))
|
|
95
|
+
timeout ? await_online(woken, monotonic_time + timeout, interval) : woken
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# List the charging sites near a vehicle
|
|
99
|
+
#
|
|
100
|
+
# The vehicle has to be online to answer (see {#wake_up}).
|
|
101
|
+
#
|
|
102
|
+
# @api public
|
|
103
|
+
# @authenticated true
|
|
104
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
105
|
+
# @return [NearbyChargingSites]
|
|
106
|
+
# @raise [RequestTimeout] if the vehicle is asleep or offline
|
|
107
|
+
# @example
|
|
108
|
+
# Tesla.nearby_charging_sites("5YJSA11111111111").superchargers.map(&:name)
|
|
109
|
+
def nearby_charging_sites(vehicle)
|
|
110
|
+
NearbyChargingSites.new(parse_response(get("#{vehicle_path(vehicle)}/nearby_charging_sites")))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Whether a vehicle allows mobile access
|
|
114
|
+
#
|
|
115
|
+
# A vehicle is commanded remotely only while mobile access is enabled in it.
|
|
116
|
+
#
|
|
117
|
+
# @api public
|
|
118
|
+
# @authenticated true
|
|
119
|
+
# @param vehicle [String, Integer, Vehicle] The VIN or ID of a vehicle, or a vehicle.
|
|
120
|
+
# @return [Boolean] whether mobile access is enabled in the vehicle
|
|
121
|
+
# @raise [RequestTimeout] if the vehicle is asleep or offline
|
|
122
|
+
# @example
|
|
123
|
+
# Tesla.mobile_enabled? "5YJSA11111111111"
|
|
124
|
+
def mobile_enabled?(vehicle)
|
|
125
|
+
parse_response(get("#{vehicle_path(vehicle)}/mobile_enabled")).eql?(true)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# The path of a vehicle
|
|
131
|
+
#
|
|
132
|
+
# @api private
|
|
133
|
+
# @param vehicle [String, Integer, Vehicle] the VIN or ID of a vehicle, or a vehicle
|
|
134
|
+
# @return [String] the path
|
|
135
|
+
def vehicle_path(vehicle)
|
|
136
|
+
"/api/1/vehicles/#{escape(tag_of(vehicle))}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Ask for a vehicle until it is online
|
|
140
|
+
#
|
|
141
|
+
# The vehicle is asked for rather than woken again, since one wake-up is enough to wake a vehicle that can be
|
|
142
|
+
# woken, and the Fleet API allows fewer of them.
|
|
143
|
+
#
|
|
144
|
+
# Kernel#sleep takes any Numeric, but its signature asks for the narrower interface Integer and Float happen to
|
|
145
|
+
# answer, so the seconds are passed to it unchecked rather than narrowing what the interval accepts.
|
|
146
|
+
#
|
|
147
|
+
# @api private
|
|
148
|
+
# @param woken [Vehicle] the vehicle, as the Fleet API last answered with it
|
|
149
|
+
# @param deadline [Numeric] the time, on the monotonic clock, the wait is over
|
|
150
|
+
# @param interval [Numeric] the seconds between the requests
|
|
151
|
+
# @return [Vehicle] the vehicle, once it is online
|
|
152
|
+
# @raise [VehicleAsleep] if the vehicle is not online once the wait is over
|
|
153
|
+
def await_online(woken, deadline, interval)
|
|
154
|
+
until woken.online?
|
|
155
|
+
raise VehicleAsleep.new(vehicle: woken) if monotonic_time >= deadline
|
|
156
|
+
|
|
157
|
+
sleep(interval) # steep:ignore UnresolvedOverloading
|
|
158
|
+
woken = vehicle(woken)
|
|
159
|
+
end
|
|
160
|
+
woken
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# The time on the monotonic clock
|
|
164
|
+
#
|
|
165
|
+
# A change to the time of the system does not move the monotonic clock, so a wait is measured with it.
|
|
166
|
+
#
|
|
167
|
+
# @api private
|
|
168
|
+
# @return [Float] the seconds since an arbitrary moment
|
|
169
|
+
def monotonic_time
|
|
170
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
data/lib/tesla/api.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "api/alert_endpoints"
|
|
4
|
+
require_relative "api/charging_endpoints"
|
|
5
|
+
require_relative "api/climate_endpoints"
|
|
6
|
+
require_relative "api/closure_endpoints"
|
|
7
|
+
require_relative "api/command_endpoints"
|
|
8
|
+
require_relative "api/media_endpoints"
|
|
9
|
+
require_relative "api/navigation_endpoints"
|
|
10
|
+
require_relative "api/oauth_endpoints"
|
|
11
|
+
require_relative "api/partner_endpoints"
|
|
12
|
+
require_relative "api/security_endpoints"
|
|
13
|
+
require_relative "api/software_endpoints"
|
|
14
|
+
require_relative "api/user_endpoints"
|
|
15
|
+
require_relative "api/vehicle_endpoints"
|
|
16
|
+
|
|
17
|
+
module Tesla
|
|
18
|
+
# The Tesla Fleet API endpoints, mixed into {Client}
|
|
19
|
+
#
|
|
20
|
+
# The endpoints are grouped into one mixin per topic. Every public method of those mixins is also available on
|
|
21
|
+
# the {Tesla} module, which delegates to {Tesla.client}.
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
module API
|
|
25
|
+
include AlertEndpoints
|
|
26
|
+
include ChargingEndpoints
|
|
27
|
+
include ClimateEndpoints
|
|
28
|
+
include ClosureEndpoints
|
|
29
|
+
include CommandEndpoints
|
|
30
|
+
include MediaEndpoints
|
|
31
|
+
include NavigationEndpoints
|
|
32
|
+
include OAuthEndpoints
|
|
33
|
+
include PartnerEndpoints
|
|
34
|
+
include SecurityEndpoints
|
|
35
|
+
include SoftwareEndpoints
|
|
36
|
+
include UserEndpoints
|
|
37
|
+
include VehicleEndpoints
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
# Base class for authentication (no authentication)
|
|
5
|
+
# @api private
|
|
6
|
+
class Authenticator
|
|
7
|
+
# The HTTP header name for authentication
|
|
8
|
+
AUTHENTICATION_HEADER = "Authorization"
|
|
9
|
+
private_constant :AUTHENTICATION_HEADER
|
|
10
|
+
|
|
11
|
+
# Generate the authentication headers for a request
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
# @param _request [Net::HTTPRequest] the HTTP request
|
|
15
|
+
# @return [Hash{String => String}] the authentication headers (empty)
|
|
16
|
+
# @example Generate empty authentication headers
|
|
17
|
+
# authenticator = Tesla::Authenticator.new
|
|
18
|
+
# authenticator.header(request)
|
|
19
|
+
def header(_request)
|
|
20
|
+
{}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Summarize the authenticator for the console
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
# @return [String] the summary, which never includes credentials
|
|
27
|
+
# @example Inspect an authenticator
|
|
28
|
+
# authenticator.inspect # => #<Tesla::Authenticator>
|
|
29
|
+
def inspect
|
|
30
|
+
"#<#{self.class}>"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "authenticator"
|
|
4
|
+
|
|
5
|
+
module Tesla
|
|
6
|
+
# Authenticator for OAuth 2.0 bearer token authentication
|
|
7
|
+
# @api private
|
|
8
|
+
class BearerTokenAuthenticator < Authenticator
|
|
9
|
+
# The access token
|
|
10
|
+
# @api private
|
|
11
|
+
# @return [String] the access token
|
|
12
|
+
# @example Get the access token
|
|
13
|
+
# authenticator.access_token
|
|
14
|
+
attr_reader :access_token
|
|
15
|
+
|
|
16
|
+
# Initialize a new BearerTokenAuthenticator
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
19
|
+
# @param access_token [String] the access token
|
|
20
|
+
# @return [BearerTokenAuthenticator] a new instance
|
|
21
|
+
# @example Create a bearer token authenticator
|
|
22
|
+
# authenticator = Tesla::BearerTokenAuthenticator.new(access_token: "eyJhbGciOiJSUzI1NiIs")
|
|
23
|
+
def initialize(access_token:)
|
|
24
|
+
@access_token = access_token
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Generate the authentication headers for a request
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
# @param _request [Net::HTTPRequest] the HTTP request
|
|
31
|
+
# @return [Hash{String => String}] the authentication headers with the access token
|
|
32
|
+
# @example Generate a bearer token authentication header
|
|
33
|
+
# authenticator.header(request)
|
|
34
|
+
def header(_request)
|
|
35
|
+
{AUTHENTICATION_HEADER => "Bearer #{access_token}"}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tesla
|
|
4
|
+
# The certificates TLS is verified with, mixed into the configuration and the connection
|
|
5
|
+
#
|
|
6
|
+
# A host whose certificate Ruby's OpenSSL does not already trust, such as the vehicle command proxy with a
|
|
7
|
+
# certificate of its own, is reached by naming that certificate here rather than by turning verification off:
|
|
8
|
+
# there is no option for that, so a request of this library is always verified.
|
|
9
|
+
#
|
|
10
|
+
# The path is checked where it is assigned, as a host and a proxy URL are (see {URLValidation}), so that a path
|
|
11
|
+
# that names nothing is reported as the assignment that was wrong instead of as the TLS failure of the next
|
|
12
|
+
# request. The value that is rejected is left as it was.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
module CertificateOptions
|
|
16
|
+
# The path of a file of certificates TLS is verified with
|
|
17
|
+
#
|
|
18
|
+
# The certificates in the file are trusted alongside the ones OpenSSL already trusts.
|
|
19
|
+
#
|
|
20
|
+
# @api private
|
|
21
|
+
# @return [String, nil] the path of the file, or nil to verify with the certificates OpenSSL trusts
|
|
22
|
+
# @example Get the CA file
|
|
23
|
+
# connection.ca_file
|
|
24
|
+
attr_reader :ca_file
|
|
25
|
+
|
|
26
|
+
# Set the path of a file of certificates TLS is verified with
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
# @param ca_file [String, nil] the path of the file, or nil to verify with the certificates OpenSSL trusts
|
|
30
|
+
# @return [void]
|
|
31
|
+
# @raise [ArgumentError] if the path does not name a file, in which case the path is left as it was
|
|
32
|
+
# @example Set the CA file
|
|
33
|
+
# connection.ca_file = "config/tls-cert.pem"
|
|
34
|
+
def ca_file=(ca_file)
|
|
35
|
+
@ca_file = ca_file && validate_ca_file(ca_file)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Check that a path names a file of certificates
|
|
41
|
+
# @api private
|
|
42
|
+
# @param ca_file [String] the path of the file
|
|
43
|
+
# @return [String] the path
|
|
44
|
+
# @raise [ArgumentError] if the path does not name a file
|
|
45
|
+
def validate_ca_file(ca_file)
|
|
46
|
+
raise ArgumentError, "Invalid CA file: #{ca_file}" unless File.file?(ca_file)
|
|
47
|
+
|
|
48
|
+
ca_file
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|