Skip to content
This repository was archived by the owner on Jul 24, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .yardopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--markup=markdown
--no-private
44 changes: 26 additions & 18 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,50 +1,58 @@
PATH
remote: .
specs:
smartcar (0.1.1)
smartcar (1.0.0)
oauth2 (~> 1.4)

GEM
remote: https://rubygems.org/
specs:
byebug (11.0.1)
byebug (11.1.3)
childprocess (3.0.0)
diff-lcs (1.3)
faraday (0.17.0)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
jwt (2.2.1)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.1.1)
oauth2 (1.4.2)
oauth2 (1.4.4)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
rack (2.0.8)
rake (13.0.1)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.2)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.4)
rack (2.2.2)
rake (12.3.3)
redcarpet (3.5.0)
rspec (3.9.0)
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.2)
rspec-support (~> 3.9.3)
rspec-expectations (3.9.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.1)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.2)
rspec-support (~> 3.9.0)
rspec-support (3.9.3)
rubyzip (2.3.0)
selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 4.0)
rubyzip (>= 1.2.2)

PLATFORMS
ruby

DEPENDENCIES
bundler (~> 2.0)
byebug (~> 11.0)
rake (>= 12.3.3)
rake (~> 12.3, >= 12.3.3)
redcarpet
rspec (~> 3.0)
selenium-webdriver (~> 3.142)
smartcar!

BUNDLED WITH
Expand Down
82 changes: 74 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,63 @@

# Smartcar Ruby SDK [![Gem Version][gem-url]][gem-image]

Ruby gem library to quickly get started with the Smartcar API.

## Overview

