smartcar 3.8.0 → 3.9.5
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/.buddy/cd.yml +29 -0
- data/.buddy/ci.yml +69 -0
- data/.rubocop.yml +5 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +3 -1
- data/lib/smartcar/auth_client.rb +23 -8
- data/lib/smartcar/utils.rb +90 -8
- data/lib/smartcar/vehicle.rb +131 -0
- data/lib/smartcar/version.rb +1 -1
- data/lib/smartcar.rb +106 -0
- data/lib/smartcar_error.rb +1 -1
- metadata +4 -3
- data/.travis.yml +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 516891931fbdcdde2425bfd191721725ac0461195f164be2f2dfe231412b2db1
|
|
4
|
+
data.tar.gz: 463981a1ae045345f3ed8aaeadaa894dea6bc2effe88ebed1500c90aeb94b22f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f5f916ff6589a296048636071fd0227ad0446078a8bdab9e34be8dbcef278f799b3c91369d5623be77a11efddf8781b4e07af77a36966fa46ebaa2c03310bd60
|
|
7
|
+
data.tar.gz: 7b8e0ebbf68942b14d29946039a10aa16b6de2f03d1d737b86a67245be4e53a656654857449716e0e69faf0a9289379257c3ff0f6b04fa21fc3c9c771cfa1e55
|
data/.buddy/cd.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
- pipeline: cd
|
|
2
|
+
name: CD
|
|
3
|
+
fail_on_prepare_env_warning: true
|
|
4
|
+
events:
|
|
5
|
+
- type: PUSH
|
|
6
|
+
refs:
|
|
7
|
+
- refs/heads/master
|
|
8
|
+
actions:
|
|
9
|
+
- action: semantic release
|
|
10
|
+
type: BUILD
|
|
11
|
+
shell: BASH
|
|
12
|
+
docker_image_name: library/ruby
|
|
13
|
+
docker_image_tag: 2.7
|
|
14
|
+
execute_commands:
|
|
15
|
+
- "set -e # Exit immediately if a command exits with non-zero status"
|
|
16
|
+
- export GEM_NAME="smartcar"
|
|
17
|
+
- export GEMSPEC_FILE="ruby-sdk.gemspec"
|
|
18
|
+
- 'export GEM_HOST_API_KEY="${RUBYGEMSAPI_KEY}"'
|
|
19
|
+
- 'if [ "${BUDDY_EXECUTION_TAG}" != "" ] && [ "${BUDDY_EXECUTION_BRANCH}" == "master" ]; then'
|
|
20
|
+
- ' echo "Tagged commit on master branch detected, proceeding with deployment..."'
|
|
21
|
+
- " gem install bundler -v 2.3.1"
|
|
22
|
+
- " gem build ${GEMSPEC_FILE}"
|
|
23
|
+
- ' GEM_VERSION=$(ruby -e "require ''rubygems''; spec = Gem::Specification::load(''${GEMSPEC_FILE}''); puts spec.version")'
|
|
24
|
+
- ' echo "Publishing ${GEM_NAME} version ${GEM_VERSION} to RubyGems..."'
|
|
25
|
+
- ' gem push "${GEM_NAME}-${GEM_VERSION}.gem"'
|
|
26
|
+
- ' echo "Deployment to RubyGems completed successfully"'
|
|
27
|
+
- else
|
|
28
|
+
- ' echo "Skipping deployment: This is not a tagged commit on the master branch"'
|
|
29
|
+
- fi
|
data/.buddy/ci.yml
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
- pipeline: ci
|
|
2
|
+
name: CI
|
|
3
|
+
events:
|
|
4
|
+
- type: PUSH
|
|
5
|
+
refs:
|
|
6
|
+
- refs/heads/*
|
|
7
|
+
fail_on_prepare_env_warning: true
|
|
8
|
+
actions:
|
|
9
|
+
- action: Run tests
|
|
10
|
+
type: BUILD
|
|
11
|
+
docker_image_name: library/ruby
|
|
12
|
+
docker_image_tag: 3.3
|
|
13
|
+
execute_commands:
|
|
14
|
+
- "# Set Firefox environment variables to help with headless operation"
|
|
15
|
+
- Xvfb :99 -screen 0 1280x1024x24 &
|
|
16
|
+
- export DISPLAY=:99.0
|
|
17
|
+
- export MOZ_HEADLESS=1
|
|
18
|
+
- export MOZ_NO_REMOTE=1
|
|
19
|
+
- "export PATH=${HOME}/firefox-latest/firefox:$PATH"
|
|
20
|
+
- firefox --version
|
|
21
|
+
- firefox &
|
|
22
|
+
- ""
|
|
23
|
+
- "export PATH=${HOME}/geckodriver:$PATH"
|
|
24
|
+
- geckodriver --version
|
|
25
|
+
- ""
|
|
26
|
+
- export BUNDLE_GEMFILE=$PWD/Gemfile
|
|
27
|
+
- gem install bundler -v 2.3.1
|
|
28
|
+
- "bundle install --jobs=3 --retry=3 --deployment --path=${BUNDLE_PATH:-vendor/bundle}"
|
|
29
|
+
- ""
|
|
30
|
+
- export MODE=test
|
|
31
|
+
- bundle exec rake
|
|
32
|
+
setup_commands:
|
|
33
|
+
- apt-get update
|
|
34
|
+
- apt-get install -y \
|
|
35
|
+
- ' libgtk-3-0 \'
|
|
36
|
+
- ' libasound2 \'
|
|
37
|
+
- ' libdbus-glib-1-2 \'
|
|
38
|
+
- ' libx11-xcb1 \'
|
|
39
|
+
- ' libxt6 \'
|
|
40
|
+
- ' libnss3 \'
|
|
41
|
+
- ' libxtst6 \'
|
|
42
|
+
- ' libxss1 \'
|
|
43
|
+
- ' libpci3 \'
|
|
44
|
+
- ' libatk1.0-0 \'
|
|
45
|
+
- ' libatk-bridge2.0-0 \'
|
|
46
|
+
- ' libcups2 \'
|
|
47
|
+
- ' libdrm2 \'
|
|
48
|
+
- ' libxcomposite1 \'
|
|
49
|
+
- ' libxdamage1 \'
|
|
50
|
+
- ' libxfixes3 \'
|
|
51
|
+
- ' libxkbcommon0 \'
|
|
52
|
+
- ' libxrandr2 \'
|
|
53
|
+
- " xvfb"
|
|
54
|
+
- ""
|
|
55
|
+
- ""
|
|
56
|
+
- "# Download the latest Firefox"
|
|
57
|
+
- wget -O /tmp/firefox-latest.tar.xz 'https://download.mozilla.org/?product=firefox-latest&lang=en-US&os=linux64'
|
|
58
|
+
- "mkdir -p ${HOME}/firefox-latest"
|
|
59
|
+
- "tar -xJf /tmp/firefox-latest.tar.xz -C ${HOME}/firefox-latest"
|
|
60
|
+
- ""
|
|
61
|
+
- "# Download Geckodriver"
|
|
62
|
+
- wget -O /tmp/geckodriver.tar.xz 'https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz'
|
|
63
|
+
- "mkdir -p ${HOME}/geckodriver"
|
|
64
|
+
- "tar -xzf /tmp/geckodriver.tar.xz -C ${HOME}/geckodriver"
|
|
65
|
+
services:
|
|
66
|
+
- type: SELENIUM_FIREFOX
|
|
67
|
+
connection:
|
|
68
|
+
host: selenium-ff
|
|
69
|
+
shell: BASH
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
data/lib/smartcar/auth_client.rb
CHANGED
|
@@ -13,14 +13,16 @@ module Smartcar
|
|
|
13
13
|
# @param [Hash] options
|
|
14
14
|
# @option options[:client_id] [String] - Client ID, if not passed fallsback to ENV['SMARTCAR_CLIENT_ID']
|
|
15
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
|
|
16
|
+
# @option options[:redirect_uri] [String] - Redirect URI registered in the
|
|
17
|
+
# application settings. The given URL must exactly match one of the registered URLs.
|
|
18
|
+
# This parameter is optional and should normally be set within the Smartcar Dashboard.
|
|
17
19
|
# @option options[:test_mode] [Boolean] - [DEPRECATED], please use `mode` instead.
|
|
18
20
|
# Launch Smartcar Connect in [test mode](https://smartcar.com/docs/guides/testing/).
|
|
19
21
|
# @option options[:mode] [String] - Determine what mode Smartcar Connect should be launched in.
|
|
20
22
|
# Should be one of test, live or simulated.
|
|
21
23
|
# @return [Smartcar::AuthClient] Returns a Smartcar::AuthClient Object that has other methods
|
|
22
24
|
def initialize(options)
|
|
23
|
-
options[:redirect_uri] ||= get_config('SMARTCAR_REDIRECT_URI')
|
|
25
|
+
options[:redirect_uri] ||= get_config('SMARTCAR_REDIRECT_URI', { nullable: true })
|
|
24
26
|
options[:client_id] ||= get_config('SMARTCAR_CLIENT_ID')
|
|
25
27
|
options[:client_secret] ||= get_config('SMARTCAR_CLIENT_SECRET')
|
|
26
28
|
options[:auth_origin] = ENV['SMARTCAR_AUTH_ORIGIN'] || AUTH_ORIGIN
|
|
@@ -30,9 +32,11 @@ module Smartcar
|
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
# Generate the OAuth authorization URL.
|
|
33
|
-
# @param
|
|
35
|
+
# @param scope_or_options [Array<String>, Hash] Array of permissions that specify what the user can access
|
|
34
36
|
# EXAMPLE : ['read_odometer', 'read_vehicle_info', 'required:read_location']
|
|
35
|
-
#
|
|
37
|
+
# For further details refer to https://smartcar.com/docs/guides/scope/
|
|
38
|
+
# This parameter is optional and should normally be set within the Smartcar Dashboard.
|
|
39
|
+
# Can also be a Hash of options if scope is not needed.
|
|
36
40
|
# @param [Hash] options
|
|
37
41
|
# @option options[:force_prompt] [Boolean] - Setting `force_prompt` to
|
|
38
42
|
# `true` will show the permissions approval screen on every authentication
|
|
@@ -53,9 +57,19 @@ module Smartcar
|
|
|
53
57
|
# For a complete list of supported makes, please see our
|
|
54
58
|
# [API Reference](https://smartcar.com/docs/api#authorization) documentation.
|
|
55
59
|
# @option options[:flags] [Hash] - A hash of flag name string as key and a string or boolean value.
|
|
60
|
+
# @option options[:user] [String] - An optional unique identifier for a vehicle owner. This identifier
|
|
61
|
+
# is used to aggregate analytics across Connect sessions for each vehicle owner.
|
|
56
62
|
#
|
|
57
63
|
# @return [String] Authorization URL string
|
|
58
|
-
def get_auth_url(
|
|
64
|
+
def get_auth_url(scope_or_options = {}, options = {})
|
|
65
|
+
scope = nil
|
|
66
|
+
if scope_or_options.is_a?(Array)
|
|
67
|
+
scope = scope_or_options
|
|
68
|
+
options ||= {}
|
|
69
|
+
elsif scope_or_options.is_a?(Hash)
|
|
70
|
+
options = scope_or_options
|
|
71
|
+
end
|
|
72
|
+
|
|
59
73
|
initialize_auth_parameters(scope, options)
|
|
60
74
|
add_single_select_options(options[:single_select])
|
|
61
75
|
connect_client.auth_code.authorize_url(@auth_parameters)
|
|
@@ -124,14 +138,15 @@ module Smartcar
|
|
|
124
138
|
def initialize_auth_parameters(scope, options)
|
|
125
139
|
@auth_parameters = {
|
|
126
140
|
response_type: CODE,
|
|
127
|
-
|
|
128
|
-
mode: mode,
|
|
129
|
-
scope: scope.join(' ')
|
|
141
|
+
mode: mode
|
|
130
142
|
}
|
|
143
|
+
@auth_parameters[:redirect_uri] = redirect_uri if redirect_uri
|
|
144
|
+
@auth_parameters[:scope] = scope.join(' ') if scope
|
|
131
145
|
@auth_parameters[:approval_prompt] = options[:force_prompt] ? FORCE : AUTO unless options[:force_prompt].nil?
|
|
132
146
|
@auth_parameters[:state] = options[:state] if options[:state]
|
|
133
147
|
@auth_parameters[:make] = options[:make_bypass] if options[:make_bypass]
|
|
134
148
|
@auth_parameters[:flags] = build_flags(options[:flags]) if options[:flags]
|
|
149
|
+
@auth_parameters[:user] = options[:user] if options[:user]
|
|
135
150
|
end
|
|
136
151
|
|
|
137
152
|
def add_single_select_options(single_select)
|
data/lib/smartcar/utils.rb
CHANGED
|
@@ -15,12 +15,19 @@ module Smartcar
|
|
|
15
15
|
|
|
16
16
|
# gets a given env variable, checks for existence and throws exception if not present
|
|
17
17
|
# @param config_name [String] key of the env variable
|
|
18
|
+
# @param options [Hash] options hash, supports :nullable key (default: false)
|
|
18
19
|
#
|
|
19
20
|
# @return [String] value of the env variable
|
|
20
|
-
def get_config(config_name)
|
|
21
|
+
def get_config(config_name, options = {})
|
|
21
22
|
# ENV.MODE is set to test by e2e tests.
|
|
22
23
|
config_name = "E2E_#{config_name}" if ENV['MODE'] == 'test'
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
unless ENV[config_name]
|
|
26
|
+
nullable = options.fetch(:nullable, false)
|
|
27
|
+
return nil if nullable
|
|
28
|
+
|
|
29
|
+
raise Smartcar::ConfigNotFound, "Environment variable #{config_name} not found !"
|
|
30
|
+
end
|
|
24
31
|
|
|
25
32
|
ENV.fetch(config_name, nil)
|
|
26
33
|
end
|
|
@@ -34,29 +41,101 @@ module Smartcar
|
|
|
34
41
|
#
|
|
35
42
|
# @return [RecursiveOpenStruct]
|
|
36
43
|
def json_to_ostruct(hash)
|
|
37
|
-
|
|
44
|
+
convert_to_ostruct_recursively(hash)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Helper method to recursively convert hashes and arrays to RecursiveOpenStruct
|
|
48
|
+
def convert_to_ostruct_recursively(obj)
|
|
49
|
+
case obj
|
|
50
|
+
when Array
|
|
51
|
+
obj.map { |el| convert_to_ostruct_recursively(el) }
|
|
52
|
+
when Hash
|
|
53
|
+
RecursiveOpenStruct.new(
|
|
54
|
+
obj.transform_values { |value| convert_to_ostruct_recursively(value) },
|
|
55
|
+
recurse_over_arrays: true
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
else
|
|
59
|
+
obj
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Helper method to convert string from camelCase or kebab-case to snake_case
|
|
64
|
+
def to_snake_case(str)
|
|
65
|
+
str.to_s
|
|
66
|
+
.gsub('-', '_') # Convert kebab-case to snake_case
|
|
67
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
68
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
69
|
+
.downcase
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Helper method to recursively convert hash keys to snake_case
|
|
73
|
+
def deep_transform_keys_to_snake_case(obj)
|
|
74
|
+
case obj
|
|
75
|
+
when Array
|
|
76
|
+
obj.map { |el| deep_transform_keys_to_snake_case(el) }
|
|
77
|
+
when Hash
|
|
78
|
+
obj.each_with_object({}) do |(key, value), result|
|
|
79
|
+
new_key = to_snake_case(key)
|
|
80
|
+
result[new_key] = deep_transform_keys_to_snake_case(value)
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
obj
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Parse date string to DateTime or return nil on error
|
|
88
|
+
def parse_date_safely(date_string)
|
|
89
|
+
return nil unless date_string
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
DateTime.parse(date_string)
|
|
93
|
+
rescue ArgumentError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
38
96
|
end
|
|
39
97
|
|
|
40
98
|
def build_meta(headers)
|
|
41
99
|
meta_hash = {
|
|
42
100
|
'sc-data-age' => :data_age,
|
|
43
101
|
'sc-unit-system' => :unit_system,
|
|
44
|
-
'sc-request-id' => :request_id
|
|
102
|
+
'sc-request-id' => :request_id,
|
|
103
|
+
'sc-fetched-at' => :fetched_at
|
|
45
104
|
}.each_with_object({}) do |(header_name, key), meta|
|
|
46
105
|
meta[key] = headers[header_name] if headers[header_name]
|
|
47
106
|
end
|
|
48
|
-
meta = json_to_ostruct(meta_hash)
|
|
49
|
-
meta.data_age &&= DateTime.parse(meta.data_age)
|
|
50
107
|
|
|
108
|
+
meta = json_to_ostruct(meta_hash)
|
|
109
|
+
meta.data_age = parse_date_safely(meta.data_age)
|
|
110
|
+
meta.fetched_at = parse_date_safely(meta.fetched_at)
|
|
51
111
|
meta
|
|
52
112
|
end
|
|
53
113
|
|
|
54
114
|
def build_response(body, headers)
|
|
55
|
-
|
|
56
|
-
|
|
115
|
+
# Check if body is already parsed (i.e., a Hash) or if it needs parsing (i.e., a String)
|
|
116
|
+
body_data = body.is_a?(String) ? JSON.parse(body) : body
|
|
117
|
+
if body_data.is_a?(Array)
|
|
118
|
+
response = OpenStruct.new(items: json_to_ostruct(body), meta: build_meta(headers))
|
|
119
|
+
else
|
|
120
|
+
response = json_to_ostruct(body)
|
|
121
|
+
response.meta = build_meta(headers)
|
|
122
|
+
end
|
|
57
123
|
response
|
|
58
124
|
end
|
|
59
125
|
|
|
126
|
+
def build_v3_response(body, headers)
|
|
127
|
+
body_data = body.is_a?(String) ? JSON.parse(body) : body
|
|
128
|
+
headers_data = headers.is_a?(String) ? JSON.parse(headers) : headers
|
|
129
|
+
|
|
130
|
+
body_snake = deep_transform_keys_to_snake_case(body_data)
|
|
131
|
+
headers_snake = deep_transform_keys_to_snake_case(headers_data)
|
|
132
|
+
|
|
133
|
+
OpenStruct.new(
|
|
134
|
+
body: json_to_ostruct(body_snake),
|
|
135
|
+
headers: json_to_ostruct(headers_snake)
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
60
139
|
def build_aliases(response, aliases)
|
|
61
140
|
(aliases || []).each do |original_name, alias_name|
|
|
62
141
|
# rubocop:disable Lint/SymbolConversion
|
|
@@ -129,6 +208,9 @@ module Smartcar
|
|
|
129
208
|
|
|
130
209
|
return :lock_status if path == '/security'
|
|
131
210
|
|
|
211
|
+
return :diagnostic_system_status if path == '/diagnostics/system_status'
|
|
212
|
+
return :diagnostic_trouble_codes if path == '/diagnostics/dtcs'
|
|
213
|
+
|
|
132
214
|
path.split('/').reject(&:empty?).join('_').to_sym
|
|
133
215
|
end
|
|
134
216
|
|
data/lib/smartcar/vehicle.rb
CHANGED
|
@@ -25,10 +25,17 @@ module Smartcar
|
|
|
25
25
|
aliases: { 'percentRemaining' => 'percentage_remaining' }
|
|
26
26
|
},
|
|
27
27
|
battery_capacity: { path: proc { |id| "/vehicles/#{id}/battery/capacity" } },
|
|
28
|
+
nominal_capacity: { path: proc { |id| "/vehicles/#{id}/battery/nominal_capacity" } },
|
|
28
29
|
charge: {
|
|
29
30
|
path: proc { |id| "/vehicles/#{id}/charge" },
|
|
30
31
|
aliases: { 'isPluggedIn' => 'is_plugged_in?' }
|
|
31
32
|
},
|
|
33
|
+
diagnostic_system_status: {
|
|
34
|
+
path: proc { |id| "/vehicles/#{id}/diagnostics/system_status" }
|
|
35
|
+
},
|
|
36
|
+
diagnostic_trouble_codes: {
|
|
37
|
+
path: proc { |id| "/vehicles/#{id}/diagnostics/dtcs" }
|
|
38
|
+
},
|
|
32
39
|
engine_oil: {
|
|
33
40
|
path: proc { |id| "/vehicles/#{id}/engine/oil" },
|
|
34
41
|
aliases: { 'lifeRemaining' => 'life_remaining' }
|
|
@@ -42,6 +49,16 @@ module Smartcar
|
|
|
42
49
|
},
|
|
43
50
|
location: { path: proc { |id| "/vehicles/#{id}/location" } },
|
|
44
51
|
odometer: { path: proc { |id| "/vehicles/#{id}/odometer" } },
|
|
52
|
+
service_history: {
|
|
53
|
+
path: proc { |id, start_date = nil, end_date = nil|
|
|
54
|
+
base_path = "/vehicles/#{id}/service/history"
|
|
55
|
+
query_params = []
|
|
56
|
+
query_params << "start_date=#{start_date}" unless start_date.nil?
|
|
57
|
+
query_params << "end_date=#{end_date}" unless end_date.nil?
|
|
58
|
+
"#{base_path}?#{query_params.join('&')}" unless query_params.empty?
|
|
59
|
+
},
|
|
60
|
+
skip: true
|
|
61
|
+
},
|
|
45
62
|
tire_pressure: {
|
|
46
63
|
path: proc { |id| "/vehicles/#{id}/tires/pressure" },
|
|
47
64
|
aliases: {
|
|
@@ -129,6 +146,14 @@ module Smartcar
|
|
|
129
146
|
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-ev-battery-capacity
|
|
130
147
|
# and a meta attribute with the relevant items from response headers.
|
|
131
148
|
|
|
149
|
+
# @!method nominal_capacity()
|
|
150
|
+
# Returns a list of nominal rated battery capacities for a vehicle.
|
|
151
|
+
#
|
|
152
|
+
# API Documentation https://smartcar.com/docs/api-reference/get-nominal-capacity
|
|
153
|
+
#
|
|
154
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api-reference/get-nominal-capacity
|
|
155
|
+
# and a meta attribute with the relevant items from response headers.
|
|
156
|
+
|
|
132
157
|
# @!method charge()
|
|
133
158
|
# Returns the current charge status of the vehicle.
|
|
134
159
|
#
|
|
@@ -137,6 +162,20 @@ module Smartcar
|
|
|
137
162
|
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-ev-battery
|
|
138
163
|
# and a meta attribute with the relevant items from response headers.
|
|
139
164
|
|
|
165
|
+
# @!method diagnostic_system_status()
|
|
166
|
+
# Returns the status of the vehicle's diagnostic systems.
|
|
167
|
+
#
|
|
168
|
+
# API Documentation: https://smartcar.com/docs/api#get-diagnostic-system-status
|
|
169
|
+
#
|
|
170
|
+
# @return [OpenStruct] An object representing the diagnostic system status and metadata.
|
|
171
|
+
|
|
172
|
+
# @!method diagnostic_trouble_codes()
|
|
173
|
+
# Retrieves diagnostic trouble codes (DTCs) from the vehicle.
|
|
174
|
+
#
|
|
175
|
+
# API Documentation: https://smartcar.com/docs/api#get-diagnostic-trouble-codes
|
|
176
|
+
#
|
|
177
|
+
# @return [OpenStruct] An object containing the DTCs and metadata.
|
|
178
|
+
|
|
140
179
|
# @!method engine_oil()
|
|
141
180
|
# Returns the remaining life span of a vehicle's engine oil
|
|
142
181
|
#
|
|
@@ -232,6 +271,40 @@ module Smartcar
|
|
|
232
271
|
build_response(response, headers)
|
|
233
272
|
end
|
|
234
273
|
|
|
274
|
+
# Retrieves a list of service records for the vehicle, optionally filtered by a specified date range.
|
|
275
|
+
# If no dates are specified, the method defaults to returning records from the last year.
|
|
276
|
+
#
|
|
277
|
+
# This method calls the Smartcar API to fetch the vehicle's service history and processes the
|
|
278
|
+
# response to return structured data.
|
|
279
|
+
#
|
|
280
|
+
# @param start_date [String, nil] the start date of the period from which records are retrieved (inclusive).
|
|
281
|
+
# Expected in 'YYYY-MM-DD' format. If nil, defaults to one year ago from today.
|
|
282
|
+
# @param end_date [String, nil] the end date of the period until which records are retrieved (inclusive).
|
|
283
|
+
# Expected in 'YYYY-MM-DD' format. If nil, defaults to today's date.
|
|
284
|
+
#
|
|
285
|
+
# @return [OpenStruct] An object representing the parsed JSON response from the API, with service history
|
|
286
|
+
# data and metadata extracted from the response headers.
|
|
287
|
+
#
|
|
288
|
+
# Example usage:
|
|
289
|
+
# vehicle.service_history('2021-01-01', '2021-12-31')
|
|
290
|
+
#
|
|
291
|
+
# Note: This method assumes that the necessary error handling is embedded within the `get` method or handled
|
|
292
|
+
# externally when exceptions arise due to network issues, API limitations, or data encoding problems.
|
|
293
|
+
def service_history(start_date = nil, end_date = nil)
|
|
294
|
+
start_date, end_date = default_date_range if start_date.nil? || end_date.nil?
|
|
295
|
+
|
|
296
|
+
path = METHODS[:service_history][:path].call(id, start_date, end_date)
|
|
297
|
+
body, headers = get(path, @query_params)
|
|
298
|
+
build_response(body, headers)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Utility method to provide default dates
|
|
302
|
+
def default_date_range
|
|
303
|
+
end_date = DateTime.now.new_offset(0).to_date
|
|
304
|
+
start_date = end_date - 365
|
|
305
|
+
[start_date.to_s, end_date.to_s]
|
|
306
|
+
end
|
|
307
|
+
|
|
235
308
|
# Subscribe the vehicle to given webhook Id.
|
|
236
309
|
#
|
|
237
310
|
# @param webhook_id [String] Webhook id to subscribe to
|
|
@@ -298,6 +371,34 @@ module Smartcar
|
|
|
298
371
|
process_batch_response(response, headers)
|
|
299
372
|
end
|
|
300
373
|
|
|
374
|
+
# Retrieve a specific signal by signal code from the vehicle.
|
|
375
|
+
# Uses the Vehicle API (v3) endpoint.
|
|
376
|
+
#
|
|
377
|
+
# API Documentation - https://smartcar.com/docs/api-reference/get-signal
|
|
378
|
+
#
|
|
379
|
+
# @param signal_code [String] The code of the signal to retrieve.
|
|
380
|
+
#
|
|
381
|
+
# @return [OpenStruct] An object with a "body" attribute containing the signal data
|
|
382
|
+
# and a "headers" attribute containing the response headers.
|
|
383
|
+
def get_signal(signal_code)
|
|
384
|
+
raise InvalidParameterValue.new, 'signal_code is a required field' if signal_code.nil? || signal_code.empty?
|
|
385
|
+
|
|
386
|
+
path = "/vehicles/#{id}/signals/#{signal_code}"
|
|
387
|
+
request_v3(path)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Retrieve all available signals from the vehicle.
|
|
391
|
+
# Uses the Vehicle API (v3) endpoint.
|
|
392
|
+
#
|
|
393
|
+
# API Documentation - https://smartcar.com/docs/api-reference/get-signals
|
|
394
|
+
#
|
|
395
|
+
# @return [OpenStruct] An object with a "body" attribute containing all signals data
|
|
396
|
+
# and a "headers" attribute containing the response headers.
|
|
397
|
+
def get_signals
|
|
398
|
+
path = "/vehicles/#{id}/signals"
|
|
399
|
+
request_v3(path)
|
|
400
|
+
end
|
|
401
|
+
|
|
301
402
|
# General purpose method to make requests to the Smartcar API - can be
|
|
302
403
|
# used to make requests to brand specific endpoints.
|
|
303
404
|
#
|
|
@@ -314,5 +415,35 @@ module Smartcar
|
|
|
314
415
|
meta = build_meta(headers)
|
|
315
416
|
json_to_ostruct({ body: raw_response, meta: meta })
|
|
316
417
|
end
|
|
418
|
+
|
|
419
|
+
private
|
|
420
|
+
|
|
421
|
+
# Makes a request to the v3 vehicles API using the vehicle API origin
|
|
422
|
+
#
|
|
423
|
+
# @param path [String] The API path to request.
|
|
424
|
+
#
|
|
425
|
+
# @return [OpenStruct] An object with body and headers attributes.
|
|
426
|
+
def request_v3(path)
|
|
427
|
+
vehicle_service = Faraday.new(
|
|
428
|
+
url: ENV['SMARTCAR_VEHICLE_API_ORIGIN'] || VEHICLE_API_ORIGIN,
|
|
429
|
+
request: { timeout: DEFAULT_REQUEST_TIMEOUT }
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
response = vehicle_service.get do |request|
|
|
433
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
434
|
+
request.headers['Content-Type'] = 'application/json'
|
|
435
|
+
request.headers['User-Agent'] =
|
|
436
|
+
"Smartcar/#{VERSION} (#{RbConfig::CONFIG['host_os']}; #{RbConfig::CONFIG['arch']}) Ruby v#{RUBY_VERSION}"
|
|
437
|
+
|
|
438
|
+
complete_path = "/v3#{path}"
|
|
439
|
+
complete_path += "?#{URI.encode_www_form(@query_params.compact)}" unless @query_params.empty?
|
|
440
|
+
request.url complete_path
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
handle_error(response)
|
|
444
|
+
|
|
445
|
+
body = response.body.empty? ? '{}' : response.body
|
|
446
|
+
build_v3_response(body, response.headers.to_h)
|
|
447
|
+
end
|
|
317
448
|
end
|
|
318
449
|
end
|
data/lib/smartcar/version.rb
CHANGED
data/lib/smartcar.rb
CHANGED
|
@@ -15,6 +15,7 @@ module Smartcar
|
|
|
15
15
|
|
|
16
16
|
# Host to connect to smartcar
|
|
17
17
|
API_ORIGIN = 'https://api.smartcar.com/'
|
|
18
|
+
VEHICLE_API_ORIGIN = 'https://vehicle.api.smartcar.com'
|
|
18
19
|
MANAGEMENT_API_ORIGIN = 'https://management.smartcar.com'
|
|
19
20
|
PATHS = {
|
|
20
21
|
compatibility: '/compatibility',
|
|
@@ -101,6 +102,49 @@ module Smartcar
|
|
|
101
102
|
))
|
|
102
103
|
end
|
|
103
104
|
|
|
105
|
+
# Module method to retrieve the Smartcar compatibility matrix for a given region.
|
|
106
|
+
# Provides the ability to filter by scope, make, and type.
|
|
107
|
+
#
|
|
108
|
+
# A compatible vehicle is a vehicle that:
|
|
109
|
+
# 1. has the hardware required for internet connectivity,
|
|
110
|
+
# 2. belongs to the makes and models Smartcar supports, and
|
|
111
|
+
# 3. supports the permissions.
|
|
112
|
+
#
|
|
113
|
+
# API Documentation - https://smartcar.com/docs/api-reference/compatibility/by-region-and-make
|
|
114
|
+
# @param region [String] One of the following regions: US, CA, or EUROPE
|
|
115
|
+
# @param options [Hash] Optional parameters
|
|
116
|
+
# @option options [Array<String>] :scope List of permissions to filter the matrix by
|
|
117
|
+
# @option options [String, Array<String>] :make List of makes to filter the matrix by
|
|
118
|
+
# (space-separated string or array)
|
|
119
|
+
# @option options [String] :type Engine type to filter the matrix by (e.g., "ICE", "HEV", "PHEV", "BEV")
|
|
120
|
+
# @option options [String] :client_id Client ID that overrides ENV
|
|
121
|
+
# @option options [String] :client_secret Client Secret that overrides ENV
|
|
122
|
+
# @option options [String] :version API version to use, defaults to what is globally set
|
|
123
|
+
# @option options [String] :mode Determine what mode Smartcar Connect should be launched in.
|
|
124
|
+
# Should be one of test, live or simulated.
|
|
125
|
+
# @option options [Faraday::Connection] :service Optional connection object to be used for requests
|
|
126
|
+
#
|
|
127
|
+
# @return [OpenStruct] An object representing the compatibility matrix organized by make,
|
|
128
|
+
# with each make containing an array of compatible models and a meta attribute with response headers.
|
|
129
|
+
def get_compatibility_matrix(region, options = {})
|
|
130
|
+
raise Base::InvalidParameterValue.new, 'region is a required field' if region.nil? || region.empty?
|
|
131
|
+
|
|
132
|
+
base_object = Base.new(
|
|
133
|
+
{
|
|
134
|
+
version: options[:version] || Smartcar.get_api_version,
|
|
135
|
+
auth_type: Base::BASIC,
|
|
136
|
+
service: options[:service]
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
base_object.token = generate_basic_auth(options, base_object)
|
|
141
|
+
|
|
142
|
+
base_object.build_response(*base_object.get(
|
|
143
|
+
"#{PATHS[:compatibility]}/matrix",
|
|
144
|
+
build_compatibility_matrix_params(region, options)
|
|
145
|
+
))
|
|
146
|
+
end
|
|
147
|
+
|
|
104
148
|
# Module method Used to get user id
|
|
105
149
|
#
|
|
106
150
|
# API Documentation - https://smartcar.com/docs/api#get-user
|
|
@@ -224,6 +268,45 @@ module Smartcar
|
|
|
224
268
|
))
|
|
225
269
|
end
|
|
226
270
|
|
|
271
|
+
# Module method to retrieve vehicle information using the v3 API.
|
|
272
|
+
#
|
|
273
|
+
# API Documentation - https://smartcar.com/docs/api-reference/get-vehicle
|
|
274
|
+
# @param vehicle_id [String] The vehicle ID
|
|
275
|
+
# @param token [String] Access token
|
|
276
|
+
# @param options [Hash] Optional parameters
|
|
277
|
+
# @option options [Hash] :flags A hash of flag name string as key and a string or boolean value.
|
|
278
|
+
# @option options [Faraday::Connection] :service Optional connection object to be used for requests
|
|
279
|
+
#
|
|
280
|
+
# @return [OpenStruct] An object with a "body" attribute containing the vehicle data
|
|
281
|
+
# and a "headers" attribute containing the response headers.
|
|
282
|
+
def get_vehicle(vehicle_id:, token:, options: {})
|
|
283
|
+
raise Base::InvalidParameterValue.new, 'vehicle_id is a required field' if vehicle_id.nil? || vehicle_id.empty?
|
|
284
|
+
raise Base::InvalidParameterValue.new, 'token is a required field' if token.nil? || token.empty?
|
|
285
|
+
|
|
286
|
+
vehicle_service = Faraday.new(
|
|
287
|
+
url: ENV['SMARTCAR_VEHICLE_API_ORIGIN'] || VEHICLE_API_ORIGIN,
|
|
288
|
+
request: { timeout: DEFAULT_REQUEST_TIMEOUT }
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
query_params = { flags: stringify_params(options[:flags]) }
|
|
292
|
+
|
|
293
|
+
response = vehicle_service.get do |request|
|
|
294
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
295
|
+
request.headers['Content-Type'] = 'application/json'
|
|
296
|
+
request.headers['User-Agent'] =
|
|
297
|
+
"Smartcar/#{VERSION} (#{RbConfig::CONFIG['host_os']}; #{RbConfig::CONFIG['arch']}) Ruby v#{RUBY_VERSION}"
|
|
298
|
+
|
|
299
|
+
complete_path = "/v3/vehicles/#{vehicle_id}"
|
|
300
|
+
complete_path += "?#{URI.encode_www_form(query_params.compact)}" unless query_params.empty?
|
|
301
|
+
request.url complete_path
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
raise build_error(response.status, response.body, response.headers) unless [200, 204].include?(response.status)
|
|
305
|
+
|
|
306
|
+
body = response.body.empty? ? '{}' : response.body
|
|
307
|
+
build_v3_response(body, response.headers.to_h)
|
|
308
|
+
end
|
|
309
|
+
|
|
227
310
|
# returns auth token for Basic vehicle management auth
|
|
228
311
|
#
|
|
229
312
|
# @return [String] Base64 encoding of default:amt
|
|
@@ -252,6 +335,29 @@ module Smartcar
|
|
|
252
335
|
query_params
|
|
253
336
|
end
|
|
254
337
|
|
|
338
|
+
def build_compatibility_matrix_params(region, options)
|
|
339
|
+
query_params = { region: region }
|
|
340
|
+
|
|
341
|
+
# Handle scope - convert array to space-separated string
|
|
342
|
+
if options[:scope]
|
|
343
|
+
query_params[:scope] = options[:scope].is_a?(Array) ? options[:scope].join(' ') : options[:scope]
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Handle make - convert array to space-separated string
|
|
347
|
+
if options[:make]
|
|
348
|
+
query_params[:make] = options[:make].is_a?(Array) ? options[:make].join(' ') : options[:make]
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Handle type
|
|
352
|
+
query_params[:type] = options[:type] if options[:type]
|
|
353
|
+
|
|
354
|
+
# Handle mode
|
|
355
|
+
mode = determine_mode(options[:test_mode], options[:mode])
|
|
356
|
+
query_params[:mode] = mode unless mode.nil?
|
|
357
|
+
|
|
358
|
+
query_params
|
|
359
|
+
end
|
|
360
|
+
|
|
255
361
|
# returns auth token for Basic auth
|
|
256
362
|
#
|
|
257
363
|
# @return [String] Base64 encoding of CLIENT:SECRET
|
data/lib/smartcar_error.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Custom SmartcarError class to represent errors from Smartcar APIs.
|
|
4
4
|
class SmartcarError < StandardError
|
|
5
|
-
attr_reader :code, :status_code, :request_id, :type, :description, :doc_url, :resolution, :detail, :retry_after
|
|
5
|
+
attr_reader :code, :status_code, :request_id, :type, :description, :doc_url, :resolution, :detail, :retry_after, :suggested_user_message
|
|
6
6
|
|
|
7
7
|
def initialize(status, body, headers)
|
|
8
8
|
@status_code = status
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smartcar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.9.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ashwin Subramanian
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -178,10 +178,11 @@ executables: []
|
|
|
178
178
|
extensions: []
|
|
179
179
|
extra_rdoc_files: []
|
|
180
180
|
files:
|
|
181
|
+
- ".buddy/cd.yml"
|
|
182
|
+
- ".buddy/ci.yml"
|
|
181
183
|
- ".gitignore"
|
|
182
184
|
- ".rspec"
|
|
183
185
|
- ".rubocop.yml"
|
|
184
|
-
- ".travis.yml"
|
|
185
186
|
- ".yardopts"
|
|
186
187
|
- CODE_OF_CONDUCT.md
|
|
187
188
|
- Gemfile
|
data/.travis.yml
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
sudo: false
|
|
2
|
-
language: ruby
|
|
3
|
-
cache: bundler
|
|
4
|
-
services:
|
|
5
|
-
- xvfb
|
|
6
|
-
addons:
|
|
7
|
-
firefox: latest
|
|
8
|
-
rvm:
|
|
9
|
-
- 2.7
|
|
10
|
-
- 3.0
|
|
11
|
-
before_install:
|
|
12
|
-
- gem install bundler -v 2.3.1
|
|
13
|
-
- wget https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz
|
|
14
|
-
- mkdir geckodriver
|
|
15
|
-
- tar -xzf geckodriver-v0.26.0-linux64.tar.gz -C geckodriver
|
|
16
|
-
- export PATH=$PATH:$PWD/geckodriver
|
|
17
|
-
# install:
|
|
18
|
-
# - firefox -headless &
|
|
19
|
-
deploy:
|
|
20
|
-
provider: rubygems
|
|
21
|
-
api_key: $RUBYGEMSAPI_KEY
|
|
22
|
-
gem: smartcar
|
|
23
|
-
gemspec: ruby-sdk.gemspec
|
|
24
|
-
on:
|
|
25
|
-
tags: true
|
|
26
|
-
branch: master
|
|
27
|
-
rvm: 2.7
|
|
28
|
-
skip_cleanup: 'true'
|