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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/CHANGELOG.md +31 -0
  4. data/CONTRIBUTING.md +45 -0
  5. data/{LICENSE.txt → LICENSE.md} +1 -1
  6. data/README.md +366 -0
  7. data/SECURITY.md +38 -0
  8. data/lib/tesla/api/alert_endpoints.rb +35 -0
  9. data/lib/tesla/api/charging_endpoints.rb +115 -0
  10. data/lib/tesla/api/climate_endpoints.rb +180 -0
  11. data/lib/tesla/api/closure_endpoints.rb +142 -0
  12. data/lib/tesla/api/command_endpoints.rb +63 -0
  13. data/lib/tesla/api/media_endpoints.rb +114 -0
  14. data/lib/tesla/api/navigation_endpoints.rb +51 -0
  15. data/lib/tesla/api/oauth_endpoints.rb +118 -0
  16. data/lib/tesla/api/partner_endpoints.rb +62 -0
  17. data/lib/tesla/api/security_endpoints.rb +123 -0
  18. data/lib/tesla/api/software_endpoints.rb +38 -0
  19. data/lib/tesla/api/user_endpoints.rb +40 -0
  20. data/lib/tesla/api/vehicle_endpoints.rb +174 -0
  21. data/lib/tesla/api.rb +39 -0
  22. data/lib/tesla/authenticator.rb +33 -0
  23. data/lib/tesla/bearer_token_authenticator.rb +38 -0
  24. data/lib/tesla/certificate_options.rb +51 -0
  25. data/lib/tesla/charge_state.rb +111 -0
  26. data/lib/tesla/charging_site.rb +67 -0
  27. data/lib/tesla/client.rb +336 -0
  28. data/lib/tesla/client_credentials.rb +230 -0
  29. data/lib/tesla/climate_state.rb +116 -0
  30. data/lib/tesla/configuration.rb +483 -0
  31. data/lib/tesla/connection.rb +361 -0
  32. data/lib/tesla/connection_pool.rb +186 -0
  33. data/lib/tesla/deep_copy.rb +41 -0
  34. data/lib/tesla/drive_state.rb +80 -0
  35. data/lib/tesla/errors/bad_gateway.rb +9 -0
  36. data/lib/tesla/errors/bad_request.rb +9 -0
  37. data/lib/tesla/errors/client_error.rb +9 -0
  38. data/lib/tesla/errors/command_failed.rb +42 -0
  39. data/lib/tesla/errors/device_unexpected_response.rb +12 -0
  40. data/lib/tesla/errors/error.rb +7 -0
  41. data/lib/tesla/errors/forbidden.rb +14 -0
  42. data/lib/tesla/errors/gateway_timeout.rb +9 -0
  43. data/lib/tesla/errors/http_error.rb +89 -0
  44. data/lib/tesla/errors/internal_server_error.rb +9 -0
  45. data/lib/tesla/errors/invalid_response.rb +33 -0
  46. data/lib/tesla/errors/locked.rb +12 -0
  47. data/lib/tesla/errors/method_not_allowed.rb +9 -0
  48. data/lib/tesla/errors/misdirected_request.rb +13 -0
  49. data/lib/tesla/errors/network_error.rb +30 -0
  50. data/lib/tesla/errors/not_acceptable.rb +9 -0
  51. data/lib/tesla/errors/not_found.rb +9 -0
  52. data/lib/tesla/errors/oauth_error.rb +53 -0
  53. data/lib/tesla/errors/payment_required.rb +12 -0
  54. data/lib/tesla/errors/precondition_failed.rb +13 -0
  55. data/lib/tesla/errors/request_timeout.rb +13 -0
  56. data/lib/tesla/errors/server_error.rb +9 -0
  57. data/lib/tesla/errors/service_unavailable.rb +9 -0
  58. data/lib/tesla/errors/too_many_redirects.rb +9 -0
  59. data/lib/tesla/errors/too_many_requests.rb +9 -0
  60. data/lib/tesla/errors/unauthorized.rb +9 -0
  61. data/lib/tesla/errors/unavailable_for_legal_reasons.rb +12 -0
  62. data/lib/tesla/errors/unprocessable_content.rb +12 -0
  63. data/lib/tesla/errors/vehicle_asleep.rb +32 -0
  64. data/lib/tesla/gui_settings.rb +60 -0
  65. data/lib/tesla/idempotence.rb +30 -0
  66. data/lib/tesla/identifiers.rb +27 -0
  67. data/lib/tesla/json_parsing.rb +38 -0
  68. data/lib/tesla/nearby_charging_sites.rb +35 -0
  69. data/lib/tesla/path_escaping.rb +30 -0
  70. data/lib/tesla/redacted_output.rb +161 -0
  71. data/lib/tesla/redirect_handler.rb +196 -0
  72. data/lib/tesla/region.rb +27 -0
  73. data/lib/tesla/request_builder.rb +138 -0
  74. data/lib/tesla/resource.rb +403 -0
  75. data/lib/tesla/response_parser.rb +92 -0
  76. data/lib/tesla/retry_after.rb +34 -0
  77. data/lib/tesla/retry_handler.rb +252 -0
  78. data/lib/tesla/settings.rb +115 -0
  79. data/lib/tesla/url_validation.rb +131 -0
  80. data/lib/tesla/user.rb +36 -0
  81. data/lib/tesla/vehicle.rb +90 -0
  82. data/lib/tesla/vehicle_config.rb +95 -0
  83. data/lib/tesla/vehicle_data.rb +67 -0
  84. data/lib/tesla/vehicle_state.rb +125 -0
  85. data/lib/tesla/version.rb +6 -0
  86. data/lib/tesla.rb +139 -0
  87. data/sig/manifest.yaml +10 -0
  88. data/sig/tesla.rbs +989 -0
  89. metadata +134 -118
  90. data/.document +0 -5
  91. data/.rvmrc +0 -1
  92. data/Gemfile +0 -13
  93. data/Gemfile.lock +0 -20
  94. data/README.rdoc +0 -19
  95. data/Rakefile +0 -47
  96. data/test/helper.rb +0 -19
  97. 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