smartcar 2.4.0 → 3.0.2
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 -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
|