smartcar 2.4.0 → 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 -16
- data/README.md +38 -36
- data/Rakefile +11 -3
- data/bin/console +4 -3
- data/lib/smartcar.rb +167 -55
- data/lib/smartcar/auth_client.rb +154 -0
- data/lib/smartcar/base.rb +18 -26
- data/lib/smartcar/utils.rb +102 -20
- data/lib/smartcar/vehicle.rb +183 -249
- data/lib/smartcar/version.rb +3 -1
- data/lib/smartcar_error.rb +49 -0
- data/ruby-sdk.gemspec +28 -21
- metadata +84 -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 -35
- 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_to_ostruct(token_hash)
|
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_to_ostruct(token_object.to_hash)
|
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,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'oauth2'
|
2
4
|
require 'base64'
|
3
5
|
module Smartcar
|
@@ -8,67 +10,57 @@ module Smartcar
|
|
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['Authorization'] = "BASIC #{get_basic_auth}" if data[:auth] == BASIC
|
28
|
+
request.headers['Authorization'] = auth_type == BASIC ? "Basic #{token}" : "Bearer #{token}"
|
30
29
|
request.headers['sc-unit-system'] = unit_system if unit_system
|
31
|
-
request.headers['Content-Type'] =
|
30
|
+
request.headers['Content-Type'] = 'application/json'
|
32
31
|
complete_path = "/v#{version}#{path}"
|
33
|
-
if verb
|
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,5 +1,7 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Smartcar
|
4
|
+
# Utils module , provides utility methods to underlying classes
|
3
5
|
module Utils
|
4
6
|
# A constructor to take a hash and assign it to the instance variables
|
5
7
|
# @param options = {} [Hash] Could by any class's hash, but the first level keys should be defined in the class
|
@@ -11,36 +13,116 @@ module Smartcar
|
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
|
-
# Utility method to return a hash of the isntance variables
|
15
|
-
#
|
16
|
-
# @return [Hash] hash of all instance variables
|
17
|
-
def to_hash
|
18
|
-
instance_variables.each_with_object({}) do |attribute, hash|
|
19
|
-
hash[attribute.to_s.delete("@").to_sym] = instance_variable_get(attribute)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
16
|
# gets a given env variable, checks for existence and throws exception if not present
|
24
17
|
# @param config_name [String] key of the env variable
|
25
18
|
#
|
26
19
|
# @return [String] value of the env variable
|
27
20
|
def get_config(config_name)
|
28
|
-
|
21
|
+
# ENV.MODE is set to test by e2e tests.
|
22
|
+
config_name = "E2E_#{config_name}" if ENV['MODE'] == 'test'
|
29
23
|
raise Smartcar::ConfigNotFound, "Environment variable #{config_name} not found !" unless ENV[config_name]
|
24
|
+
|
30
25
|
ENV[config_name]
|
31
26
|
end
|
32
27
|
|
33
|
-
#
|
34
|
-
#
|
28
|
+
# Converts a hash to RecursiveOpenStruct (a powered up OpenStruct object).
|
29
|
+
# NOTE - Do not replace with the more elegant looking
|
30
|
+
# JSON.parse(meta_hash.to_json, object_class: OpenStruct)
|
31
|
+
# this is because we had an app using OJ as their json parser which led to an issue using the
|
32
|
+
# above mentioned method. Source : https://github.com/ohler55/oj/issues/239
|
33
|
+
# @param hash [Hash] json object as hash
|
35
34
|
#
|
36
|
-
# @return [
|
37
|
-
def
|
35
|
+
# @return [RecursiveOpenStruct]
|
36
|
+
def json_to_ostruct(hash)
|
37
|
+
RecursiveOpenStruct.new(hash, recurse_over_arrays: true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_meta(headers)
|
41
|
+
meta_hash = {
|
42
|
+
'sc-data-age' => :data_age,
|
43
|
+
'sc-unit-system' => :unit_system,
|
44
|
+
'sc-request-id' => :request_id
|
45
|
+
}.each_with_object({}) do |(header_name, key), meta|
|
46
|
+
meta[key] = headers[header_name] if headers[header_name]
|
47
|
+
end
|
48
|
+
meta = json_to_ostruct(meta_hash)
|
49
|
+
meta.data_age &&= DateTime.parse(meta.data_age)
|
50
|
+
|
51
|
+
meta
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_response(body, headers)
|
55
|
+
response = json_to_ostruct(body)
|
56
|
+
response.meta = build_meta(headers)
|
57
|
+
response
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_aliases(response, aliases)
|
61
|
+
(aliases || []).each do |original_name, alias_name|
|
62
|
+
response.send("#{alias_name}=".to_sym, response.send(original_name.to_sym))
|
63
|
+
end
|
64
|
+
|
65
|
+
response
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_error(status, body_string, headers)
|
69
|
+
content_type = headers['content-type'] || ''
|
70
|
+
return SmartcarError.new(status, body_string, headers) unless content_type.include?('application/json')
|
71
|
+
|
72
|
+
begin
|
73
|
+
parsed_body = JSON.parse(body_string, { symbolize_names: true })
|
74
|
+
rescue StandardError => e
|
75
|
+
return SmartcarError.new(
|
76
|
+
status,
|
77
|
+
{
|
78
|
+
message: e.message,
|
79
|
+
type: 'SDK_ERROR'
|
80
|
+
},
|
81
|
+
headers
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
return SmartcarError.new(status, parsed_body, headers) if parsed_body[:error] || parsed_body[:type]
|
86
|
+
|
87
|
+
SmartcarError.new(status, parsed_body.merge({ type: 'SDK_ERROR' }), headers)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Given the response from smartcar API, throws an error if needed
|
91
|
+
# @param response [Object] response Object with status and body
|
92
|
+
def handle_error(response)
|
38
93
|
status = response.status
|
39
|
-
return nil if [200,204].include?(status)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
94
|
+
return nil if [200, 204].include?(status)
|
95
|
+
|
96
|
+
raise build_error(response.status, response.body, response.headers)
|
97
|
+
end
|
98
|
+
|
99
|
+
def process_batch_response(response_body, response_headers)
|
100
|
+
response_object = OpenStruct.new
|
101
|
+
response_body['responses'].each do |item|
|
102
|
+
attribute_name = convert_path_to_attribute(item['path'])
|
103
|
+
aliases = Vehicle::METHODS[attribute_name.to_sym][:aliases]
|
104
|
+
# merging the top level request headers and separate headers for each item of batch
|
105
|
+
headers = response_headers.merge(item['headers'] || {})
|
106
|
+
response = if [200, 204].include?(item['code'])
|
107
|
+
build_aliases(build_response(item['body'], headers), aliases)
|
108
|
+
else
|
109
|
+
build_error(item['code'], item['body'].to_json, headers)
|
110
|
+
end
|
111
|
+
response_object.define_singleton_method attribute_name do
|
112
|
+
raise response if response.is_a?(SmartcarError)
|
113
|
+
|
114
|
+
response
|
115
|
+
end
|
116
|
+
end
|
117
|
+
response_object
|
118
|
+
end
|
119
|
+
|
120
|
+
# takes a path and converts it to the keys we use.
|
121
|
+
# EX - '/charge' -> :charge, '/battery/capacity' -> :battery_capacity
|
122
|
+
def convert_path_to_attribute(path)
|
123
|
+
return :attributes if path == '/'
|
124
|
+
|
125
|
+
path.split('/').reject(&:empty?).join('_').to_sym
|
44
126
|
end
|
45
127
|
end
|
46
128
|
end
|
data/lib/smartcar/vehicle.rb
CHANGED
@@ -1,304 +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
|
-
# Path for hitting compatibility end point
|
12
|
-
COMPATIBLITY_PATH = '/compatibility'.freeze
|
13
|
-
|
14
|
-
# Path for hitting vehicle ids end point
|
15
|
-
PATH = Proc.new{|id| "/vehicles/#{id}"}
|
16
|
-
|
17
15
|
attr_reader :id
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
23
74
|
@token = token
|
24
75
|
@id = id
|
25
|
-
@unit_system = unit_system
|
26
|
-
@version = version
|
27
|
-
end
|
28
|
-
|
29
|
-
# Class method Used to get all the vehicles in the app. This only returns
|
30
|
-
# API - https://smartcar.com/docs/api#get-all-vehicles
|
31
|
-
# @param token [String] - Access token
|
32
|
-
# @param options [Hash] - Optional filter parameters (check documentation)
|
33
|
-
#
|
34
|
-
# @return [Array] of vehicle IDs(Strings)
|
35
|
-
def self.all_vehicle_ids(token:, options: {}, version: Smartcar.get_api_version)
|
36
|
-
response, meta = new(token: token, id: 'none', version: version).fetch(
|
37
|
-
path: PATH.call(''),
|
38
|
-
options: options
|
39
|
-
)
|
40
|
-
response['vehicles']
|
41
|
-
end
|
42
|
-
|
43
|
-
# Class method Used to check compatiblity for VIN and scope
|
44
|
-
# API - https://smartcar.com/docs/api#connect-compatibility
|
45
|
-
# @param vin [String] VIN of the vehicle to be checked
|
46
|
-
# @param scope [Array of Strings] - array of scopes
|
47
|
-
# @param country [String] An optional country code according to
|
48
|
-
# [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).
|
49
|
-
# Defaults to US.
|
50
|
-
#
|
51
|
-
# @return [Boolean] true or false
|
52
|
-
def self.compatible?(vin:, scope:, country: 'US', version: Smartcar.get_api_version)
|
53
|
-
raise InvalidParameterValue.new, "vin is a required field" if vin.nil?
|
54
|
-
raise InvalidParameterValue.new, "scope is a required field" if scope.nil?
|
55
|
-
|
56
|
-
response, meta = new(token: 'none', id: 'none', version: version).fetch(path: COMPATIBLITY_PATH,
|
57
|
-
options: {
|
58
|
-
vin: vin,
|
59
|
-
scope: scope.join(' '),
|
60
|
-
country: country
|
61
|
-
},
|
62
|
-
auth: BASIC
|
63
|
-
)
|
64
|
-
response['compatible']
|
65
|
-
end
|
66
|
-
|
67
|
-
# Method to get batch requests
|
68
|
-
# API - https://smartcar.com/docs/api#post-batch-request
|
69
|
-
# @param attributes [Array] Array of strings or symbols of attributes to be fetched together
|
70
|
-
#
|
71
|
-
# @return [Hash] Hash wth key as requested attribute(symbol) and value as Error OR Object of the requested attribute
|
72
|
-
def batch(attributes = [])
|
73
|
-
raise InvalidParameterValue.new, "vin is a required field" if attributes.nil?
|
74
|
-
request_body = get_batch_request_body(attributes)
|
75
|
-
response, _meta = post(PATH.call(id) + "/batch", request_body)
|
76
|
-
process_batch_response(response)
|
77
|
-
end
|
78
|
-
|
79
|
-
# Fetch the list of permissions that this application has been granted for
|
80
|
-
# this vehicle
|
81
|
-
# EX : Smartcar::Vehicle.new(token: token, id: id).permissions
|
82
|
-
# @param options [Hash] - Optional filter parameters (check documentation)
|
83
|
-
#
|
84
|
-
# @return [Permissions] object
|
85
|
-
def permissions(options: {})
|
86
|
-
get_attribute(Permissions)
|
87
|
-
end
|
76
|
+
@unit_system = options[:unit_system]
|
77
|
+
@version = options[:version]
|
88
78
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
# @return [Boolean] true if success
|
93
|
-
def disconnect!
|
94
|
-
response = delete(PATH.call(id) + "/application")
|
95
|
-
response['status'] == SUCCESS
|
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?
|
96
82
|
end
|
97
83
|
|
98
|
-
#
|
99
|
-
#
|
84
|
+
# @!method attributes()
|
85
|
+
# Returns make model year and id of the vehicle
|
100
86
|
#
|
101
|
-
#
|
102
|
-
def lock!
|
103
|
-
lock_or_unlock!(action: Smartcar::LOCK)
|
104
|
-
end
|
105
|
-
|
106
|
-
# Methods Used to unlock car
|
107
|
-
# API - https://smartcar.com/docs/api#post-security
|
87
|
+
# API Documentation - https://smartcar.com/api#get-vehicle-attributes
|
108
88
|
#
|
109
|
-
# @return [
|
110
|
-
|
111
|
-
lock_or_unlock!(action: Smartcar::UNLOCK)
|
112
|
-
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.
|
113
91
|
|
114
|
-
#
|
92
|
+
# @!method battery()
|
93
|
+
# Returns the state of charge (SOC) and remaining range of an electric or plug-in hybrid vehicle's battery.
|
115
94
|
#
|
95
|
+
# API Documentation https://smartcar.com/docs/api#get-ev-battery
|
116
96
|
#
|
117
|
-
# @return [
|
118
|
-
|
119
|
-
start_or_stop_charge!(action: Smartcar::START_CHARGE)
|
120
|
-
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.
|
121
99
|
|
122
|
-
#
|
100
|
+
# @!method battery_capacity()
|
101
|
+
# Returns the capacity of an electric or plug-in hybrid vehicle's battery.
|
123
102
|
#
|
103
|
+
# API Documentation https://smartcar.com/docs/api#get-ev-battery-capacity
|
124
104
|
#
|
125
|
-
# @return [
|
126
|
-
|
127
|
-
start_or_stop_charge!(action: Smartcar::STOP_CHARGE)
|
128
|
-
end
|
129
|
-
|
130
|
-
# Returns make model year and id of the vehicle
|
131
|
-
# API - https://smartcar.com/api#get-vehicle-attributes
|
132
|
-
#
|
133
|
-
# @return [VehicleAttributes] object
|
134
|
-
def vehicle_attributes
|
135
|
-
get_attribute(VehicleAttributes)
|
136
|
-
end
|
137
|
-
|
138
|
-
# Returns the state of charge (SOC) and remaining range of an electric or
|
139
|
-
# plug-in hybrid vehicle's battery.
|
140
|
-
# API - https://smartcar.com/docs/api#get-ev-battery
|
141
|
-
#
|
142
|
-
# @return [Battery] object
|
143
|
-
def battery
|
144
|
-
get_attribute(Battery)
|
145
|
-
end
|
146
|
-
|
147
|
-
# Returns the capacity of an electric or
|
148
|
-
# plug-in hybrid vehicle's battery.
|
149
|
-
# API - https://smartcar.com/docs/api#get-ev-battery-capacity
|
150
|
-
#
|
151
|
-
# @return [Battery] object
|
152
|
-
def battery_capacity
|
153
|
-
get_attribute(BatteryCapacity)
|
154
|
-
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.
|
155
107
|
|
108
|
+
# @!method charge()
|
156
109
|
# Returns the current charge status of the vehicle.
|
157
|
-
# API - https://smartcar.com/docs/api#get-ev-battery
|
158
110
|
#
|
159
|
-
#
|
160
|
-
|
161
|
-
|
162
|
-
|
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.
|
163
115
|
|
116
|
+
# @!method engine_oil()
|
164
117
|
# Returns the remaining life span of a vehicle's engine oil
|
165
|
-
# API - https://smartcar.com/docs/api#get-engine-oil-life
|
166
118
|
#
|
167
|
-
#
|
168
|
-
|
169
|
-
|
170
|
-
|
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.
|
171
123
|
|
124
|
+
# @!method fuel()
|
172
125
|
# Returns the status of the fuel remaining in the vehicle's gas tank.
|
173
|
-
# API - https://smartcar.com/docs/api#get-fuel-tank
|
174
126
|
#
|
175
|
-
#
|
176
|
-
|
177
|
-
|
178
|
-
|
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.
|
179
131
|
|
132
|
+
# @!method location()
|
180
133
|
# Returns the last known location of the vehicle in geographic coordinates.
|
181
|
-
# API - https://smartcar.com/docs/api#get-location
|
182
134
|
#
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
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.
|
187
139
|
|
140
|
+
# @!method odometer()
|
188
141
|
# Returns the vehicle's last known odometer reading.
|
189
|
-
# API - https://smartcar.com/docs/api#get-odometer
|
190
142
|
#
|
191
|
-
#
|
192
|
-
|
193
|
-
|
194
|
-
|
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.
|
195
147
|
|
148
|
+
# @!method tire_pressure()
|
196
149
|
# Returns the air pressure of each of the vehicle's tires.
|
197
|
-
# API - https://smartcar.com/docs/api#get-tire-pressure
|
198
150
|
#
|
199
|
-
#
|
200
|
-
|
201
|
-
|
202
|
-
|
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.
|
203
155
|
|
156
|
+
# @!method vin()
|
204
157
|
# Returns the vehicle's manufacturer identifier (VIN).
|
205
|
-
# API - https://smartcar.com/docs/api#get-vin
|
206
158
|
#
|
207
|
-
#
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
get_path(EngineOil) => EngineOil,
|
234
|
-
get_path(Fuel) => Fuel,
|
235
|
-
get_path(Location) => Location,
|
236
|
-
get_path(Odometer) => Odometer,
|
237
|
-
get_path(Permissions) => Permissions,
|
238
|
-
get_path(TirePressure) => TirePressure,
|
239
|
-
get_path(Vin) => Vin,
|
240
|
-
}
|
241
|
-
end
|
242
|
-
|
243
|
-
# @private
|
244
|
-
BatchItemResponse = Struct.new(:body, :status, :headers) do
|
245
|
-
def body_with_meta
|
246
|
-
body.merge(meta: headers)
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
def get_batch_request_body(attributes)
|
251
|
-
attributes = validated_attributes(attributes)
|
252
|
-
requests = attributes.each_with_object([]) do |item, requests|
|
253
|
-
requests << { path: allowed_attributes[item] }
|
254
|
-
end
|
255
|
-
{ requests: requests }
|
256
|
-
end
|
257
|
-
|
258
|
-
def process_batch_response(responses)
|
259
|
-
inverted_map = allowed_attributes.invert
|
260
|
-
responses["responses"].each_with_object({}) do |response, result|
|
261
|
-
item_response = BatchItemResponse.new(response["body"], response["code"], response["headers"])
|
262
|
-
error = get_error(item_response)
|
263
|
-
path = response["path"]
|
264
|
-
result[inverted_map[path]] = error || get_object(path_to_class[path], item_response.body_with_meta)
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def validated_attributes(attributes)
|
269
|
-
attributes.map!(&:to_sym)
|
270
|
-
unsupported_attributes = (attributes - allowed_attributes.keys) || []
|
271
|
-
unless unsupported_attributes.empty?
|
272
|
-
message = "Unsupported attribute(s) requested in batch - #{unsupported_attributes.join(',')}"
|
273
|
-
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])
|
274
185
|
end
|
275
|
-
attributes
|
276
186
|
end
|
277
187
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
def
|
286
|
-
|
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)
|
287
198
|
end
|
288
199
|
|
289
|
-
|
290
|
-
|
291
|
-
|
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))
|
292
209
|
end
|
293
210
|
|
294
|
-
|
295
|
-
|
296
|
-
|
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)
|
297
224
|
end
|
298
225
|
|
299
|
-
|
300
|
-
|
301
|
-
|
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)
|
302
236
|
end
|
303
237
|
end
|
304
238
|
end
|