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,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "resource"
|
|
4
|
+
|
|
5
|
+
module Tesla
|
|
6
|
+
# The state of the battery and the charging of a vehicle
|
|
7
|
+
# @api public
|
|
8
|
+
class ChargeState < Resource
|
|
9
|
+
inspect_with :battery_level, :charging_state
|
|
10
|
+
|
|
11
|
+
# @!method battery_level
|
|
12
|
+
# The charge of the battery, as a percentage
|
|
13
|
+
# @api public
|
|
14
|
+
# @return [Integer, nil] the charge of the battery
|
|
15
|
+
# @example
|
|
16
|
+
# charge_state.battery_level
|
|
17
|
+
attribute :battery_level
|
|
18
|
+
|
|
19
|
+
# @!method usable_battery_level
|
|
20
|
+
# The charge of the battery that can be used, as a percentage
|
|
21
|
+
#
|
|
22
|
+
# It is lower than the charge of the battery when the battery is cold.
|
|
23
|
+
#
|
|
24
|
+
# @api public
|
|
25
|
+
# @return [Integer, nil] the charge of the battery that can be used
|
|
26
|
+
# @example
|
|
27
|
+
# charge_state.usable_battery_level
|
|
28
|
+
attribute :usable_battery_level
|
|
29
|
+
|
|
30
|
+
# @!method battery_range
|
|
31
|
+
# The rated range of the battery in miles, whatever unit the vehicle displays
|
|
32
|
+
# @api public
|
|
33
|
+
# @return [Float, nil] the rated range of the battery
|
|
34
|
+
# @example
|
|
35
|
+
# charge_state.battery_range
|
|
36
|
+
attribute :battery_range
|
|
37
|
+
|
|
38
|
+
# @!method charge_limit
|
|
39
|
+
# The charge the vehicle stops charging at, as a percentage
|
|
40
|
+
# @api public
|
|
41
|
+
# @return [Integer, nil] the charge the vehicle stops charging at
|
|
42
|
+
# @example
|
|
43
|
+
# charge_state.charge_limit
|
|
44
|
+
attribute :charge_limit, :charge_limit_soc
|
|
45
|
+
|
|
46
|
+
# @!method charging_state
|
|
47
|
+
# The state of charging, such as "Charging" or "Disconnected"
|
|
48
|
+
# @api public
|
|
49
|
+
# @return [String, nil] the state of charging
|
|
50
|
+
# @example
|
|
51
|
+
# charge_state.charging_state
|
|
52
|
+
attribute :charging_state
|
|
53
|
+
|
|
54
|
+
# @!method charge_amps
|
|
55
|
+
# The current the vehicle asks the charger for, in amps
|
|
56
|
+
# @api public
|
|
57
|
+
# @return [Integer, nil] the current the vehicle asks the charger for
|
|
58
|
+
# @example
|
|
59
|
+
# charge_state.charge_amps
|
|
60
|
+
attribute :charge_amps
|
|
61
|
+
|
|
62
|
+
# @!method charge_rate
|
|
63
|
+
# The rate of charging in miles of range per hour
|
|
64
|
+
# @api public
|
|
65
|
+
# @return [Float, nil] the rate of charging
|
|
66
|
+
# @example
|
|
67
|
+
# charge_state.charge_rate
|
|
68
|
+
attribute :charge_rate
|
|
69
|
+
|
|
70
|
+
# @!method charger_power
|
|
71
|
+
# The power of the charger in kilowatts
|
|
72
|
+
# @api public
|
|
73
|
+
# @return [Integer, nil] the power of the charger
|
|
74
|
+
# @example
|
|
75
|
+
# charge_state.charger_power
|
|
76
|
+
attribute :charger_power
|
|
77
|
+
|
|
78
|
+
# @!method minutes_to_full_charge
|
|
79
|
+
# The minutes until the vehicle reaches its charge limit
|
|
80
|
+
# @api public
|
|
81
|
+
# @return [Integer, nil] the minutes until the vehicle reaches its charge limit
|
|
82
|
+
# @example
|
|
83
|
+
# charge_state.minutes_to_full_charge
|
|
84
|
+
attribute :minutes_to_full_charge
|
|
85
|
+
|
|
86
|
+
# @!method charge_port_latch
|
|
87
|
+
# The state of the latch of the charge port, such as "Engaged"
|
|
88
|
+
# @api public
|
|
89
|
+
# @return [String, nil] the state of the latch of the charge port
|
|
90
|
+
# @example
|
|
91
|
+
# charge_state.charge_port_latch
|
|
92
|
+
attribute :charge_port_latch
|
|
93
|
+
|
|
94
|
+
# @!method charge_port_door_open?
|
|
95
|
+
# Whether the door of the charge port is open
|
|
96
|
+
# @api public
|
|
97
|
+
# @return [Boolean] whether the door of the charge port is open
|
|
98
|
+
# @example
|
|
99
|
+
# charge_state.charge_port_door_open?
|
|
100
|
+
predicate :charge_port_door_open
|
|
101
|
+
|
|
102
|
+
# @!method timestamp
|
|
103
|
+
# The time the vehicle reported the state
|
|
104
|
+
# @api public
|
|
105
|
+
# @return [Time, nil] the time the vehicle reported the state
|
|
106
|
+
# @raise [InvalidResponse] if the timestamp is not a number of milliseconds
|
|
107
|
+
# @example
|
|
108
|
+
# charge_state.timestamp
|
|
109
|
+
time_attribute :timestamp
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "resource"
|
|
4
|
+
|
|
5
|
+
module Tesla
|
|
6
|
+
# A charging site near a vehicle
|
|
7
|
+
# @api public
|
|
8
|
+
class ChargingSite < Resource
|
|
9
|
+
inspect_with :name, :type
|
|
10
|
+
|
|
11
|
+
# @!method name
|
|
12
|
+
# The name of the site
|
|
13
|
+
# @api public
|
|
14
|
+
# @return [String, nil] the name of the site
|
|
15
|
+
# @example
|
|
16
|
+
# charging_site.name
|
|
17
|
+
attribute :name
|
|
18
|
+
|
|
19
|
+
# @!method type
|
|
20
|
+
# The type of the site, "supercharger" or "destination"
|
|
21
|
+
# @api public
|
|
22
|
+
# @return [String, nil] the type of the site
|
|
23
|
+
# @example
|
|
24
|
+
# charging_site.type
|
|
25
|
+
attribute :type
|
|
26
|
+
|
|
27
|
+
# @!method distance_miles
|
|
28
|
+
# The distance from the vehicle to the site in miles
|
|
29
|
+
# @api public
|
|
30
|
+
# @return [Float, nil] the distance from the vehicle to the site
|
|
31
|
+
# @example
|
|
32
|
+
# charging_site.distance_miles
|
|
33
|
+
attribute :distance_miles
|
|
34
|
+
|
|
35
|
+
# @!method available_stalls
|
|
36
|
+
# The number of stalls that are free, which a Supercharger reports
|
|
37
|
+
# @api public
|
|
38
|
+
# @return [Integer, nil] the number of stalls that are free
|
|
39
|
+
# @example
|
|
40
|
+
# charging_site.available_stalls
|
|
41
|
+
attribute :available_stalls
|
|
42
|
+
|
|
43
|
+
# @!method total_stalls
|
|
44
|
+
# The number of stalls the site has, which a Supercharger reports
|
|
45
|
+
# @api public
|
|
46
|
+
# @return [Integer, nil] the number of stalls the site has
|
|
47
|
+
# @example
|
|
48
|
+
# charging_site.total_stalls
|
|
49
|
+
attribute :total_stalls
|
|
50
|
+
|
|
51
|
+
# @!method location
|
|
52
|
+
# The position of the site, as its "lat" and "long"
|
|
53
|
+
# @api public
|
|
54
|
+
# @return [Hash{String => Float}, nil] the position of the site
|
|
55
|
+
# @example
|
|
56
|
+
# charging_site.location
|
|
57
|
+
attribute :location
|
|
58
|
+
|
|
59
|
+
# @!method site_closed?
|
|
60
|
+
# Whether the site is closed
|
|
61
|
+
# @api public
|
|
62
|
+
# @return [Boolean] whether the site is closed
|
|
63
|
+
# @example
|
|
64
|
+
# charging_site.site_closed?
|
|
65
|
+
predicate :site_closed
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/tesla/client.rb
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
require "uri"
|
|
5
|
+
require_relative "api"
|
|
6
|
+
require_relative "client_credentials"
|
|
7
|
+
require_relative "configuration"
|
|
8
|
+
require_relative "connection"
|
|
9
|
+
require_relative "idempotence"
|
|
10
|
+
require_relative "redirect_handler"
|
|
11
|
+
require_relative "request_builder"
|
|
12
|
+
require_relative "response_parser"
|
|
13
|
+
require_relative "retry_handler"
|
|
14
|
+
require_relative "url_validation"
|
|
15
|
+
|
|
16
|
+
module Tesla
|
|
17
|
+
# A client for the Tesla Fleet API
|
|
18
|
+
# @api public
|
|
19
|
+
class Client
|
|
20
|
+
extend Forwardable
|
|
21
|
+
include API
|
|
22
|
+
include ClientCredentials
|
|
23
|
+
include Idempotence
|
|
24
|
+
include URLValidation
|
|
25
|
+
|
|
26
|
+
# The status the Fleet API answers a request with when its access token is missing, expired, or revoked
|
|
27
|
+
UNAUTHORIZED_STATUS = "401"
|
|
28
|
+
private_constant :UNAUTHORIZED_STATUS
|
|
29
|
+
|
|
30
|
+
# The host for API requests
|
|
31
|
+
# @api public
|
|
32
|
+
# @return [String] the host for API requests, including scheme
|
|
33
|
+
# @example Get the host
|
|
34
|
+
# client.host
|
|
35
|
+
attr_reader :host
|
|
36
|
+
|
|
37
|
+
def_delegators :@connection, :open_timeout, :read_timeout, :write_timeout, :keep_alive_timeout, :proxy_url, :debug_output
|
|
38
|
+
def_delegators :@connection, :open_timeout=, :read_timeout=, :write_timeout=, :keep_alive_timeout=, :proxy_url=, :debug_output=
|
|
39
|
+
def_delegators :@connection, :ca_file, :ca_file=
|
|
40
|
+
def_delegators :@redirect_handler, :max_redirects, :max_redirects=
|
|
41
|
+
def_delegators :@retry_handler, :max_retries, :max_retry_delay
|
|
42
|
+
def_delegators :@retry_handler, :max_retries=, :max_retry_delay=
|
|
43
|
+
def_delegators :@request_builder, :user_agent
|
|
44
|
+
def_delegators :@request_builder, :user_agent=
|
|
45
|
+
|
|
46
|
+
# The methods of Forwardable, which the class delegates with rather than offers, so that `Client.delegate` and
|
|
47
|
+
# the rest are not mistaken for methods of the API
|
|
48
|
+
private_class_method(*Forwardable.instance_methods)
|
|
49
|
+
|
|
50
|
+
# Build a client, and close it once a block is done with it
|
|
51
|
+
#
|
|
52
|
+
# Without a block the client is returned, as it is from any other constructor. With one, the client is given to
|
|
53
|
+
# the block and its connections are closed once the block returns or raises, as `Net::HTTP.start` closes the
|
|
54
|
+
# connection it opened, and what the block returns is returned. The client can still be used afterwards, since
|
|
55
|
+
# a closed client opens its connections again as it needs them.
|
|
56
|
+
#
|
|
57
|
+
# The options are handed to {#initialize}, which is what declares them, so they are collected here and named
|
|
58
|
+
# there.
|
|
59
|
+
#
|
|
60
|
+
# @api public
|
|
61
|
+
# @param options [Hash] the options of {#initialize}
|
|
62
|
+
# @yield [client] the client, which is closed once the block is done with it
|
|
63
|
+
# @return [Client, Object] the client, or what the block returned
|
|
64
|
+
# @example Close the connections of a client once a series of requests is done
|
|
65
|
+
# vehicles = Tesla::Client.new(access_token: "eyJhbGciOiJSUzI1NiIs") do |client|
|
|
66
|
+
# client.vehicles
|
|
67
|
+
# end
|
|
68
|
+
def self.new(**options) # steep:ignore DifferentMethodParameterKind
|
|
69
|
+
client = super
|
|
70
|
+
|
|
71
|
+
return client unless block_given?
|
|
72
|
+
|
|
73
|
+
begin
|
|
74
|
+
yield client
|
|
75
|
+
ensure
|
|
76
|
+
client.close
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Initialize a new Tesla Fleet API client
|
|
81
|
+
#
|
|
82
|
+
# Every option defaults to the global configuration (see {Tesla.configure}).
|
|
83
|
+
#
|
|
84
|
+
# A client with an access token alone sends it until it expires. One that also has a refresh token and a client
|
|
85
|
+
# ID asks for a new access token when the Fleet API answers that the one it sent is no longer good, and one
|
|
86
|
+
# with those and no access token asks for one before its first request.
|
|
87
|
+
#
|
|
88
|
+
# @api public
|
|
89
|
+
# @param host [String] the host for API requests, including scheme
|
|
90
|
+
# @param access_token [String, nil] the access token requests are authorized with
|
|
91
|
+
# @param refresh_token [String, nil] the refresh token a new access token is asked for with
|
|
92
|
+
# @param client_id [String, nil] the client ID of the application
|
|
93
|
+
# @param client_secret [String, nil] the client secret of the application
|
|
94
|
+
# @param redirect_uri [String, nil] the URL the authorization server sends a user back to
|
|
95
|
+
# @param audience [String, nil] the host of the Fleet API tokens are issued for, or nil for the host
|
|
96
|
+
# @param authorization_endpoint [String] the URL a user is sent to in order to authorize the application
|
|
97
|
+
# @param token_endpoint [String] the URL tokens are issued and refreshed at
|
|
98
|
+
# @param on_token_refresh [#call, nil] what is called with the token each time the access token is refreshed
|
|
99
|
+
# @param user_agent [String] the 'User-Agent' HTTP header sent with requests
|
|
100
|
+
# @param open_timeout [Numeric] the timeout for opening connections in seconds
|
|
101
|
+
# @param read_timeout [Numeric] the timeout for reading responses in seconds
|
|
102
|
+
# @param write_timeout [Numeric] the timeout for writing requests in seconds
|
|
103
|
+
# @param debug_output [IO, nil] the IO object for debug output
|
|
104
|
+
# @param proxy_url [String, nil] the proxy URL for requests
|
|
105
|
+
# @param keep_alive_timeout [Numeric] the seconds an idle connection is kept open for another request
|
|
106
|
+
# @param max_redirects [Integer] the maximum number of redirects to follow
|
|
107
|
+
# @param ca_file [String, nil] the path of a file of certificates TLS is verified with
|
|
108
|
+
# @param max_retries [Integer] the number of times a request that was turned away is sent again
|
|
109
|
+
# @param max_retry_delay [Numeric] the longest a request waits before it is sent again, in seconds
|
|
110
|
+
# @return [Client] a new client instance
|
|
111
|
+
# @example Create a client with an access token
|
|
112
|
+
# client = Tesla::Client.new(access_token: "eyJhbGciOiJSUzI1NiIs")
|
|
113
|
+
# @example Create a client that refreshes its access token
|
|
114
|
+
# client = Tesla::Client.new(client_id: "81527cff06843c8634fdc09e8ac0abef",
|
|
115
|
+
# refresh_token: "NA_9d0b4bfd0c4c3a6e")
|
|
116
|
+
# @example Create a client that sends requests through the vehicle command proxy
|
|
117
|
+
# client = Tesla::Client.new(host: "https://localhost:4443", ca_file: "config/tls-cert.pem",
|
|
118
|
+
# audience: Tesla::Configuration::NORTH_AMERICA_HOST)
|
|
119
|
+
# @raise [ArgumentError] if the host is not an HTTP or HTTPS URL, or the certificate path names nothing
|
|
120
|
+
def initialize(host: Tesla.host, access_token: Tesla.access_token, refresh_token: Tesla.refresh_token,
|
|
121
|
+
client_id: Tesla.client_id, client_secret: Tesla.client_secret, redirect_uri: Tesla.redirect_uri,
|
|
122
|
+
audience: Tesla.audience, authorization_endpoint: Tesla.authorization_endpoint, token_endpoint: Tesla.token_endpoint,
|
|
123
|
+
on_token_refresh: Tesla.on_token_refresh,
|
|
124
|
+
user_agent: Tesla.user_agent,
|
|
125
|
+
open_timeout: Tesla.open_timeout,
|
|
126
|
+
read_timeout: Tesla.read_timeout,
|
|
127
|
+
write_timeout: Tesla.write_timeout,
|
|
128
|
+
debug_output: Tesla.debug_output,
|
|
129
|
+
proxy_url: Tesla.proxy_url,
|
|
130
|
+
keep_alive_timeout: Tesla.keep_alive_timeout,
|
|
131
|
+
max_redirects: Tesla.max_redirects,
|
|
132
|
+
ca_file: Tesla.ca_file,
|
|
133
|
+
max_retries: Tesla.max_retries,
|
|
134
|
+
max_retry_delay: Tesla.max_retry_delay)
|
|
135
|
+
@host = validate_host(host)
|
|
136
|
+
@connection = Connection.new(open_timeout:, read_timeout:, write_timeout:, debug_output:, proxy_url:,
|
|
137
|
+
keep_alive_timeout:, ca_file:)
|
|
138
|
+
@request_builder = RequestBuilder.new(user_agent:)
|
|
139
|
+
@redirect_handler = RedirectHandler.new(connection: @connection, request_builder: @request_builder, max_redirects:)
|
|
140
|
+
@retry_handler = RetryHandler.new(max_retries:, max_retry_delay:)
|
|
141
|
+
@response_parser = ResponseParser.new
|
|
142
|
+
initialize_credentials(access_token:, refresh_token:, client_id:, client_secret:, redirect_uri:, audience:,
|
|
143
|
+
authorization_endpoint:, token_endpoint:, on_token_refresh:)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Set the host for API requests
|
|
147
|
+
#
|
|
148
|
+
# @api public
|
|
149
|
+
# @param host [String] the host for API requests, including scheme
|
|
150
|
+
# @return [void]
|
|
151
|
+
# @raise [ArgumentError] if the host is not an HTTP or HTTPS URL, in which case the host is left as it was
|
|
152
|
+
# @example Set the host
|
|
153
|
+
# client.host = Tesla::Configuration::EUROPE_HOST
|
|
154
|
+
def host=(host)
|
|
155
|
+
@host = validate_host(host)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Summarize the client for the console
|
|
159
|
+
#
|
|
160
|
+
# @api public
|
|
161
|
+
# @return [String] the summary, which includes the host but never credentials
|
|
162
|
+
# @example Inspect a client
|
|
163
|
+
# client.inspect # => #<Tesla::Client host="https://fleet-api.prd.na.vn.cloud.tesla.com">
|
|
164
|
+
def inspect
|
|
165
|
+
"#<#{self.class} host=#{host.inspect}>"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Close the connections the client keeps open for the next request
|
|
169
|
+
#
|
|
170
|
+
# The connections are opened again as they are needed, so requests can still be made afterwards.
|
|
171
|
+
#
|
|
172
|
+
# @api public
|
|
173
|
+
# @return [Client] the client
|
|
174
|
+
# @example Close the connections a client keeps open
|
|
175
|
+
# client.close
|
|
176
|
+
def close
|
|
177
|
+
@connection.close
|
|
178
|
+
self
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Perform a GET request to the Tesla Fleet API
|
|
182
|
+
#
|
|
183
|
+
# @api public
|
|
184
|
+
# @param path [String] the request path
|
|
185
|
+
# @param params [Hash] the query parameters
|
|
186
|
+
# @param headers [Hash{String => String}] the headers to send with the request, which replace the ones the
|
|
187
|
+
# client would send, the 'Authorization' header of its access token among them
|
|
188
|
+
# @return [String] the response body
|
|
189
|
+
# @raise [HTTPError] if the response is not successful
|
|
190
|
+
# @raise [ArgumentError] if the path is a URL of an origin other than the host
|
|
191
|
+
# @example Get the vehicles of the account
|
|
192
|
+
# client.get("/api/1/vehicles")
|
|
193
|
+
# @example Send a header of your own
|
|
194
|
+
# client.get("/api/1/vehicles", headers: {"Accept" => "application/json"})
|
|
195
|
+
def get(path, params = {}, headers: {})
|
|
196
|
+
execute_request(:get, path, params:, headers:)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Perform a DELETE request to the Tesla Fleet API
|
|
200
|
+
#
|
|
201
|
+
# @api public
|
|
202
|
+
# @param path [String] the request path
|
|
203
|
+
# @param params [Hash] the query parameters
|
|
204
|
+
# @param headers [Hash{String => String}] the headers to send with the request, which replace the ones the
|
|
205
|
+
# client would send, the 'Authorization' header of its access token among them
|
|
206
|
+
# @return [String] the response body
|
|
207
|
+
# @raise [HTTPError] if the response is not successful
|
|
208
|
+
# @raise [ArgumentError] if the path is a URL of an origin other than the host
|
|
209
|
+
# @example Remove a driver from a vehicle
|
|
210
|
+
# client.delete("/api/1/vehicles/5YJSA11111111111/drivers", {share_user_id: 800001})
|
|
211
|
+
def delete(path, params = {}, headers: {})
|
|
212
|
+
execute_request(:delete, path, params:, headers:)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Perform a POST request to the Tesla Fleet API
|
|
216
|
+
#
|
|
217
|
+
# @api public
|
|
218
|
+
# @param path [String] the request path
|
|
219
|
+
# @param body [Hash, Array, String] the request body, which is sent as JSON unless it is a String
|
|
220
|
+
# @param headers [Hash{String => String}] the headers to send with the request, which replace the ones the
|
|
221
|
+
# client would send, the 'Authorization' header of its access token among them
|
|
222
|
+
# @return [String] the response body
|
|
223
|
+
# @raise [HTTPError] if the response is not successful
|
|
224
|
+
# @raise [ArgumentError] if the path is a URL of an origin other than the host
|
|
225
|
+
# @example Lock the doors of a vehicle
|
|
226
|
+
# client.post("/api/1/vehicles/5YJSA11111111111/command/door_lock")
|
|
227
|
+
def post(path, body = {}, headers: {})
|
|
228
|
+
execute_request(:post, path, body:, headers:)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Perform a PUT request to the Tesla Fleet API
|
|
232
|
+
#
|
|
233
|
+
# @api public
|
|
234
|
+
# @param path [String] the request path
|
|
235
|
+
# @param body [Hash, Array, String] the request body, which is sent as JSON unless it is a String
|
|
236
|
+
# @param headers [Hash{String => String}] the headers to send with the request, which replace the ones the
|
|
237
|
+
# client would send, the 'Authorization' header of its access token among them
|
|
238
|
+
# @return [String] the response body
|
|
239
|
+
# @raise [HTTPError] if the response is not successful
|
|
240
|
+
# @raise [ArgumentError] if the path is a URL of an origin other than the host
|
|
241
|
+
# @example Replace the fleet telemetry configuration of a vehicle
|
|
242
|
+
# client.put("/api/1/vehicles/fleet_telemetry_config", {vins: ["5YJSA11111111111"], config: {}})
|
|
243
|
+
def put(path, body = {}, headers: {})
|
|
244
|
+
execute_request(:put, path, body:, headers:)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Perform a PATCH request to the Tesla Fleet API
|
|
248
|
+
#
|
|
249
|
+
# @api public
|
|
250
|
+
# @param path [String] the request path
|
|
251
|
+
# @param body [Hash, Array, String] the request body, which is sent as JSON unless it is a String
|
|
252
|
+
# @param headers [Hash{String => String}] the headers to send with the request, which replace the ones the
|
|
253
|
+
# client would send, the 'Authorization' header of its access token among them
|
|
254
|
+
# @return [String] the response body
|
|
255
|
+
# @raise [HTTPError] if the response is not successful
|
|
256
|
+
# @raise [ArgumentError] if the path is a URL of an origin other than the host
|
|
257
|
+
# @example Update a resource
|
|
258
|
+
# client.patch("/api/1/vehicles/5YJSA11111111111", {display_name: "Nikola 2.0"})
|
|
259
|
+
def patch(path, body = {}, headers: {})
|
|
260
|
+
execute_request(:patch, path, body:, headers:)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
private
|
|
264
|
+
|
|
265
|
+
# The connection used for API requests
|
|
266
|
+
# @api private
|
|
267
|
+
# @return [Connection] the connection
|
|
268
|
+
attr_reader :connection
|
|
269
|
+
|
|
270
|
+
# The request builder used for API requests
|
|
271
|
+
# @api private
|
|
272
|
+
# @return [RequestBuilder] the request builder
|
|
273
|
+
attr_reader :request_builder
|
|
274
|
+
|
|
275
|
+
# The redirect handler the responses of API requests are followed with
|
|
276
|
+
# @api private
|
|
277
|
+
# @return [RedirectHandler] the redirect handler
|
|
278
|
+
attr_reader :redirect_handler
|
|
279
|
+
|
|
280
|
+
# The retry handler the requests the server turns away are sent again with
|
|
281
|
+
# @api private
|
|
282
|
+
# @return [RetryHandler] the retry handler
|
|
283
|
+
attr_reader :retry_handler
|
|
284
|
+
|
|
285
|
+
# Execute an HTTP request to the Tesla Fleet API
|
|
286
|
+
#
|
|
287
|
+
# A request answered with a 401 Unauthorized is sent once more with a new access token when the client has what
|
|
288
|
+
# refreshing one takes, since the Fleet API never acted on a request it did not authorize.
|
|
289
|
+
#
|
|
290
|
+
# @api private
|
|
291
|
+
# @param http_method [Symbol] the HTTP method
|
|
292
|
+
# @param path [String] the request path
|
|
293
|
+
# @param params [Hash] the query parameters
|
|
294
|
+
# @param body [Hash, Array, String, nil] the request body
|
|
295
|
+
# @param headers [Hash{String => String}] the headers to send with the request
|
|
296
|
+
# @return [String] the response body
|
|
297
|
+
def execute_request(http_method, path, headers:, params: {}, body: nil)
|
|
298
|
+
uri = build_uri(host, path)
|
|
299
|
+
attempt = ->(access_token) { perform(access_token:, http_method:, uri:, params:, body:, headers:) }
|
|
300
|
+
access_token = renewed_access_token(nil)
|
|
301
|
+
response = attempt.call(access_token)
|
|
302
|
+
if response.code.eql?(UNAUTHORIZED_STATUS) && refreshable?
|
|
303
|
+
response = attempt.call(renewed_access_token(access_token))
|
|
304
|
+
end
|
|
305
|
+
@response_parser.parse(response:)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Build a request, send it, and follow its redirects, again when it is turned away
|
|
309
|
+
#
|
|
310
|
+
# Each attempt builds a request of its own, so the retry handler is told whether the request is safe to send
|
|
311
|
+
# again rather than given one to read the method of. Only a safe request, which asks the server for something
|
|
312
|
+
# rather than asking a vehicle to do something, is sent again after a 502, 503, or 504, or after the network
|
|
313
|
+
# lost it, since the vehicle may have acted on it before the answer went missing. Any request is sent again for
|
|
314
|
+
# a 429, which a rate limiter answers before the request reaches the endpoint.
|
|
315
|
+
#
|
|
316
|
+
# A retry follows the redirects of the request again from the start, rather than sending it straight to where a
|
|
317
|
+
# redirect led the first time.
|
|
318
|
+
#
|
|
319
|
+
# @api private
|
|
320
|
+
# @param access_token [String, nil] the access token the request is authorized with
|
|
321
|
+
# @param http_method [Symbol] the HTTP method
|
|
322
|
+
# @param uri [URI::Generic] the request URI
|
|
323
|
+
# @param params [Hash] the query parameters
|
|
324
|
+
# @param body [Hash, Array, String, nil] the request body
|
|
325
|
+
# @param headers [Hash{String => String}] the headers to send with the request
|
|
326
|
+
# @return [Net::HTTPResponse] the response
|
|
327
|
+
def perform(access_token:, http_method:, uri:, params:, body:, headers:)
|
|
328
|
+
authenticator = authenticator_for(access_token)
|
|
329
|
+
@retry_handler.handle(retry_unanswered: safe?(http_method)) do
|
|
330
|
+
request = @request_builder.build(http_method:, uri:, params:, body:, headers:, authenticator:)
|
|
331
|
+
response = @connection.perform(request:)
|
|
332
|
+
@redirect_handler.handle(response:, request:, authenticator:, body:, headers:)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|