smartcar 2.1.0 → 3.0.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 +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +32 -0
- data/.travis.yml +1 -2
- data/Gemfile +4 -2
- data/Gemfile.lock +78 -20
- data/README.md +38 -36
- data/Rakefile +11 -3
- data/bin/console +4 -3
- data/lib/open_struct_extensions.rb +21 -0
- data/lib/smartcar.rb +173 -43
- data/lib/smartcar/auth_client.rb +154 -0
- data/lib/smartcar/base.rb +21 -29
- data/lib/smartcar/utils.rb +107 -35
- data/lib/smartcar/vehicle.rb +183 -252
- data/lib/smartcar/version.rb +3 -1
- data/lib/smartcar_error.rb +49 -0
- data/ruby-sdk.gemspec +27 -21
- metadata +71 -24
- data/lib/smartcar/battery.rb +0 -13
- data/lib/smartcar/battery_capacity.rb +0 -9
- data/lib/smartcar/charge.rb +0 -13
- data/lib/smartcar/engine_oil.rb +0 -12
- data/lib/smartcar/fuel.rb +0 -15
- data/lib/smartcar/location.rb +0 -10
- data/lib/smartcar/oauth.rb +0 -119
- data/lib/smartcar/odometer.rb +0 -9
- data/lib/smartcar/permissions.rb +0 -9
- data/lib/smartcar/tire_pressure.rb +0 -19
- data/lib/smartcar/user.rb +0 -26
- data/lib/smartcar/vehicle_attributes.rb +0 -12
- data/lib/smartcar/vin.rb +0 -10
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smartcar
|
4
|
+
# AuthClient class to take care of the Oauth 2.0 with Smartcar APIs
|
5
|
+
#
|
6
|
+
class AuthClient
|
7
|
+
include Smartcar::Utils
|
8
|
+
|
9
|
+
attr_reader :redirect_uri, :client_id, :client_secret, :scope, :mode, :flags, :origin
|
10
|
+
|
11
|
+
# Constructor for a client object
|
12
|
+
#
|
13
|
+
# @param [Hash] options
|
14
|
+
# @option options[:client_id] [String] - Client ID, if not passed fallsback to ENV['SMARTCAR_CLIENT_ID']
|
15
|
+
# @option options[:client_secret] [String] - Client Secret, if not passed fallsback to ENV['SMARTCAR_CLIENT_SECRET']
|
16
|
+
# @option options[:redirect_uri] [String] - Redirect URI, if not passed fallsback to ENV['SMARTCAR_REDIRECT_URI']
|
17
|
+
# @option options[:test_mode] [Boolean] - Setting this to 'true' runs it in test mode.
|
18
|
+
#
|
19
|
+
# @return [Smartcar::AuthClient] Returns a Smartcar::AuthClient Object that has other methods
|
20
|
+
def initialize(options)
|
21
|
+
options[:redirect_uri] ||= get_config('SMARTCAR_REDIRECT_URI')
|
22
|
+
options[:client_id] ||= get_config('SMARTCAR_CLIENT_ID')
|
23
|
+
options[:client_secret] ||= get_config('SMARTCAR_CLIENT_SECRET')
|
24
|
+
options[:mode] = options[:test_mode].is_a?(TrueClass) ? TEST : LIVE
|
25
|
+
options[:origin] = ENV['SMARTCAR_AUTH_ORIGIN'] || AUTH_ORIGIN
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generate the OAuth authorization URL.
|
30
|
+
# @param scope [Array<String>] Array of permissions that specify what the user can access
|
31
|
+
# EXAMPLE : ['read_odometer', 'read_vehicle_info', 'required:read_location']
|
32
|
+
# For further details refer to https://smartcar.com/docs/guides/scope/
|
33
|
+
# @param [Hash] options
|
34
|
+
# @option options[:force_prompt] [Boolean] - Setting `force_prompt` to
|
35
|
+
# `true` will show the permissions approval screen on every authentication
|
36
|
+
# attempt, even if the user has previously consented to the exact scope of
|
37
|
+
# permissions.
|
38
|
+
# @option options[:single_select] [Hash] - An optional object that sets the
|
39
|
+
# behavior of the grant dialog displayed to the user. Object can contain two keys :
|
40
|
+
# - enabled - Boolean value, if set to `true`,
|
41
|
+
# `single_select` limits the user to selecting only one vehicle.
|
42
|
+
# - vin - String vin, if set, Smartcar will only authorize the vehicle
|
43
|
+
# with the specified VIN.
|
44
|
+
# See the [Single Select guide](https://smartcar.com/docs/guides/single-select/) for more information.
|
45
|
+
# @option options[:state] [String] - OAuth state parameter passed to the
|
46
|
+
# redirect uri. This parameter may be used for identifying the user who
|
47
|
+
# initiated the request.
|
48
|
+
# @option options[:make_bypass] [String] - `make_bypass' is an optional parameter that allows
|
49
|
+
# users to bypass the car brand selection screen.
|
50
|
+
# For a complete list of supported makes, please see our
|
51
|
+
# [API Reference](https://smartcar.com/docs/api#authorization) documentation.
|
52
|
+
# @option options[:flags] [Hash] - A hash of flag name string as key and a string or boolean value.
|
53
|
+
#
|
54
|
+
# @return [String] Authorization URL string
|
55
|
+
def get_auth_url(scope, options = {})
|
56
|
+
initialize_auth_parameters(scope, options)
|
57
|
+
add_single_select_options(options[:single_select])
|
58
|
+
client.auth_code.authorize_url(@auth_parameters)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Generates the tokens hash using the code returned in oauth process.
|
62
|
+
# @param code [String] This is the code that is returned after user
|
63
|
+
# visits and authorizes on the authorization URL.
|
64
|
+
# @param [Hash] options
|
65
|
+
# @option options[:flags] [Hash] - A hash of flag name string as key and a string or boolean value.
|
66
|
+
#
|
67
|
+
# @return [Hash] Hash of token, refresh token, expiry info and token type
|
68
|
+
def exchange_code(code, options = {})
|
69
|
+
set_token_url(options[:flags])
|
70
|
+
|
71
|
+
token_hash = client.auth_code
|
72
|
+
.get_token(code, redirect_uri: redirect_uri)
|
73
|
+
.to_hash
|
74
|
+
|
75
|
+
JSON.parse(token_hash.to_json, object_class: OpenStruct)
|
76
|
+
rescue OAuth2::Error => e
|
77
|
+
raise build_error(e.response.status, e.response.body, e.response.headers)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Refreshing the access token
|
81
|
+
# @param token [String] refresh_token received during token exchange
|
82
|
+
# @param [Hash] options
|
83
|
+
# @option options[:flags] [Hash] - A hash of flag name string as key and a string or boolean value.
|
84
|
+
#
|
85
|
+
# @return [Hash] Hash of token, refresh token, expiry info and token type
|
86
|
+
def exchange_refresh_token(token, options = {})
|
87
|
+
set_token_url(options[:flags])
|
88
|
+
|
89
|
+
token_object = OAuth2::AccessToken.from_hash(client, { refresh_token: token })
|
90
|
+
token_object = token_object.refresh!
|
91
|
+
|
92
|
+
JSON.parse(token_object.to_hash.to_json, object_class: OpenStruct)
|
93
|
+
rescue OAuth2::Error => e
|
94
|
+
raise build_error(e.response.status, e.response.body, e.response.headers)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Checks if token is expired using Oauth2 classes
|
98
|
+
# @param expires_at [Number] expires_at as time since epoch
|
99
|
+
#
|
100
|
+
# @return [Boolean]
|
101
|
+
def expired?(expires_at)
|
102
|
+
OAuth2::AccessToken.from_hash(client, { expires_at: expires_at }).expired?
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def build_flags(flags)
|
108
|
+
return unless flags
|
109
|
+
|
110
|
+
flags.map { |key, value| "#{key}:#{value}" }.join(' ')
|
111
|
+
end
|
112
|
+
|
113
|
+
def set_token_url(flags)
|
114
|
+
params = {}
|
115
|
+
params[:flags] = build_flags(flags) if flags
|
116
|
+
# Note - The inbuild interface to get the token does not allow any way to pass additional
|
117
|
+
# URL params. Hence building the token URL with the flags and setting it in client.
|
118
|
+
client.options[:token_url] = client.connection.build_url('/oauth/token', params).request_uri
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize_auth_parameters(scope, options)
|
122
|
+
@auth_parameters = {
|
123
|
+
response_type: CODE,
|
124
|
+
redirect_uri: redirect_uri,
|
125
|
+
mode: mode,
|
126
|
+
scope: scope.join(' ')
|
127
|
+
}
|
128
|
+
@auth_parameters[:approval_prompt] = options[:force_prompt] ? FORCE : AUTO unless options[:force_prompt].nil?
|
129
|
+
@auth_parameters[:state] = options[:state] if options[:state]
|
130
|
+
@auth_parameters[:make] = options[:make_bypass] if options[:make_bypass]
|
131
|
+
@auth_parameters[:flags] = build_flags(options[:flags]) if options[:flags]
|
132
|
+
end
|
133
|
+
|
134
|
+
def add_single_select_options(single_select)
|
135
|
+
return unless single_select
|
136
|
+
|
137
|
+
if single_select[:vin]
|
138
|
+
@auth_parameters[:single_select_vin] = single_select[:vin]
|
139
|
+
@auth_parameters[:single_select] = true
|
140
|
+
elsif !single_select[:enabled].nil?
|
141
|
+
@auth_parameters[:single_select] = single_select[:enabled]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# gets the Oauth Client object
|
146
|
+
#
|
147
|
+
# @return [OAuth2::Client] A Oauth Client object.
|
148
|
+
def client
|
149
|
+
@client ||= OAuth2::Client.new(client_id,
|
150
|
+
client_secret,
|
151
|
+
site: origin)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/smartcar/base.rb
CHANGED
@@ -1,74 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'oauth2'
|
2
4
|
require 'base64'
|
3
5
|
module Smartcar
|
4
6
|
# The Base class for all of the other class.
|
5
7
|
# Let other classes inherit from here and put common methods here.
|
6
8
|
class Base
|
7
|
-
include Utils
|
9
|
+
include Smartcar::Utils
|
8
10
|
|
9
11
|
# Error raised when an invalid parameter is passed.
|
10
12
|
class InvalidParameterValue < StandardError; end
|
11
|
-
# Constant for Bearer auth type
|
12
|
-
BEARER = 'BEARER'.freeze
|
13
13
|
# Constant for Basic auth type
|
14
|
-
BASIC = '
|
14
|
+
BASIC = 'Basic'
|
15
15
|
# Number of seconds to wait for response
|
16
16
|
REQUEST_TIMEOUT = 310
|
17
17
|
|
18
|
-
attr_accessor :token, :error, :
|
18
|
+
attr_accessor :token, :error, :unit_system, :version, :auth_type
|
19
19
|
|
20
|
-
%i
|
20
|
+
%i[get post patch put delete].each do |verb|
|
21
21
|
# meta programming and define all Restful methods.
|
22
22
|
# @param path [String] the path to hit for the request.
|
23
23
|
# @param data [Hash] request body if needed.
|
24
24
|
#
|
25
25
|
# @return [Hash] The response Json parsed as a hash.
|
26
|
-
define_method verb do |path, data=nil|
|
26
|
+
define_method verb do |path, data = nil|
|
27
27
|
response = service.send(verb) do |request|
|
28
|
-
request.headers['Authorization'] = "
|
29
|
-
request.headers['
|
30
|
-
request.headers['
|
31
|
-
|
32
|
-
|
33
|
-
if verb==:get
|
28
|
+
request.headers['Authorization'] = auth_type == BASIC ? "Basic #{token}" : "Bearer #{token}"
|
29
|
+
request.headers['sc-unit-system'] = unit_system if unit_system
|
30
|
+
request.headers['Content-Type'] = 'application/json'
|
31
|
+
complete_path = "/v#{version}#{path}"
|
32
|
+
if verb == :get
|
34
33
|
request.url complete_path, data
|
35
34
|
else
|
36
35
|
request.url complete_path
|
37
36
|
request.body = data.to_json if data
|
38
37
|
end
|
39
38
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
handle_error(response)
|
40
|
+
# required to handle unsubscribe response
|
41
|
+
body = response.body.empty? ? '{}' : response.body
|
42
|
+
[JSON.parse(body), response.headers]
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
# This requires a proc 'PATH' to be defined in the class
|
47
47
|
# @param path [String] resource path
|
48
|
-
# @param
|
48
|
+
# @param query_params [Hash] query params
|
49
49
|
# @param auth [String] type of auth
|
50
50
|
#
|
51
51
|
# @return [Object]
|
52
|
-
def fetch(path
|
53
|
-
|
54
|
-
|
55
|
-
get(_path, {auth: auth})
|
52
|
+
def fetch(path:, query_params: {})
|
53
|
+
path += "?#{URI.encode_www_form(query_params)}" unless query_params.empty?
|
54
|
+
get(path)
|
56
55
|
end
|
57
56
|
|
58
57
|
private
|
59
58
|
|
60
|
-
# returns auth token for BASIC auth
|
61
|
-
#
|
62
|
-
# @return [String] Base64 encoding of CLIENT:SECRET
|
63
|
-
def get_basic_auth
|
64
|
-
Base64.strict_encode64("#{get_config('CLIENT_ID')}:#{get_config('CLIENT_SECRET')}")
|
65
|
-
end
|
66
|
-
|
67
59
|
# gets a smartcar API service/client
|
68
60
|
#
|
69
61
|
# @return [OAuth2::AccessToken] An initialized AccessToken instance that acts as service client
|
70
62
|
def service
|
71
|
-
@service ||= Faraday.new(url:
|
63
|
+
@service ||= Faraday.new(url: ENV['SMARTCAR_API_ORIGIN'] || API_ORIGIN, request: { timeout: REQUEST_TIMEOUT })
|
72
64
|
end
|
73
65
|
end
|
74
66
|
end
|
data/lib/smartcar/utils.rb
CHANGED
@@ -1,44 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smartcar
|
1
4
|
# Utils module , provides utility methods to underlying classes
|
2
5
|
module Utils
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# A constructor to take a hash and assign it to the instance variables
|
7
|
+
# @param options = {} [Hash] Could by any class's hash, but the first level keys should be defined in the class
|
8
|
+
#
|
9
|
+
# @return [Subclass os Base] Returns object of any subclass like Report
|
10
|
+
def initialize(options = {})
|
11
|
+
options.each do |attribute, value|
|
12
|
+
instance_variable_set("@#{attribute}", value)
|
13
|
+
end
|
10
14
|
end
|
11
|
-
end
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
# gets a given env variable, checks for existence and throws exception if not present
|
17
|
+
# @param config_name [String] key of the env variable
|
18
|
+
#
|
19
|
+
# @return [String] value of the env variable
|
20
|
+
def get_config(config_name)
|
21
|
+
# ENV.MODE is set to test by e2e tests.
|
22
|
+
config_name = "E2E_#{config_name}" if ENV['MODE'] == 'test'
|
23
|
+
raise Smartcar::ConfigNotFound, "Environment variable #{config_name} not found !" unless ENV[config_name]
|
24
|
+
|
25
|
+
ENV[config_name]
|
19
26
|
end
|
20
|
-
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def build_meta(headers)
|
29
|
+
meta_hash = {
|
30
|
+
'sc-data-age' => :data_age,
|
31
|
+
'sc-unit-system' => :unit_system,
|
32
|
+
'sc-request-id' => :request_id
|
33
|
+
}.each_with_object({}) do |(header_name, key), meta|
|
34
|
+
meta[key] = headers[header_name] if headers[header_name]
|
35
|
+
end
|
36
|
+
meta = JSON.parse(meta_hash.to_json, object_class: OpenStruct)
|
37
|
+
meta.data_age &&= DateTime.parse(meta.data_age)
|
38
|
+
|
39
|
+
meta
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_response(body, headers)
|
43
|
+
response = JSON.parse(body.to_json, object_class: OpenStruct)
|
44
|
+
response.meta = build_meta(headers)
|
45
|
+
response
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_aliases(response, aliases)
|
49
|
+
(aliases || []).each do |original_name, alias_name|
|
50
|
+
response.send("#{alias_name}=".to_sym, response.send(original_name.to_sym))
|
51
|
+
end
|
52
|
+
|
53
|
+
response
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_error(status, body_string, headers)
|
57
|
+
content_type = headers['content-type'] || ''
|
58
|
+
return SmartcarError.new(status, body_string, headers) unless content_type.include?('application/json')
|
31
59
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
60
|
+
begin
|
61
|
+
parsed_body = JSON.parse(body_string, { symbolize_names: true })
|
62
|
+
rescue StandardError => e
|
63
|
+
return SmartcarError.new(
|
64
|
+
status,
|
65
|
+
{
|
66
|
+
message: e.message,
|
67
|
+
type: 'SDK_ERROR'
|
68
|
+
},
|
69
|
+
headers
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
return SmartcarError.new(status, parsed_body, headers) if parsed_body[:error] || parsed_body[:type]
|
74
|
+
|
75
|
+
SmartcarError.new(status, parsed_body.merge({ type: 'SDK_ERROR' }), headers)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Given the response from smartcar API, throws an error if needed
|
79
|
+
# @param response [Object] response Object with status and body
|
80
|
+
def handle_error(response)
|
81
|
+
status = response.status
|
82
|
+
return nil if [200, 204].include?(status)
|
83
|
+
|
84
|
+
raise build_error(response.status, response.body, response.headers)
|
85
|
+
end
|
86
|
+
|
87
|
+
def process_batch_response(response_body, response_headers)
|
88
|
+
response_object = OpenStruct.new
|
89
|
+
response_body['responses'].each do |item|
|
90
|
+
attribute_name = convert_path_to_attribute(item['path'])
|
91
|
+
aliases = Vehicle::METHODS[attribute_name.to_sym][:aliases]
|
92
|
+
# merging the top level request headers and separate headers for each item of batch
|
93
|
+
headers = response_headers.merge(item['headers'])
|
94
|
+
response = if [200, 204].include?(item['code'])
|
95
|
+
build_aliases(build_response(item['body'], headers), aliases)
|
96
|
+
else
|
97
|
+
build_error(item['code'], item['body'].to_json, headers)
|
98
|
+
end
|
99
|
+
response_object.define_singleton_method attribute_name do
|
100
|
+
raise response if response.is_a?(SmartcarError)
|
101
|
+
|
102
|
+
response
|
103
|
+
end
|
104
|
+
end
|
105
|
+
response_object
|
106
|
+
end
|
107
|
+
|
108
|
+
# takes a path and converts it to the keys we use.
|
109
|
+
# EX - '/charge' -> :charge, '/battery/capacity' -> :battery_capacity
|
110
|
+
def convert_path_to_attribute(path)
|
111
|
+
return :attributes if path == '/'
|
112
|
+
|
113
|
+
path.split('/').reject(&:empty?).join('_').to_sym
|
114
|
+
end
|
43
115
|
end
|
44
116
|
end
|
data/lib/smartcar/vehicle.rb
CHANGED
@@ -1,307 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Smartcar
|
2
4
|
# Vehicle class to connect to vehicle basic info,disconnect, lock unlock and get all vehicles API
|
3
5
|
# For ease of use, this also has methods define to be able to call other resources on a vehicle object
|
4
6
|
# For Ex. Vehicle object will be treate as an entity and doing vehicle_object.
|
5
7
|
# Battery should return Battery object.
|
6
8
|
#
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
# @attr [String] token Access token used to connect to Smartcar API.
|
10
|
+
# @attr [String] id Smartcar vehicle ID.
|
11
|
+
# @attr [Hash] options
|
12
|
+
# @attr unit_system [String] Unit system to represent the data in, defaults to Imperial
|
13
|
+
# @attr version [String] API version to be used.
|
10
14
|
class Vehicle < Base
|
11
|
-
include Utils
|
12
|
-
|
13
|
-
|
14
|
-
# Path for hitting compatibility end point
|
15
|
-
COMPATIBLITY_PATH = '/compatibility'.freeze
|
16
|
-
|
17
|
-
# Path for hitting vehicle ids end point
|
18
|
-
PATH = Proc.new{|id| "/vehicles/#{id}"}
|
19
|
-
|
20
15
|
attr_reader :id
|
21
|
-
attr_accessor :token, :unit_system
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
17
|
+
# @private
|
18
|
+
METHODS = {
|
19
|
+
permissions: { path: proc { |id| "/vehicles/#{id}/permissions" }, skip: true },
|
20
|
+
attributes: { path: proc { |id| "/vehicles/#{id}" } },
|
21
|
+
battery: {
|
22
|
+
path: proc { |id| "/vehicles/#{id}/battery" },
|
23
|
+
aliases: { 'percentRemaining' => 'percentage_remaining' }
|
24
|
+
},
|
25
|
+
battery_capacity: { path: proc { |id| "/vehicles/#{id}/battery/capacity" } },
|
26
|
+
charge: {
|
27
|
+
path: proc { |id| "/vehicles/#{id}/charge" },
|
28
|
+
aliases: { 'isPluggedIn' => 'is_plugged_in?' }
|
29
|
+
},
|
30
|
+
engine_oil: {
|
31
|
+
path: proc { |id| "/vehicles/#{id}/engine/oil" },
|
32
|
+
aliases: { 'lifeRemaining' => 'life_remaining' }
|
33
|
+
},
|
34
|
+
fuel: {
|
35
|
+
path: proc { |id| "/vehicles/#{id}/fuel" },
|
36
|
+
aliases: {
|
37
|
+
'amountRemaining' => 'amount_remaining',
|
38
|
+
'percentRemaining' => 'percent_remaining'
|
39
|
+
}
|
40
|
+
},
|
41
|
+
location: { path: proc { |id| "/vehicles/#{id}/location" } },
|
42
|
+
odometer: { path: proc { |id| "/vehicles/#{id}/odometer" } },
|
43
|
+
tire_pressure: {
|
44
|
+
path: proc { |id| "/vehicles/#{id}/tires/pressure" },
|
45
|
+
aliases: {
|
46
|
+
'backLeft' => 'back_left',
|
47
|
+
'backRight' => 'back_right',
|
48
|
+
'frontLeft' => 'front_left',
|
49
|
+
'frontRight' => 'front_right'
|
50
|
+
}
|
51
|
+
},
|
52
|
+
vin: { path: proc { |id| "/vehicles/#{id}/vin" } },
|
53
|
+
disconnect!: { type: :delete, path: proc { |id| "/vehicles/#{id}/application" } },
|
54
|
+
lock!: { type: :post, path: proc { |id| "/vehicles/#{id}/security" }, body: { action: 'LOCK' } },
|
55
|
+
unlock!: { type: :post, path: proc { |id| "/vehicles/#{id}/security" }, body: { action: 'UNLOCK' } },
|
56
|
+
start_charge!: { type: :post, path: proc { |id| "/vehicles/#{id}/charge" }, body: { action: 'START' } },
|
57
|
+
stop_charge!: { type: :post, path: proc { |id| "/vehicles/#{id}/charge" }, body: { action: 'STOP' } },
|
58
|
+
subscribe!: {
|
59
|
+
type: :post,
|
60
|
+
path: proc { |id, webhook_id| "/vehicles/#{id}/webhooks/#{webhook_id}" },
|
61
|
+
aliases: {
|
62
|
+
'webhookId' => 'webhook_id',
|
63
|
+
'vehicleId' => 'vehicle_id'
|
64
|
+
},
|
65
|
+
skip: true
|
66
|
+
},
|
67
|
+
unsubscribe!: { type: :post, path: proc { |id, webhook_id|
|
68
|
+
"/vehicles/#{id}/webhooks/#{webhook_id}"
|
69
|
+
}, skip: true }
|
70
|
+
}.freeze
|
71
|
+
|
72
|
+
def initialize(token:, id:, options: { unit_system: METRIC, version: Smartcar.get_api_version })
|
73
|
+
super
|
27
74
|
@token = token
|
28
75
|
@id = id
|
29
|
-
@unit_system = unit_system
|
30
|
-
|
31
|
-
|
32
|
-
# Class method Used to get all the vehicles in the app. This only returns
|
33
|
-
# API - https://smartcar.com/docs/api#get-all-vehicles
|
34
|
-
# @param token [String] - Access token
|
35
|
-
# @param options [Hash] - Optional filter parameters (check documentation)
|
36
|
-
#
|
37
|
-
# @return [Array] of vehicle IDs(Strings)
|
38
|
-
def self.all_vehicle_ids(token:, options: {})
|
39
|
-
response, meta = new(token: token, id: 'none').fetch(
|
40
|
-
path: PATH.call(''),
|
41
|
-
options: options
|
42
|
-
)
|
43
|
-
response['vehicles']
|
44
|
-
end
|
45
|
-
|
46
|
-
# Class method Used to check compatiblity for VIN and scope
|
47
|
-
# API - https://smartcar.com/docs/api#connect-compatibility
|
48
|
-
# @param vin [String] VIN of the vehicle to be checked
|
49
|
-
# @param scope [Array of Strings] - array of scopes
|
50
|
-
# @param country [String] An optional country code according to
|
51
|
-
# [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).
|
52
|
-
# Defaults to US.
|
53
|
-
#
|
54
|
-
# @return [Boolean] true or false
|
55
|
-
def self.compatible?(vin:, scope:, country: 'US')
|
56
|
-
raise InvalidParameterValue.new, "vin is a required field" if vin.nil?
|
57
|
-
raise InvalidParameterValue.new, "scope is a required field" if scope.nil?
|
58
|
-
|
59
|
-
response, meta = new(token: 'none', id: 'none').fetch(path: COMPATIBLITY_PATH,
|
60
|
-
options: {
|
61
|
-
vin: vin,
|
62
|
-
scope: scope.join(' '),
|
63
|
-
country: country
|
64
|
-
},
|
65
|
-
auth: BASIC
|
66
|
-
)
|
67
|
-
response['compatible']
|
68
|
-
end
|
76
|
+
@unit_system = options[:unit_system]
|
77
|
+
@version = options[:version]
|
69
78
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
#
|
74
|
-
# @return [Hash] Hash wth key as requested attribute(symbol) and value as Error OR Object of the requested attribute
|
75
|
-
def batch(attributes = [])
|
76
|
-
raise InvalidParameterValue.new, "vin is a required field" if attributes.nil?
|
77
|
-
request_body = get_batch_request_body(attributes)
|
78
|
-
response, _meta = post(PATH.call(id) + "/batch", request_body)
|
79
|
-
process_batch_response(response)
|
79
|
+
raise InvalidParameterValue.new, "Invalid Units provided : #{@unit_system}" unless UNITS.include?(@unit_system)
|
80
|
+
raise InvalidParameterValue.new, 'Vehicle ID (id) is a required field' if id.nil?
|
81
|
+
raise InvalidParameterValue.new, 'Access Token(token) is a required field' if token.nil?
|
80
82
|
end
|
81
83
|
|
82
|
-
#
|
83
|
-
#
|
84
|
-
# EX : Smartcar::Vehicle.new(token: token, id: id).permissions
|
85
|
-
# @param options [Hash] - Optional filter parameters (check documentation)
|
86
|
-
#
|
87
|
-
# @return [Permissions] object
|
88
|
-
def permissions(options: {})
|
89
|
-
get_attribute(Permissions)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Method Used toRevoke access for the current requesting application
|
93
|
-
# API - https://smartcar.com/docs/api#delete-disconnect
|
94
|
-
#
|
95
|
-
# @return [Boolean] true if success
|
96
|
-
def disconnect!
|
97
|
-
response = delete(PATH.call(id) + "/application")
|
98
|
-
response['status'] == SUCCESS
|
99
|
-
end
|
100
|
-
|
101
|
-
# Methods Used to lock car
|
102
|
-
# API - https://smartcar.com/docs/api#post-security
|
103
|
-
#
|
104
|
-
# @return [Boolean] true if success
|
105
|
-
def lock!
|
106
|
-
lock_or_unlock!(action: Smartcar::LOCK)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Methods Used to unlock car
|
110
|
-
# API - https://smartcar.com/docs/api#post-security
|
111
|
-
#
|
112
|
-
# @return [Boolean] true if success
|
113
|
-
def unlock!
|
114
|
-
lock_or_unlock!(action: Smartcar::UNLOCK)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Method used to start charging a car
|
84
|
+
# @!method attributes()
|
85
|
+
# Returns make model year and id of the vehicle
|
118
86
|
#
|
87
|
+
# API Documentation - https://smartcar.com/api#get-vehicle-attributes
|
119
88
|
#
|
120
|
-
# @return [
|
121
|
-
|
122
|
-
start_or_stop_charge!(action: Smartcar::START_CHARGE)
|
123
|
-
end
|
89
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/api#get-vehicle-attributes
|
90
|
+
# and a meta attribute with the relevant items from response headers.
|
124
91
|
|
125
|
-
#
|
92
|
+
# @!method battery()
|
93
|
+
# Returns the state of charge (SOC) and remaining range of an electric or plug-in hybrid vehicle's battery.
|
126
94
|
#
|
95
|
+
# API Documentation https://smartcar.com/docs/api#get-ev-battery
|
127
96
|
#
|
128
|
-
# @return [
|
129
|
-
|
130
|
-
start_or_stop_charge!(action: Smartcar::STOP_CHARGE)
|
131
|
-
end
|
97
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-ev-battery
|
98
|
+
# and a meta attribute with the relevant items from response headers.
|
132
99
|
|
133
|
-
#
|
134
|
-
#
|
100
|
+
# @!method battery_capacity()
|
101
|
+
# Returns the capacity of an electric or plug-in hybrid vehicle's battery.
|
135
102
|
#
|
136
|
-
#
|
137
|
-
def vehicle_attributes
|
138
|
-
get_attribute(VehicleAttributes)
|
139
|
-
end
|
140
|
-
|
141
|
-
# Returns the state of charge (SOC) and remaining range of an electric or
|
142
|
-
# plug-in hybrid vehicle's battery.
|
143
|
-
# API - https://smartcar.com/docs/api#get-ev-battery
|
144
|
-
#
|
145
|
-
# @return [Battery] object
|
146
|
-
def battery
|
147
|
-
get_attribute(Battery)
|
148
|
-
end
|
149
|
-
|
150
|
-
# Returns the capacity of an electric or
|
151
|
-
# plug-in hybrid vehicle's battery.
|
152
|
-
# API - https://smartcar.com/docs/api#get-ev-battery-capacity
|
103
|
+
# API Documentation https://smartcar.com/docs/api#get-ev-battery-capacity
|
153
104
|
#
|
154
|
-
# @return [
|
155
|
-
|
156
|
-
get_attribute(BatteryCapacity)
|
157
|
-
end
|
105
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-ev-battery-capacity
|
106
|
+
# and a meta attribute with the relevant items from response headers.
|
158
107
|
|
108
|
+
# @!method charge()
|
159
109
|
# Returns the current charge status of the vehicle.
|
160
|
-
# API - https://smartcar.com/docs/api#get-ev-battery
|
161
110
|
#
|
162
|
-
#
|
163
|
-
|
164
|
-
|
165
|
-
|
111
|
+
# API Documentation https://smartcar.com/docs/api#get-ev-battery
|
112
|
+
#
|
113
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-ev-battery
|
114
|
+
# and a meta attribute with the relevant items from response headers.
|
166
115
|
|
116
|
+
# @!method engine_oil()
|
167
117
|
# Returns the remaining life span of a vehicle's engine oil
|
168
|
-
# API - https://smartcar.com/docs/api#get-engine-oil-life
|
169
118
|
#
|
170
|
-
#
|
171
|
-
|
172
|
-
|
173
|
-
|
119
|
+
# API Documentation https://smartcar.com/docs/api#get-engine-oil-life
|
120
|
+
#
|
121
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-engine-oil-life
|
122
|
+
# and a meta attribute with the relevant items from response headers.
|
174
123
|
|
124
|
+
# @!method fuel()
|
175
125
|
# Returns the status of the fuel remaining in the vehicle's gas tank.
|
176
|
-
# API - https://smartcar.com/docs/api#get-fuel-tank
|
177
126
|
#
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
|
127
|
+
# API Documentation https://smartcar.com/docs/api#get-fuel-tank
|
128
|
+
#
|
129
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-fuel-tank
|
130
|
+
# and a meta attribute with the relevant items from response headers.
|
182
131
|
|
132
|
+
# @!method location()
|
183
133
|
# Returns the last known location of the vehicle in geographic coordinates.
|
184
|
-
# API - https://smartcar.com/docs/api#get-location
|
185
134
|
#
|
186
|
-
#
|
187
|
-
|
188
|
-
|
189
|
-
|
135
|
+
# API Documentation https://smartcar.com/docs/api#get-location
|
136
|
+
#
|
137
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-location
|
138
|
+
# and a meta attribute with the relevant items from response headers.
|
190
139
|
|
140
|
+
# @!method odometer()
|
191
141
|
# Returns the vehicle's last known odometer reading.
|
192
|
-
# API - https://smartcar.com/docs/api#get-odometer
|
193
142
|
#
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
143
|
+
# API Documentation https://smartcar.com/docs/api#get-odometer
|
144
|
+
#
|
145
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-odometer
|
146
|
+
# and a meta attribute with the relevant items from response headers.
|
198
147
|
|
148
|
+
# @!method tire_pressure()
|
199
149
|
# Returns the air pressure of each of the vehicle's tires.
|
200
|
-
# API - https://smartcar.com/docs/api#get-tire-pressure
|
201
150
|
#
|
202
|
-
#
|
203
|
-
|
204
|
-
|
205
|
-
|
151
|
+
# API Documentation https://smartcar.com/docs/api#get-tire-pressure
|
152
|
+
#
|
153
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-tire-pressure
|
154
|
+
# and a meta attribute with the relevant items from response headers.
|
206
155
|
|
156
|
+
# @!method vin()
|
207
157
|
# Returns the vehicle's manufacturer identifier (VIN).
|
208
|
-
# API - https://smartcar.com/docs/api#get-vin
|
209
158
|
#
|
210
|
-
#
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
get_path(EngineOil) => EngineOil,
|
237
|
-
get_path(Fuel) => Fuel,
|
238
|
-
get_path(Location) => Location,
|
239
|
-
get_path(Odometer) => Odometer,
|
240
|
-
get_path(Permissions) => Permissions,
|
241
|
-
get_path(TirePressure) => TirePressure,
|
242
|
-
get_path(Vin) => Vin,
|
243
|
-
}
|
244
|
-
end
|
245
|
-
|
246
|
-
# @private
|
247
|
-
BatchItemResponse = Struct.new(:body, :status, :headers) do
|
248
|
-
def body_with_meta
|
249
|
-
body.merge(meta: headers)
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
def get_batch_request_body(attributes)
|
254
|
-
attributes = validated_attributes(attributes)
|
255
|
-
requests = attributes.each_with_object([]) do |item, requests|
|
256
|
-
requests << { path: allowed_attributes[item] }
|
257
|
-
end
|
258
|
-
{ requests: requests }
|
259
|
-
end
|
260
|
-
|
261
|
-
def process_batch_response(responses)
|
262
|
-
inverted_map = allowed_attributes.invert
|
263
|
-
responses["responses"].each_with_object({}) do |response, result|
|
264
|
-
item_response = BatchItemResponse.new(response["body"], response["code"], response["headers"])
|
265
|
-
error = get_error(item_response)
|
266
|
-
path = response["path"]
|
267
|
-
result[inverted_map[path]] = error || get_object(path_to_class[path], item_response.body_with_meta)
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
def validated_attributes(attributes)
|
272
|
-
attributes.map!(&:to_sym)
|
273
|
-
unsupported_attributes = (attributes - allowed_attributes.keys) || []
|
274
|
-
unless unsupported_attributes.empty?
|
275
|
-
message = "Unsupported attribute(s) requested in batch - #{unsupported_attributes.join(',')}"
|
276
|
-
raise InvalidParameterValue.new, message
|
159
|
+
# API Documentation https://smartcar.com/docs/api#get-vin
|
160
|
+
#
|
161
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-vin
|
162
|
+
# and a meta attribute with the relevant items from response headers.
|
163
|
+
|
164
|
+
# NOTES :
|
165
|
+
# - We only generate the methods where there is no query string or additional options considering thats
|
166
|
+
# the majority, for all the ones that require parameters, write them separately.
|
167
|
+
# Ex. permissions, subscribe, unsubscribe
|
168
|
+
# - The following snippet generates methods dynamically , but if we are adding a new item,
|
169
|
+
# make sure we also add the doc for it.
|
170
|
+
METHODS.each do |method, item|
|
171
|
+
# We add these to the METHODS object to keep it in one place, but mark them to be skipped
|
172
|
+
# for dynamic generation
|
173
|
+
next if item[:skip]
|
174
|
+
|
175
|
+
define_method method do
|
176
|
+
body, headers = case item[:type]
|
177
|
+
when :post
|
178
|
+
post(item[:path].call(id), item[:body])
|
179
|
+
when :delete
|
180
|
+
delete(item[:path].call(id))
|
181
|
+
else
|
182
|
+
fetch(path: item[:path].call(id))
|
183
|
+
end
|
184
|
+
build_aliases(build_response(body, headers), item[:aliases])
|
277
185
|
end
|
278
|
-
attributes
|
279
186
|
end
|
280
187
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
def
|
289
|
-
|
188
|
+
# Method to fetch the list of permissions that this application has been granted for this vehicle.
|
189
|
+
# API - https://smartcar.com/docs/api#get-application-permissions
|
190
|
+
#
|
191
|
+
# @param paging [Hash] Optional filter parameters (check documentation)
|
192
|
+
#
|
193
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-application-permissions
|
194
|
+
# and a meta attribute with the relevant items from response headers.
|
195
|
+
def permissions(paging = {})
|
196
|
+
response, headers = fetch(path: METHODS.dig(:permissions, :path).call(id), query_params: paging)
|
197
|
+
build_response(response, headers)
|
290
198
|
end
|
291
199
|
|
292
|
-
|
293
|
-
|
294
|
-
|
200
|
+
# Subscribe the vehicle to given webhook Id.
|
201
|
+
#
|
202
|
+
# @param webhook_id [String] Webhook id to subscribe to
|
203
|
+
#
|
204
|
+
# @return [OpenStruct] And object representing the JSON response and a meta attribute
|
205
|
+
# with the relevant items from response headers.
|
206
|
+
def subscribe!(webhook_id)
|
207
|
+
response, headers = post(METHODS.dig(:subscribe!, :path).call(id, webhook_id), {})
|
208
|
+
build_aliases(build_response(response, headers), METHODS.dig(:subscribe!, :aliases))
|
295
209
|
end
|
296
210
|
|
297
|
-
|
298
|
-
|
299
|
-
|
211
|
+
# Unubscribe the vehicle from given webhook Id.
|
212
|
+
#
|
213
|
+
# @param amt [String] Application management token
|
214
|
+
# @param webhook_id [String] Webhook id to subscribe to
|
215
|
+
#
|
216
|
+
# @return [OpenStruct] Meta attribute with the relevant items from response headers.
|
217
|
+
def unsubscribe!(amt, webhook_id)
|
218
|
+
# swapping off the token with amt for unsubscribe.
|
219
|
+
access_token = token
|
220
|
+
self.token = amt
|
221
|
+
response, headers = delete(METHODS.dig(:unsubscribe!, :path).call(id, webhook_id))
|
222
|
+
self.token = access_token
|
223
|
+
build_response(response, headers)
|
300
224
|
end
|
301
225
|
|
302
|
-
|
303
|
-
|
304
|
-
|
226
|
+
# Method to get batch requests.
|
227
|
+
# API - https://smartcar.com/docs/api#post-batch-request
|
228
|
+
# @param paths [Array] Array of paths as strings. Ex ['/battery', '/odometer']
|
229
|
+
#
|
230
|
+
# @return [OpenStruct] Object with one attribute per requested path that returns
|
231
|
+
# an OpenStruct object of the requested attribute or taises if it is an error.
|
232
|
+
def batch(paths)
|
233
|
+
request_body = { requests: paths.map { |path| { path: path } } }
|
234
|
+
response, headers = post("/vehicles/#{id}/batch", request_body)
|
235
|
+
process_batch_response(response, headers)
|
305
236
|
end
|
306
237
|
end
|
307
238
|
end
|