purple_air_api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +15 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +86 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +8 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/purple_air_api.rb +42 -0
- data/lib/purple_air_api/V1/client.rb +63 -0
- data/lib/purple_air_api/V1/errors.rb +44 -0
- data/lib/purple_air_api/V1/raise_http_exception.rb +51 -0
- data/lib/purple_air_api/V1/sensors/errors.rb +9 -0
- data/lib/purple_air_api/V1/sensors/get_sensor.rb +136 -0
- data/lib/purple_air_api/V1/sensors/get_sensors.rb +226 -0
- data/lib/purple_air_api/version.rb +6 -0
- data/purple_air_api.gemspec +37 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 01b62b7c6de5e4d8448ae7a1285db326b712464b3e9b18ed2d6ae828f6d1146c
|
4
|
+
data.tar.gz: 48f3649122ce37ec5413588bcdf8d12105506303f749e10989d89cb7cc2b69d0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d5af049019b69727c5a0421abe9dc2dcfbec967532728caeb4fdc5cf29401d80b99c4381f7612bbac56f3beab33bf0fb1d108fa6539b2b81b39e55b0ae02f9d1
|
7
|
+
data.tar.gz: 845f907c45befc6d3abed0fae9e586ce0a05a818d0e7376cf4213a1152b15b6d4026a380a6da95825435a9e28dfd2134b616b4e4b831430959ac075a1a586276
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at dylankiselbach@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in purple_air_api.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem 'rake', '~> 13.0'
|
9
|
+
|
10
|
+
group :test, :development do
|
11
|
+
gem 'faker'
|
12
|
+
gem 'rspec', '~> 3.0'
|
13
|
+
gem 'rubocop', '~> 1.9'
|
14
|
+
gem 'webmock'
|
15
|
+
gem 'yard'
|
16
|
+
# for yard
|
17
|
+
gem 'webrick'
|
18
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
purple_air_api (0.1.0)
|
5
|
+
faraday (~> 1.3)
|
6
|
+
fast_jsonparser (~> 0.5)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
addressable (2.7.0)
|
12
|
+
public_suffix (>= 2.0.2, < 5.0)
|
13
|
+
ast (2.4.2)
|
14
|
+
concurrent-ruby (1.1.8)
|
15
|
+
crack (0.4.5)
|
16
|
+
rexml
|
17
|
+
diff-lcs (1.4.4)
|
18
|
+
faker (2.15.1)
|
19
|
+
i18n (>= 1.6, < 2)
|
20
|
+
faraday (1.3.0)
|
21
|
+
faraday-net_http (~> 1.0)
|
22
|
+
multipart-post (>= 1.2, < 3)
|
23
|
+
ruby2_keywords
|
24
|
+
faraday-net_http (1.0.1)
|
25
|
+
fast_jsonparser (0.5.0)
|
26
|
+
hashdiff (1.0.1)
|
27
|
+
i18n (1.8.7)
|
28
|
+
concurrent-ruby (~> 1.0)
|
29
|
+
multipart-post (2.1.1)
|
30
|
+
parallel (1.20.1)
|
31
|
+
parser (3.0.0.0)
|
32
|
+
ast (~> 2.4.1)
|
33
|
+
public_suffix (4.0.6)
|
34
|
+
rainbow (3.0.0)
|
35
|
+
rake (13.0.3)
|
36
|
+
regexp_parser (2.0.3)
|
37
|
+
rexml (3.2.4)
|
38
|
+
rspec (3.10.0)
|
39
|
+
rspec-core (~> 3.10.0)
|
40
|
+
rspec-expectations (~> 3.10.0)
|
41
|
+
rspec-mocks (~> 3.10.0)
|
42
|
+
rspec-core (3.10.1)
|
43
|
+
rspec-support (~> 3.10.0)
|
44
|
+
rspec-expectations (3.10.1)
|
45
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
46
|
+
rspec-support (~> 3.10.0)
|
47
|
+
rspec-mocks (3.10.1)
|
48
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
49
|
+
rspec-support (~> 3.10.0)
|
50
|
+
rspec-support (3.10.1)
|
51
|
+
rubocop (1.9.0)
|
52
|
+
parallel (~> 1.10)
|
53
|
+
parser (>= 3.0.0.0)
|
54
|
+
rainbow (>= 2.2.2, < 4.0)
|
55
|
+
regexp_parser (>= 1.8, < 3.0)
|
56
|
+
rexml
|
57
|
+
rubocop-ast (>= 1.2.0, < 2.0)
|
58
|
+
ruby-progressbar (~> 1.7)
|
59
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
60
|
+
rubocop-ast (1.4.1)
|
61
|
+
parser (>= 2.7.1.5)
|
62
|
+
ruby-progressbar (1.11.0)
|
63
|
+
ruby2_keywords (0.0.4)
|
64
|
+
unicode-display_width (2.0.0)
|
65
|
+
webmock (3.11.1)
|
66
|
+
addressable (>= 2.3.6)
|
67
|
+
crack (>= 0.3.2)
|
68
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
69
|
+
webrick (1.7.0)
|
70
|
+
yard (0.9.26)
|
71
|
+
|
72
|
+
PLATFORMS
|
73
|
+
x86_64-darwin-19
|
74
|
+
|
75
|
+
DEPENDENCIES
|
76
|
+
faker
|
77
|
+
purple_air_api!
|
78
|
+
rake (~> 13.0)
|
79
|
+
rspec (~> 3.0)
|
80
|
+
rubocop (~> 1.9)
|
81
|
+
webmock
|
82
|
+
webrick
|
83
|
+
yard
|
84
|
+
|
85
|
+
BUNDLED WITH
|
86
|
+
2.2.5
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 dkiselbach
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# PurpleAirApi
|
2
|
+
|
3
|
+
This is a client for interacting with the PurpleAir API. This was written using the V1 of the API. In order to use this gem, you must have been granted read and write tokens from PurpleAir.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'purple_air_api'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install purple_air_api
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
To use this gem, instantiate an instance of a PurpleAirApi client by making the following request:
|
24
|
+
|
25
|
+
`client = PurpleAirApi.client(read_token: your_read_token, write_token: your_write_token)`
|
26
|
+
|
27
|
+
You can then use this client to interact with the various API methods under the client like:
|
28
|
+
|
29
|
+
`client.get_sensors(options)`
|
30
|
+
|
31
|
+
Options would be and of the parameters you would like to pass onto the PurpleAir API. The gem will parse the parameters into the format required by the API.
|
32
|
+
|
33
|
+
## Development
|
34
|
+
|
35
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
36
|
+
|
37
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dkiselbach/purple_air_api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/dkiselbach/purple_air_api/CODE_OF_CONDUCT.md).
|
42
|
+
|
43
|
+
## License
|
44
|
+
|
45
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
46
|
+
|
47
|
+
## Code of Conduct
|
48
|
+
|
49
|
+
Everyone interacting in the PurpleAirApi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/dkiselbach/purple_air_api/CODE_OF_CONDUCT.md).
|
50
|
+
|
51
|
+
[](code_of_conduct.md)
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'purple_air_api'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require 'irb'
|
11
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'fast_jsonparser'
|
5
|
+
require_relative 'purple_air_api/version'
|
6
|
+
require_relative 'purple_air_api/V1/client'
|
7
|
+
require_relative 'purple_air_api/V1/raise_http_exception'
|
8
|
+
require_relative 'purple_air_api/V1/errors'
|
9
|
+
require_relative 'purple_air_api/v1/sensors/get_sensors'
|
10
|
+
require_relative 'purple_air_api/v1/sensors/get_sensor'
|
11
|
+
require_relative 'purple_air_api/v1/sensors/errors'
|
12
|
+
|
13
|
+
# The PurpleAirApi is a gem intended to be used to interact with the PurpleAir API easily.
|
14
|
+
module PurpleAirApi
|
15
|
+
# Alias for PurpleAirApi::V1::Client.new
|
16
|
+
#
|
17
|
+
# @return [PurpleAirApi::V1::Client]
|
18
|
+
# @example requesting data for a few sensors
|
19
|
+
# options = { fields: ['icon', 'name'], location_type: ['outside'], show_only: [26, 41], max_age: 3600}
|
20
|
+
# PurpleAirApi.client(read_token: "1234", write_token: "1234").request_sensors(options)
|
21
|
+
|
22
|
+
def self.client(read_token:, write_token: nil)
|
23
|
+
PurpleAirApi::V1::Client.new(read_token: read_token, write_token: write_token)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Delegate to PurpleAirApi::V1::Client
|
27
|
+
def self.method_missing(method, *args, &block)
|
28
|
+
return super unless client.respond_to?(method)
|
29
|
+
|
30
|
+
client.send(method, *args, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Delegate to PurpleAirApi::V1::Client
|
34
|
+
def self.respond_to?(method, include_all: false)
|
35
|
+
client.respond_to?(method, include_all) || super
|
36
|
+
end
|
37
|
+
|
38
|
+
# Delegate to PurpleAirApi::V1::Client
|
39
|
+
def self.respond_to_missing?(method_name, include_private: false)
|
40
|
+
client.respond_to_missing?(method_name, include_private) || super
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PurpleAirApi
|
4
|
+
# The V1 API Module for namespacing the PurpleAir V1 API
|
5
|
+
module V1
|
6
|
+
# Client class for interfacing with the V1 PurpleAir API. Refer to the instance methods to learn more about what
|
7
|
+
# methods and API options are available.
|
8
|
+
class Client
|
9
|
+
attr_reader :read_client, :write_client
|
10
|
+
|
11
|
+
# The base URL for the PurpleAir API
|
12
|
+
API_URL = 'https://api.purpleair.com/v1/'
|
13
|
+
|
14
|
+
# Creates a read and write client to interface with the Purple Air API.
|
15
|
+
# @!method initialize(read_token:, write_token:)
|
16
|
+
# @param read_token [String] The read client you received from PurpleAir
|
17
|
+
# @param write_token [String] The write client you received from PurpleAir
|
18
|
+
# @example generate a client instance
|
19
|
+
# PurpleAirApi::V1::Client.new(read_token: "1234", write_token: "1234")
|
20
|
+
|
21
|
+
def initialize(read_token:, write_token: nil)
|
22
|
+
@read_client = create_http_client(read_token)
|
23
|
+
@write_client = create_http_client(write_token) unless write_token.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Makes a request to the sensors endpoint and returns an instance of the V1::GetSensors class.
|
27
|
+
# @!method request_sensors(options)
|
28
|
+
# @param [Hash] options A hash of options { :option_name=>value }
|
29
|
+
# @option options [Array<Integer>] :show_only An array of indexes for the sensors you want to request.
|
30
|
+
# @option options [Array<String>] :location_type An array of strings specifying sensor location.
|
31
|
+
# @option options [Array<String>] :fields An array of fields you want returned.
|
32
|
+
# @option options [Integer] :modified_since Only return sensors updated since this timestamp.
|
33
|
+
# @option options [Integer] :max_age Only return sensors updated in the last n seconds.
|
34
|
+
# @option options [Hash<array>] :bounding_box A hash with a :nw and :se array { nw: [lat,long], se: [lat,long] }.
|
35
|
+
# @example
|
36
|
+
# { bounding_box: { nw: [37.7790262, -122.4199061], se: [37.6535403, -122.4168664]}}
|
37
|
+
# @option options [Array<String>] :read_keys An array of read-keys which are required for private devices.
|
38
|
+
# @return [PurpleAirApi::GetSensors]
|
39
|
+
# @example request sensor data for a few sensors
|
40
|
+
# options = { fields: ['icon', 'name'], location_type: ['outside'], show_only: [20, 47], max_age: 3600}
|
41
|
+
# client = PurpleAirApi::V1::Client.new(read_token: "1234", write_token: "1234")
|
42
|
+
# response = client.request_sensors(options)
|
43
|
+
# response_hash = response.parsed_response
|
44
|
+
|
45
|
+
def request_sensors(options = {})
|
46
|
+
GetSensors.call(client: read_client, **options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def request_sensor(sensor_index:, read_key: nil)
|
50
|
+
GetSensor.call(client: read_client, sensor_index: sensor_index, read_key: read_key)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def create_http_client(token)
|
56
|
+
Faraday.new(url: API_URL) do |faraday|
|
57
|
+
faraday.headers['X-API-KEY'] = token
|
58
|
+
faraday.use RaiseHttpException
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PurpleAirApi
|
4
|
+
module V1
|
5
|
+
# A custom error class for rescuing from all PurpleAir API errors
|
6
|
+
class BaseError < StandardError
|
7
|
+
attr_reader :response_object, :error_type
|
8
|
+
|
9
|
+
# Initialize the error object with error_type and the Faraday response object. PurpleAir returns a human friendly
|
10
|
+
# error message and type which is added here. You can also reference the response to view the raw response
|
11
|
+
# from PurpleAir.
|
12
|
+
# @!method initialize(message, error_type, response_object)
|
13
|
+
# @param message [String] the message you want displayed when the error is raised
|
14
|
+
# @param error_type [String] the error type that PurpleAir includes in the JSON response
|
15
|
+
# @param response_object [Faraday::Env] the Faraday response object
|
16
|
+
def initialize(message, error_type, response_object)
|
17
|
+
super(message)
|
18
|
+
@error_type = error_type
|
19
|
+
@response_object = response_object
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raised when the PurpleAir API returns the HTTP status code 403
|
24
|
+
class ApiKeyError < BaseError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when the PurpleAir API returns the HTTP status code 415
|
28
|
+
class MissingJsonPayloadError < BaseError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised when the PurpleAir API returns the HTTP status code 400. Use the message and error_type on the Error
|
32
|
+
# object to determine additional information regarding the error.
|
33
|
+
class ApiError < BaseError
|
34
|
+
end
|
35
|
+
|
36
|
+
# Raised when the PurpleAir API returns the HTTP status code 404
|
37
|
+
class NotFoundError < BaseError
|
38
|
+
end
|
39
|
+
|
40
|
+
# Raised when the PurpleAir API returns the HTTP status code 500
|
41
|
+
class ServerError < BaseError
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PurpleAirApi
|
4
|
+
module V1
|
5
|
+
# Handles any HTTP exceptions for 400 and 500 error codes
|
6
|
+
class RaiseHttpException < Faraday::Middleware
|
7
|
+
# A switch statement which determines which error to raise depending on error code
|
8
|
+
def call(env)
|
9
|
+
@app.call(env).on_complete do |response|
|
10
|
+
self.response = response
|
11
|
+
case response[:status].to_i
|
12
|
+
when 400
|
13
|
+
raise ApiError.new(error_message, parsed_response[:error], response)
|
14
|
+
when 403
|
15
|
+
raise ApiKeyError.new(error_message, parsed_response[:error], response)
|
16
|
+
when 404
|
17
|
+
raise NotFoundError.new(error_message, parsed_response[:error], response)
|
18
|
+
when 415
|
19
|
+
raise MissingJsonPayloadError.new(error_message, 'MissingJsonPayloadError', response)
|
20
|
+
when 500
|
21
|
+
raise ServerError.new(error_message, 'ServerError', response)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Faraday syntax for implementing middleware
|
27
|
+
def initialize(app)
|
28
|
+
super app
|
29
|
+
@parser = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_accessor :response
|
35
|
+
|
36
|
+
def error_message
|
37
|
+
parsed_response[:description]
|
38
|
+
end
|
39
|
+
|
40
|
+
def parsed_response
|
41
|
+
@parsed_response ||= FastJsonparser.parse(response[:body])
|
42
|
+
rescue FastJsonparser::ParseError
|
43
|
+
unknown_error_message
|
44
|
+
end
|
45
|
+
|
46
|
+
def unknown_error_message
|
47
|
+
{ description: 'Something went wrong in the request.' }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PurpleAirApi
|
4
|
+
module V1
|
5
|
+
# Class for requesting sensor data for a single sensor. This will return different response formats from the API.
|
6
|
+
class GetSensor
|
7
|
+
attr_accessor :http_response, :request_options, :index
|
8
|
+
attr_reader :http_client
|
9
|
+
|
10
|
+
# The endpoint URL
|
11
|
+
URL = 'https://api.purpleair.com/v1/sensors/'
|
12
|
+
|
13
|
+
# Calls initializes the class and requests the data from PurpleAir.
|
14
|
+
# @!method call(...)
|
15
|
+
|
16
|
+
def self.call(...)
|
17
|
+
new(...).request
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates a HTTP friendly options hash depending on your inputs
|
21
|
+
# @!method initialize(client:, **options)
|
22
|
+
# @param client [Faraday::Connection] Your HTTP client initialized in Client
|
23
|
+
# @param options [Hash] Your HTTP options for the request.
|
24
|
+
|
25
|
+
def initialize(client:, sensor_index:, read_key: nil)
|
26
|
+
@http_client = client
|
27
|
+
@request_options = {}
|
28
|
+
create_options_hash(sensor_index, read_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Makes a get request to the PurpleAir Get Sensors Data endpoint https://api.purpleair.com/v1/sensors.
|
32
|
+
# @!method request
|
33
|
+
# @return [PurpleAirApi::V1::GetSensors]
|
34
|
+
|
35
|
+
def request
|
36
|
+
self.http_response = http_client.get(url, request_options)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Delegate to PurpleAirApi::V1::GetSensor.json_response
|
41
|
+
|
42
|
+
def parsed_response
|
43
|
+
json_response
|
44
|
+
end
|
45
|
+
|
46
|
+
# Takes the raw response from PurpleAir and parses the JSON.
|
47
|
+
# @!method json_response
|
48
|
+
# @return [Json]
|
49
|
+
# @example json_response example
|
50
|
+
# response.json_response
|
51
|
+
# {
|
52
|
+
# "api_version": "V1.0.6-0.0.9",
|
53
|
+
# "time_stamp": 1615053213,
|
54
|
+
# "sensor": {
|
55
|
+
# "sensor_index": 20,
|
56
|
+
# "name": "Oakdale",
|
57
|
+
# "model": "UNKNOWN",
|
58
|
+
# "location_type": 0,
|
59
|
+
# "latitude": 40.6031,
|
60
|
+
# "longitude": -111.8361,
|
61
|
+
# "altitude": 4636,
|
62
|
+
# "last_seen": 1615053181,
|
63
|
+
# "last_modified": 1575003022,
|
64
|
+
# "private": 0,
|
65
|
+
# "channel_state": 1,
|
66
|
+
# "channel_flags_manual": 2,
|
67
|
+
# "pm1.0_a": 0.0,
|
68
|
+
# "pm2.5_a": 0.0,
|
69
|
+
# "pm10.0_a": 0.0,
|
70
|
+
# "0.3_um_count_a": 0.0,
|
71
|
+
# "0.5_um_count_a": 0.0,
|
72
|
+
# "1.0_um_count_a": 0.0,
|
73
|
+
# "2.5_um_count_a": 0.0,
|
74
|
+
# "5.0_um_count_a": 0.0,
|
75
|
+
# "10.0_um_count_a": 0.0,
|
76
|
+
# "stats_a": {
|
77
|
+
# "pm2.5": 0.0,
|
78
|
+
# "pm2.5_10minute": 0.0,
|
79
|
+
# "pm2.5_30minute": 0.0,
|
80
|
+
# "pm2.5_60minute": 0.0,
|
81
|
+
# "pm2.5_6hour": 0.0,
|
82
|
+
# "pm2.5_24hour": 0.0,
|
83
|
+
# "pm2.5_1week": 0.0,
|
84
|
+
# "time_stamp": 1615053181
|
85
|
+
# },
|
86
|
+
# "analog_input": 0.01,
|
87
|
+
# "primary_id_a": 66984,
|
88
|
+
# "primary_key_a": "TLKXL2SOJ9M0KGFK",
|
89
|
+
# "secondary_id_a": 71207,
|
90
|
+
# "secondary_key_a": "224YRSOGD8TNE5FQ",
|
91
|
+
# "primary_id_b": 209227,
|
92
|
+
# "primary_key_b": "3YF75LZ4DL7HDH9O",
|
93
|
+
# "secondary_id_b": 209228,
|
94
|
+
# "secondary_key_b": "Y02CNZZDMR15KCMX",
|
95
|
+
# "hardware": "1.0+PMSX003-O",
|
96
|
+
# "led_brightness": 0.0,
|
97
|
+
# "firmware_version": "6.01",
|
98
|
+
# "rssi": -78.0,
|
99
|
+
# "icon": 0,
|
100
|
+
# "channel_flags_auto": 0
|
101
|
+
# }
|
102
|
+
# }
|
103
|
+
|
104
|
+
def json_response
|
105
|
+
@json_response ||= FastJsonparser.parse(http_response.body)
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def create_options_hash(sensor_index, read_key)
|
111
|
+
sensor_index(sensor_index)
|
112
|
+
request_options.merge!(
|
113
|
+
read_key(read_key)
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def sensor_index(index)
|
118
|
+
raise OptionsError, 'sensor_index must be an Integer' unless index.instance_of?(Integer)
|
119
|
+
|
120
|
+
self.index = index.to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
def read_key(key)
|
124
|
+
return {} if key.nil?
|
125
|
+
|
126
|
+
raise OptionsError, 'read_key must be a String' unless key.instance_of?(String)
|
127
|
+
|
128
|
+
{ read_key: key }
|
129
|
+
end
|
130
|
+
|
131
|
+
def url
|
132
|
+
URL + index
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PurpleAirApi
|
4
|
+
module V1
|
5
|
+
# Class for requesting sensor data. This will return different response formats from the API.
|
6
|
+
class GetSensors
|
7
|
+
attr_accessor :request_options, :http_response
|
8
|
+
attr_reader :http_client
|
9
|
+
attr_writer :parsed_response
|
10
|
+
|
11
|
+
# The default value for fields that will be returned by PurpleAir
|
12
|
+
DEFAULT_FIELDS = %w[icon name latitude longitude altitude pm2.5].freeze
|
13
|
+
|
14
|
+
# The default location type for the sensor
|
15
|
+
DEFAULT_LOCATION_TYPE = %w[outside inside].freeze
|
16
|
+
|
17
|
+
# The endpoint URL
|
18
|
+
URL = 'https://api.purpleair.com/v1/sensors'
|
19
|
+
|
20
|
+
# Calls initializes the class and requests the data from PurpleAir.
|
21
|
+
# @!method call(...)
|
22
|
+
|
23
|
+
def self.call(...)
|
24
|
+
new(...).request
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates a HTTP friendly options hash depending on your inputs
|
28
|
+
# @!method initialize(client:, **options)
|
29
|
+
# @param client [Faraday::Connection] Your HTTP client initialized in Client
|
30
|
+
# @param options [Hash] Your HTTP options for the request.
|
31
|
+
|
32
|
+
def initialize(client:, **options)
|
33
|
+
@http_client = client
|
34
|
+
@request_options = {}
|
35
|
+
create_options_hash(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Makes a get request to the PurpleAir Get Sensors Data endpoint https://api.purpleair.com/v1/sensors.
|
39
|
+
# @!method request
|
40
|
+
# @return [PurpleAirApi::V1::GetSensors]
|
41
|
+
|
42
|
+
def request
|
43
|
+
self.http_response = http_client.get(URL, request_options)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Takes the raw response from PurpleAir and generates a hash indexed by sensor index. You can use this response
|
48
|
+
# like a normal hash object. This GetSensorsClass.parsed_response[:47] would return a hash of data for sensor 47
|
49
|
+
# with each hash key labelling the associated data.
|
50
|
+
# @!method parsed_response
|
51
|
+
# @return [Hash]
|
52
|
+
# @example parsed_response example
|
53
|
+
# response.parsed_response
|
54
|
+
# {
|
55
|
+
# :fields=>["sensor_index", "name", "icon", "latitude", "longitude", "altitude", "pm2.5"],
|
56
|
+
# :api_version=>"V1.0.6-0.0.9",
|
57
|
+
# :time_stamp=>1614787814,
|
58
|
+
# :data_time_stamp=>nil,
|
59
|
+
# :max_age=>3600,
|
60
|
+
# :data=>
|
61
|
+
# {
|
62
|
+
# 20=>{"sensor_index"=>20, "name"=>"Oakdale", "icon"=>0, "latitude"=>40.6031,
|
63
|
+
# "longitude"=>-111.8361, "altitude"=>4636, "pm2.5"=>0.0},
|
64
|
+
# 47=>{"sensor_index"=>47, "name"=>"OZONE TEST", "icon"=>0, "latitude"=>40.4762,
|
65
|
+
# "longitude"=>-111.8826, "altitude"=>nil, "pm2.5"=>nil}
|
66
|
+
# }
|
67
|
+
# }
|
68
|
+
|
69
|
+
def parsed_response
|
70
|
+
@parsed_response ||= parse_response
|
71
|
+
end
|
72
|
+
|
73
|
+
# Takes the raw response from PurpleAir and parses the JSON.
|
74
|
+
# @!method json_response
|
75
|
+
# @return [Json]
|
76
|
+
# @example json_response example
|
77
|
+
# response.json_response
|
78
|
+
# {
|
79
|
+
# :api_version=>"V1.0.6-0.0.9",
|
80
|
+
# :time_stamp=>1614787814,
|
81
|
+
# :data_time_stamp=>1614787807,
|
82
|
+
# :location_type=>0,
|
83
|
+
# :max_age=>3600,
|
84
|
+
# :fields=>["sensor_index", "name", "icon", "latitude", "longitude", "altitude", "pm2.5"],
|
85
|
+
# :data=>[
|
86
|
+
# [20, "Oakdale", 0, 40.6031, -111.8361, 4636, 0.0],
|
87
|
+
# [47, "OZONE TEST", 0, 40.4762, -111.8826, nil, nil]
|
88
|
+
# ]
|
89
|
+
# }
|
90
|
+
|
91
|
+
def json_response
|
92
|
+
@json_response ||= FastJsonparser.parse(http_response.body)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
attr_accessor :data, :data_fields
|
98
|
+
|
99
|
+
def create_options_hash(options)
|
100
|
+
request_options.merge!(
|
101
|
+
fields(options[:fields] || DEFAULT_FIELDS),
|
102
|
+
location_type(options[:location_type]),
|
103
|
+
show_only(options[:show_only]),
|
104
|
+
modified_since(options[:modified_since]),
|
105
|
+
max_age(options[:max_age]),
|
106
|
+
bounding_box(options[:bounding_box]),
|
107
|
+
read_keys(options[:read_keys])
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
def fields(fields_array)
|
112
|
+
unless fields_array.instance_of?(Array) && fields_array.first.instance_of?(String)
|
113
|
+
raise OptionsError, 'fields must be an array of strings specifying the fields you want returned'
|
114
|
+
end
|
115
|
+
|
116
|
+
{ fields: fields_array.join(',') }
|
117
|
+
end
|
118
|
+
|
119
|
+
def show_only(sensor_indices)
|
120
|
+
return {} if sensor_indices.nil?
|
121
|
+
|
122
|
+
unless sensor_indices.instance_of?(Array) && sensor_indices.first.instance_of?(Integer)
|
123
|
+
raise OptionsError,
|
124
|
+
'show_only must be an array of integers specifying the sensor indices you data returned for'
|
125
|
+
end
|
126
|
+
|
127
|
+
{ show_only: sensor_indices.join(',') }
|
128
|
+
end
|
129
|
+
|
130
|
+
def location_type(location_type)
|
131
|
+
return {} if location_type.nil?
|
132
|
+
|
133
|
+
unless location_type.instance_of?(Array) && location_type.first.instance_of?(String)
|
134
|
+
raise OptionsError,
|
135
|
+
"location_type must be an array of strings specifying either ['inside'], ['outside'], or both"
|
136
|
+
end
|
137
|
+
|
138
|
+
location_type.map!(&:downcase)
|
139
|
+
|
140
|
+
location_type_parameter(location_type)
|
141
|
+
end
|
142
|
+
|
143
|
+
def location_type_parameter(location_type)
|
144
|
+
return {} if location_type.include?('outside') && location_type.include?('inside')
|
145
|
+
return { location_type: 1 } if location_type.include?('inside')
|
146
|
+
return { location_type: 0 } if location_type.include?('outside')
|
147
|
+
end
|
148
|
+
|
149
|
+
def modified_since(timestamp)
|
150
|
+
return {} if timestamp.nil?
|
151
|
+
raise OptionsError, 'timestamp must be a valid Integer' unless timestamp.instance_of?(Integer)
|
152
|
+
|
153
|
+
{ modified_since: timestamp }
|
154
|
+
end
|
155
|
+
|
156
|
+
def max_age(seconds)
|
157
|
+
return {} if seconds.nil?
|
158
|
+
raise OptionsError, 'seconds must be a valid Integer' unless seconds.instance_of?(Integer)
|
159
|
+
|
160
|
+
{ max_age: seconds.to_i }
|
161
|
+
end
|
162
|
+
|
163
|
+
def bounding_box(coordinates)
|
164
|
+
return {} if coordinates.nil?
|
165
|
+
|
166
|
+
unless coordinates.instance_of?(Hash) && coordinates[:nw].length == 2 && coordinates[:se].length == 2
|
167
|
+
raise OptionsError, 'coordinates must be a Hash with a :nw and :se array containing [lat, long]'
|
168
|
+
end
|
169
|
+
|
170
|
+
{
|
171
|
+
nwlat: coordinates[:nw][0], nwlng: coordinates[:nw][1], selat: coordinates[:se][0], selng: coordinates[:se][1]
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
def read_keys(keys)
|
176
|
+
return {} if keys.nil?
|
177
|
+
|
178
|
+
unless keys.instance_of?(Array) && keys.first.instance_of?(String)
|
179
|
+
raise OptionsError, 'read_keys must be an Array of Strings'
|
180
|
+
end
|
181
|
+
|
182
|
+
{ read_keys: keys.join(',') }
|
183
|
+
end
|
184
|
+
|
185
|
+
def parse_response
|
186
|
+
response = json_response
|
187
|
+
|
188
|
+
generate_response_hash(response)
|
189
|
+
|
190
|
+
self.data = response[:data]
|
191
|
+
self.data_fields = response[:fields]
|
192
|
+
|
193
|
+
merge_data_hash
|
194
|
+
end
|
195
|
+
|
196
|
+
def generate_response_hash(response_hash)
|
197
|
+
fields, api_version,
|
198
|
+
time_stamp, date_time_stamp, max_age = response_hash.values_at(:fields, :api_version, :time_stamp,
|
199
|
+
:date_time_stamp, :max_age)
|
200
|
+
self.parsed_response = {
|
201
|
+
fields: fields,
|
202
|
+
api_version: api_version,
|
203
|
+
time_stamp: time_stamp,
|
204
|
+
data_time_stamp: date_time_stamp,
|
205
|
+
max_age: max_age
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
def merge_data_hash
|
210
|
+
parsed_response.merge!({ data: generate_data_hash })
|
211
|
+
end
|
212
|
+
|
213
|
+
def generate_data_hash
|
214
|
+
data_hash = {}
|
215
|
+
data.each do |sensor|
|
216
|
+
sensor_index = sensor.first
|
217
|
+
data_hash.merge!(sensor_index => {})
|
218
|
+
sensor.each_with_index do |data_point, index|
|
219
|
+
data_hash[sensor_index].merge!(data_fields[index] => data_point)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
data_hash
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/purple_air_api/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'purple_air_api'
|
7
|
+
spec.version = PurpleAirApi::VERSION
|
8
|
+
spec.authors = ['Dylan Kiselbach']
|
9
|
+
spec.email = ['dylankiselbach@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'This is an library to assist in interacting with the PurpleAir API.'
|
12
|
+
spec.description = 'To use this gem you will need your own read and write key from PurpleAir which can be retrieved
|
13
|
+
by contacting their support team https://www2.purpleair.com/pages/contact-us.'
|
14
|
+
spec.homepage = 'https://github.com/dkiselbach/purple_air_api'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
17
|
+
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/dkiselbach/purple_air_api.'
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/dkiselbach/purple_air_api.'
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
spec.bindir = 'exe'
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ['lib']
|
30
|
+
|
31
|
+
# Uncomment to register a new dependency of your gem
|
32
|
+
spec.add_dependency 'faraday', '~> 1.3'
|
33
|
+
spec.add_dependency 'fast_jsonparser', '~> 0.5'
|
34
|
+
|
35
|
+
# For more information and examples about making a new gem, checkout our
|
36
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: purple_air_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dylan Kiselbach
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-03-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fast_jsonparser
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.5'
|
41
|
+
description: |-
|
42
|
+
To use this gem you will need your own read and write key from PurpleAir which can be retrieved
|
43
|
+
by contacting their support team https://www2.purpleair.com/pages/contact-us.
|
44
|
+
email:
|
45
|
+
- dylankiselbach@gmail.com
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".gitignore"
|
51
|
+
- ".rspec"
|
52
|
+
- ".rubocop.yml"
|
53
|
+
- CODE_OF_CONDUCT.md
|
54
|
+
- Gemfile
|
55
|
+
- Gemfile.lock
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- bin/console
|
60
|
+
- bin/setup
|
61
|
+
- lib/purple_air_api.rb
|
62
|
+
- lib/purple_air_api/V1/client.rb
|
63
|
+
- lib/purple_air_api/V1/errors.rb
|
64
|
+
- lib/purple_air_api/V1/raise_http_exception.rb
|
65
|
+
- lib/purple_air_api/V1/sensors/errors.rb
|
66
|
+
- lib/purple_air_api/V1/sensors/get_sensor.rb
|
67
|
+
- lib/purple_air_api/V1/sensors/get_sensors.rb
|
68
|
+
- lib/purple_air_api/version.rb
|
69
|
+
- purple_air_api.gemspec
|
70
|
+
homepage: https://github.com/dkiselbach/purple_air_api
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata:
|
74
|
+
homepage_uri: https://github.com/dkiselbach/purple_air_api
|
75
|
+
source_code_uri: https://github.com/dkiselbach/purple_air_api.
|
76
|
+
changelog_uri: https://github.com/dkiselbach/purple_air_api.
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.7.0
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.2.3
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: This is an library to assist in interacting with the PurpleAir API.
|
96
|
+
test_files: []
|