The [Smartcar API](https://smartcar.com/docs) lets you read vehicle data
(location, odometer) and send commands to vehicles (lock, unlock) using HTTP requests.

To make requests to a vehicle from a web or mobile application, the end user
must connect their vehicle using
[Smartcar Connect](https://smartcar.com/docs/api#smartcar-connect).
This flow follows the OAuth spec and will return a `code` which can be used to
obtain an access token from Smartcar.

The Smartcar Ruby Gem provides methods to:

1. Generate the link to redirect to Connect.
2. Make a request to Smartcar with the `code` obtained from Connect to obtain an
access and refresh token
3. Make requests to the Smartcar API to read vehicle data and send commands to
vehicles using the access token obtained in step 2.

Before integrating with Smartcar's SDK, you'll need to register an application
in the [Smartcar Developer portal](https://developer.smartcar.com). If you do
not have access to the dashboard, please
[request access](https://smartcar.com/subscribe).

### Flow

- Create a new `AuthClient` object with your `clientId`, `clientSecret`,
`redirectUri`, and required `scope`.
- Redirect the user to Smartcar Connect using `getAuthUrl` or one
of our frontend SDKs.
- The user will login, and then accept or deny your `scope`'s permissions.
- Handle the get request to `redirectUri`.
- If the user accepted your permissions, `req.query.code` will contain an
authorization code.
- Use `exchangeCode` with this code to obtain an access object
containing an access token (lasting 2 hours) and a refresh token
(lasting 60 days).
- Save this access object.
- If the user denied your permissions, `req.query.error` will be set
to `"access_denied"`.
- If you passed a state parameter to `getAuthUrl`, `req.query.state` will
contain the state value.
- Get the user's vehicles with `getVehicleIds`.
- Create a new `Vehicle` object using a `vehicleId` from the previous response,
and the `access_token`.
- Make requests to the Smartcar API.
- Use `exchangeRefreshToken` on your saved `refreshToken` to retrieve a new token
when your `accessToken` expires.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'smartcar-ruby'
gem 'smartcar'
```

And then execute:
Expand All @@ -16,16 +66,16 @@ And then execute:

Or install it yourself as:

$ gem install smartcar-ruby
$ gem install smartcar

## Usage

Setup the environment variables for SMARTCAR_CLIENT_ID and SMARTCAR_SECRET.
Setup the environment variables for CLIENT_ID and CLIENT_SECRET.
```bash
# Get your API keys from https://dashboard.smartcar.com/signup
export SMARTCAR_CLIENT_ID=<client id>
export SMARTCAR_SECRET=<client secret>
```
export CLIENT_ID=<client id>
export CLIENT_SECRET=<client secret>
```

Example Usage for calling the reports API with oAuth token
```ruby
Expand All @@ -36,13 +86,26 @@ Example Usage for calling the reports API with oAuth token
2.5.5 :010 > vehicle = Smartcar::Vehicle.new(token: token, id: ids.first)
=> #<Smartcar::Vehicle:0x00007fbad71aa2b8 @token="56801a5e-6a0b-4d05-a43e-52a4d5e6648f", @id="35e8a7c4-9e5c-4eb6-b552-7509e371669a", @unit_system="imperial">
2.5.5 :011 > vehicle.permissions
=> ["control_security", "read_battery", "read_charge", "read_location", "read_odometer", "read_vehicle_info", "read_vin"]
=> #<Smartcar::Odometer:0x00007fbad63851f0 @permissions=["control_security", "read_battery", "read_charge", "read_location", "read_odometer", "read_vehicle_info", "read_vin"]>
2.5.5 :012 > vehicle.odometer
=> #<Smartcar::Odometer:0x00007fbad718a3f0 @distance=74988.44443760936>
2.5.5 :013 > vehicle.battery
=> #<Smartcar::Battery:0x00007fbad50f4c80 @range=134.35, @percentRemaining=0.02>
2.5.5 :014 > vehicle.charge
=> #<Smartcar::Charge:0x00007fbad787e620 @state="FULLY_CHARGED", @isPluggedIn=true>
2.5.5 :015 > vehicle.lock!
=> true
2.5.5 :016 > vehicle.start_charge!
Traceback (most recent call last):
8: from /usr/share/rvm/rubies/ruby-2.5.5/bin/irb:23:in `<main>'
7: from /usr/share/rvm/rubies/ruby-2.5.5/bin/irb:23:in `load'
6: from /usr/share/rvm/rubies/ruby-2.5.5/lib/ruby/gems/2.7.0/gems/irb-1.2.1/exe/irb:11:in `<top (required)>'
5: from (irb):7
4: from (irb):8:in `rescue in irb_binding'
3: from /home/st-2vgpnn2/.rvm/gems/ruby-2.5.5/gems/smartcar-0.1.2/lib/smartcar/vehicle.rb:102:in `block (2 levels) in <class:Vehicle>'
2: from /home/st-2vgpnn2/.rvm/gems/ruby-2.5.5/gems/smartcar-0.1.2/lib/smartcar/vehicle.rb:192:in `start_or_stop_charge!'
1: from /home/st-2vgpnn2/.rvm/gems/ruby-2.5.5/gems/smartcar-0.1.2/lib/smartcar/base.rb:39:in `block (2 levels) in <class:Base>'
Smartcar::ExternalServiceError (API error - {"error":"vehicle_state_error","message":"Charging plug is not connected to the vehicle.","code":"VS_004"})
```

Example Usage for oAuth -
Expand Down Expand Up @@ -72,4 +135,7 @@ To contribute, please:

1. Open an issue for the feature (or bug) you would like to resolve.
2. Resolve the issue and add tests in your feature branch.
3. Open a PR from your feature branch into `develop` that tags the issue.
3. Open a PR from your feature branch into `develop` that tags the issue.

[gem-image]: https://badge.fury.io/rb/smartcar
[gem-url]: https://badge.fury.io/rb/smartcar.svg
20 changes: 18 additions & 2 deletions lib/smartcar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,38 @@
require "smartcar/user"


module Smartcar
# Main Smartcar umbrella module
module Smartcar
# Error raised when a config is not found
class ConfigNotFound < StandardError; end
# Error raised when Smartcar returns non 400, 404, 401, 200 or 204 response
class ExternalServiceError < StandardError; end
# Error raised when Smartcar returns 404
class ServiceUnavailableError < ExternalServiceError; end
# Error raised when Smartcar returns Authentication Error with status 401
class AuthenticationError < ExternalServiceError; end
class ParserError < ExternalServiceError; end
# Error raised when Smartcar returns 400 response
class BadRequestError < ExternalServiceError; end
# Smartcar API version being used
API_VERSION = "v1.0".freeze
# Host to connect to smartcar
SITE = "https://api.smartcar.com/".freeze

# Path for smartcar oauth
OAUTH_PATH = "https://connect.smartcar.com/oauth/authorize".freeze
%w(success code test live force auto metric imperial).each do |constant|
# Constant to represent the value
const_set(constant.upcase, constant.freeze)
end

# Lock value sent in request body
LOCK = "LOCK".freeze
# Unlock value sent in request body
UNLOCK = "UNLOCK".freeze
# Start charge value sent in request body
START_CHARGE = "START".freeze
# Stop charge value sent in request body
STOP_CHARGE = "STOP".freeze
# Constant for units
UNITS = [IMPERIAL,METRIC]
end
42 changes: 21 additions & 21 deletions lib/smartcar/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@
module Smartcar
# The Base class for all of the other class.
# Let other classes inherit from here and put common methods here.
#
# @author [ashwin]
#
class Base
include Utils

# Error raised when an invalid parameter is passed.
class InvalidParameterValue < StandardError; end
# Constant for Bearer auth type
BEARER = 'BEARER'.freeze
# Constant for Basic auth type
BASIC = 'BASIC'.freeze

attr_accessor :token
# meta programming and define all Restful methods.
# @param path [String] the path to hit for the request.
# @param token [String] the access token to be used.
#
# @return [Hash] The response Json parsed as a hash.
attr_accessor :token, :error, :meta

%i{get post patch put delete}.each do |verb|
# meta programming and define all Restful methods.
# @param path [String] the path to hit for the request.
# @param data [Hash] request body if needed.
#
# @return [Hash] The response Json parsed as a hash.
define_method verb do |path, data=nil|
response = service.send(verb) do |request|
request.headers['Authorization'] = "BEARER #{token}"
Expand All @@ -29,21 +32,19 @@ class InvalidParameterValue < StandardError; end
request.url complete_path, data
else
request.url complete_path
request.body = data if data
request.body = data.to_json if data
end
end
status = response.status
raise ServiceUnavailableError.new, "Service Unavailable - #{response.body}" if status == 404
raise BadRequestError.new, "Bad Request - #{response.body}" if status == 400
raise AuthenticationError.new, "Authentication error" if status == 401
raise ExternalServiceError.new, "API error - #{response.body}" unless [200,204].include?(status)
JSON.parse(response.body)
error = get_error(response)
raise error if error
[JSON.parse(response.body), response.headers]
end
end

# This requires a proc 'PATH' to be defined in the class
# @param token [String] Access token
# @param token [String] Vechicle ID
# @param path [String] resource path
# @param options [Hash] query params
# @param auth [String] type of auth
#
# @return [Object]
def fetch(path: , options: {}, auth: 'BEARER')
Expand All @@ -58,15 +59,14 @@ def fetch(path: , options: {}, auth: 'BEARER')
#
# @return [String] Base64 encoding of CLIENT:SECRET
def get_basic_auth
Base64.strict_encode64("#{ENV['SMARTCAR_CLIENT_ID']}:#{ENV['SMARTCAR_SECRET']}")
Base64.strict_encode64("#{get_config('CLIENT_ID')}:#{get_config('CLIENT_SECRET')}")
end

# gets a smartcar API service/client
# @param token [String] Access token.
#
# @return [OAuth2::AccessToken] An initialized AccessToken instance that acts as service client
def service
@service ||= Faraday.new(url: SITE)
end
end
end
end
11 changes: 5 additions & 6 deletions lib/smartcar/battery.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
module Smartcar
# class to represent battery info
#
# @author [ashwin]
#
# class to represent Battery info
#@attr [Number] percentRemaining Decimal value representing the remaining charge percent.
#@attr [Number] range Remaining range of the vehicle.
class Battery < Base
include Utils
# Path Proc for hitting battery end point
PATH = Proc.new{|id| "/vehicles/#{id}/battery"}
attr_accessor :percentRemaining, :range
attr_reader :percentRemaining, :range

# just to have Ruby-esque method names
alias_method :percentage_remaining, :percentRemaining
Expand Down
9 changes: 4 additions & 5 deletions lib/smartcar/charge.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
module Smartcar
# class to represent Charge info
#
# @author [ashwin]
#
#@attr [Boolean] isPluggedIn Specifies if the vehicle is plugged in.
#@attr [String] state Charging state of the vehicle.
class Charge < Base
include Utils
# Path Proc for hitting charge end point
PATH = Proc.new{|id| "/vehicles/#{id}/charge"}
attr_accessor :isPluggedIn, :state
attr_reader :isPluggedIn, :state

# just to have Ruby-esque method names
alias_method :is_plugged_in?, :isPluggedIn
Expand Down
10 changes: 4 additions & 6 deletions lib/smartcar/engine_oil.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
module Smartcar
# class to represent Engine oil life
#
# @author [ashwin]
#
# class to represent Engine oil info
#@attr [Number] lifeRemaining Remaining life of the engine oil
class EngineOil < Base
include Utils
# Path Proc for hitting engine oil end point
PATH = Proc.new{|id| "/vehicles/#{id}/engine/oil"}
attr_accessor :lifeRemaining
attr_reader :lifeRemaining

# just to have Ruby-esque method names
alias_method :life_remaining, :lifeRemaining
Expand Down
10 changes: 5 additions & 5 deletions lib/smartcar/fuel.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module Smartcar
# class to represent Fuel info
#
# @author [ashwin]
#
#@attr [Number] amountRemaining Amount of fuel remaining.
#@attr [Number] percentageRemaining Decimal value representing the remaining fuel percent.
#@attr [Number] range Remaining range of the vehicle.
class Fuel < Base
include Utils
# Path Proc for hitting fuel end point
PATH = Proc.new{|id| "/vehicles/#{id}/fuel"}
attr_accessor :amountRemaining, :percentRemaining, :range
attr_reader :amountRemaining, :percentRemaining, :range

# just to have Ruby-esque method names
alias_method :amount_remaining, :amountRemaining
Expand Down
Loading