weatherlink 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 +18 -0
- data/.rspec +3 -0
- data/.rubocop.yml +27 -0
- data/.travis.yml +6 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +36 -0
- data/LICENSE.txt +21 -0
- data/README.md +177 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/weatherlink.rb +86 -0
- data/lib/weatherlink/api_v2.rb +282 -0
- data/lib/weatherlink/client.rb +64 -0
- data/lib/weatherlink/data_record.rb +12 -0
- data/lib/weatherlink/hash_wrapper.rb +26 -0
- data/lib/weatherlink/local_api_v1.rb +143 -0
- data/lib/weatherlink/local_client.rb +60 -0
- data/lib/weatherlink/node.rb +24 -0
- data/lib/weatherlink/sensor.rb +24 -0
- data/lib/weatherlink/sensor_data.rb +60 -0
- data/lib/weatherlink/sensor_data_collection.rb +36 -0
- data/lib/weatherlink/sensor_record.rb +39 -0
- data/lib/weatherlink/station.rb +71 -0
- data/lib/weatherlink/version.rb +5 -0
- data/weatherlink.gemspec +37 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d16465c0ef961c536b4a160c17993ae39978a833ff0602bb3c2975dcdb12225b
|
4
|
+
data.tar.gz: e33b3cd29d2430334633ca59020bca24f18cb13d1b0ff2ab5c1e650dfd1f0436
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 651ba70b2b30dac3401707ba115aca784be784da3e5f7519b4d630fbe9f22e97e7f6ee2a706e0de248e1efea96e0c330e0df4f4a8642b5072a3e2dadfbd0bec2
|
7
|
+
data.tar.gz: b651ba906113a68977be4cda967dddcca66ca556a481b4d6bbe66723d115b157f161509d9cb0a759e02dce84a11aa45f8869aac77a6c52500a1f1d0d53ffa2be
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.6
|
3
|
+
NewCops: enable
|
4
|
+
Layout/LineLength:
|
5
|
+
Max: 120
|
6
|
+
Style/TrailingCommaInArrayLiteral:
|
7
|
+
EnforcedStyleForMultiline: consistent_comma
|
8
|
+
Style/TrailingCommaInHashLiteral:
|
9
|
+
EnforcedStyleForMultiline: consistent_comma
|
10
|
+
Style/FormatString:
|
11
|
+
Enabled: false
|
12
|
+
Style/FormatStringToken:
|
13
|
+
Enabled: false
|
14
|
+
Style/Documentation:
|
15
|
+
Enabled: false
|
16
|
+
Metrics/ClassLength:
|
17
|
+
Enabled: false
|
18
|
+
Metrics/MethodLength:
|
19
|
+
Enabled: false
|
20
|
+
Metrics/AbcSize:
|
21
|
+
Enabled: false
|
22
|
+
Metrics/CyclomaticComplexity:
|
23
|
+
Enabled: false
|
24
|
+
Metrics/PerceivedComplexity:
|
25
|
+
Enabled: false
|
26
|
+
Style/SymbolArray:
|
27
|
+
MinSize: 1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
weatherlink (0.1.0)
|
5
|
+
ruby-units (~> 2.3)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.3)
|
11
|
+
rake (12.3.3)
|
12
|
+
rspec (3.9.0)
|
13
|
+
rspec-core (~> 3.9.0)
|
14
|
+
rspec-expectations (~> 3.9.0)
|
15
|
+
rspec-mocks (~> 3.9.0)
|
16
|
+
rspec-core (3.9.2)
|
17
|
+
rspec-support (~> 3.9.3)
|
18
|
+
rspec-expectations (3.9.2)
|
19
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
20
|
+
rspec-support (~> 3.9.0)
|
21
|
+
rspec-mocks (3.9.1)
|
22
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
23
|
+
rspec-support (~> 3.9.0)
|
24
|
+
rspec-support (3.9.3)
|
25
|
+
ruby-units (2.3.1)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
rake (~> 12.0)
|
32
|
+
rspec (~> 3.0)
|
33
|
+
weatherlink!
|
34
|
+
|
35
|
+
BUNDLED WITH
|
36
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Jeremy Cole
|
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,177 @@
|
|
1
|
+
# WeatherLink
|
2
|
+
|
3
|
+
This is an unofficial implementation of the Davis Instruments WeatherLink API, including both the Local API (v1) and the web API (v2).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'weatherlink'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install weatherlink
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Local API
|
24
|
+
|
25
|
+
To read the data from a local WeatherLink Live device via the "local API" (there is no authentication):
|
26
|
+
|
27
|
+
```
|
28
|
+
require 'weatherlink'
|
29
|
+
|
30
|
+
> wl = WeatherLink::LocalClient.new(host: '<local ip address>')
|
31
|
+
```
|
32
|
+
|
33
|
+
The `current_conditions` method returns a `SensorData` object for each sensors:
|
34
|
+
|
35
|
+
```
|
36
|
+
> cc = wl.current_conditions
|
37
|
+
=> #<WeatherLink::SensorDataCollection (3 sensors)>
|
38
|
+
|
39
|
+
> cc.to_a
|
40
|
+
=> [#<WeatherLink::SensorData lsid=1 (Local API - ISS Record, 1 records)>,
|
41
|
+
#<WeatherLink::SensorData lsid=2 (Local API - LSS Temperature/Humidity Record, 1 records)>,
|
42
|
+
#<WeatherLink::SensorData lsid=3 (Local API - LSS Barometric Pressure Record, 1 records)>]
|
43
|
+
```
|
44
|
+
|
45
|
+
Each `SensorData` is a wrapper for a hash containing the underlying data. Each of its keys can also be used as a method to get the data itself.
|
46
|
+
|
47
|
+
```
|
48
|
+
> pp cc[0].to_h
|
49
|
+
{"ts"=>1609130694,
|
50
|
+
"temp"=>24.2 tempF,
|
51
|
+
"hum"=>84.9 %,
|
52
|
+
"dew_point"=>20.3 tempF,
|
53
|
+
"wet_bulb"=>22.6 tempF,
|
54
|
+
"heat_index"=>24.2 tempF,
|
55
|
+
"wind_chill"=>24.2 tempF,
|
56
|
+
"thw_index"=>24.2 tempF,
|
57
|
+
"thsw_index"=>22.2 tempF,
|
58
|
+
"wind_speed_last"=>0 mph,
|
59
|
+
"wind_dir_last"=>0 deg,
|
60
|
+
"wind_speed_avg_last_1_min"=>0 mph,
|
61
|
+
"wind_dir_scalar_avg_last_1_min"=>149 deg,
|
62
|
+
"wind_speed_avg_last_2_min"=>0 mph,
|
63
|
+
"wind_dir_scalar_avg_last_2_min"=>149 deg,
|
64
|
+
"wind_speed_hi_last_2_min"=>1 mph,
|
65
|
+
"wind_dir_at_hi_speed_last_2_min"=>149 deg,
|
66
|
+
"wind_speed_avg_last_10_min"=>0 mph,
|
67
|
+
"wind_dir_scalar_avg_last_10_min"=>149 deg,
|
68
|
+
"wind_speed_hi_last_10_min"=>1 mph,
|
69
|
+
"wind_dir_at_hi_speed_last_10_min"=>149 deg,
|
70
|
+
"rain_size"=>1,
|
71
|
+
"rain_rate_last"=>0 in/h,
|
72
|
+
"rain_rate_hi"=>0 in/h,
|
73
|
+
"rainfall_last_15_min"=>0 in,
|
74
|
+
"rain_rate_hi_last_15_min"=>0 in/h,
|
75
|
+
"rainfall_last_60_min"=>0 in,
|
76
|
+
"rainfall_last_24_hr"=>0 in,
|
77
|
+
"rain_storm"=>0 in,
|
78
|
+
"rain_storm_start_at"=>nil,
|
79
|
+
"solar_rad"=>0 W/m^2,
|
80
|
+
"uv_index"=>0.0,
|
81
|
+
"rx_state"=>0,
|
82
|
+
"trans_battery_flag"=>0,
|
83
|
+
"rainfall_daily"=>0 in,
|
84
|
+
"rainfall_monthly"=>18 in,
|
85
|
+
"rainfall_year"=>128 in,
|
86
|
+
"rain_storm_last"=>5 in,
|
87
|
+
"rain_storm_last_start_at"=>1608224100,
|
88
|
+
"rain_storm_last_end_at"=>1608318061}
|
89
|
+
```
|
90
|
+
|
91
|
+
Note that the unit support uses `ruby-units` which uses `Rational`, so some of the results can be... interesting:
|
92
|
+
|
93
|
+
```
|
94
|
+
> cc[0].temp
|
95
|
+
=> -3342515348439043/791648371998720 tempC
|
96
|
+
```
|
97
|
+
|
98
|
+
Use `scalar.to_f` to get a `Float` most of the time:
|
99
|
+
|
100
|
+
```
|
101
|
+
> cc[0].temp.scalar.to_f
|
102
|
+
=> -4.222222222222226
|
103
|
+
```
|
104
|
+
|
105
|
+
### Web API
|
106
|
+
|
107
|
+
Obtain API credentials form your WeatherLink account and initialize the API:
|
108
|
+
|
109
|
+
```
|
110
|
+
require 'weatherlink'
|
111
|
+
|
112
|
+
> wl = WeatherLink::Client.new(api_key: '<api key>', api_secret: '<api secret>')
|
113
|
+
```
|
114
|
+
|
115
|
+
Various aspects of the API and devices can be interrogated:
|
116
|
+
|
117
|
+
```
|
118
|
+
> wl.stations
|
119
|
+
=> [#<WeatherLink::Station station_id=1 gateway_id_hex=a (Jackalope)>]
|
120
|
+
|
121
|
+
> wl.sensors
|
122
|
+
=> [#<WeatherLink::Sensor lsid=1 (Davis Instruments - WeatherLink LIVE Health)>,
|
123
|
+
#<WeatherLink::Sensor lsid=2 (Davis Instruments - Barometer)>,
|
124
|
+
#<WeatherLink::Sensor lsid=3 (Davis Instruments - Inside Temp/Hum)>,
|
125
|
+
#<WeatherLink::Sensor lsid=4 (Davis Instruments - Vantage Pro2 Plus /w 24-hr-Fan-Aspirated Radiation shield, UV & Solar Radiation Sensors)>,
|
126
|
+
#<WeatherLink::Sensor lsid=5 (Davis Instruments - AQS Health)>,
|
127
|
+
#<WeatherLink::Sensor lsid=6 (Davis Instruments - AirLink)>,
|
128
|
+
#<WeatherLink::Sensor lsid=7 (Davis Instruments - AQS Health)>,
|
129
|
+
#<WeatherLink::Sensor lsid=8 (Davis Instruments - AirLink)>,
|
130
|
+
#<WeatherLink::Sensor lsid=9 (Davis Instruments - AQS Health)>,
|
131
|
+
#<WeatherLink::Sensor lsid=10 (Davis Instruments - AirLink)>]
|
132
|
+
|
133
|
+
> wl.nodes
|
134
|
+
=> [#<WeatherLink::Node device_id_hex=a (Station - Barn)>,
|
135
|
+
#<WeatherLink::Node device_id_hex=b (Station - Living Room)>,
|
136
|
+
#<WeatherLink::Node device_id_hex=c (Station - Office)>]
|
137
|
+
```
|
138
|
+
|
139
|
+
Since most users will probably only have one station, a `station` method returns the first station for convenience:
|
140
|
+
|
141
|
+
```
|
142
|
+
wl.station
|
143
|
+
=> #<WeatherLink::Station station_id=1 gateway_id_hex=a (Station)>
|
144
|
+
```
|
145
|
+
|
146
|
+
To collect current weather data from all sensor of the first/primary station:
|
147
|
+
|
148
|
+
```
|
149
|
+
> wl.station.current
|
150
|
+
=> #<WeatherLink::SensorDataCollection (10 sensors)>
|
151
|
+
```
|
152
|
+
|
153
|
+
At this point accessing the underlying data is exactly the same as using the Local API above.
|
154
|
+
|
155
|
+
Since the station knows the IP addresses of each sensor, if you're on the local network you can also access these sensors directly through the `local_sensors` method (which uses the same `WeatherLink::LocalClient` API above, but automatically configures it for each known sensor):
|
156
|
+
|
157
|
+
```
|
158
|
+
> wl.station.local_sensors
|
159
|
+
=> [#<struct WeatherLink::Station::LocalSensor device=#<WeatherLink::Station station_id=1 gateway_id_hex=a (Station)>, host="1.2.3.4">,
|
160
|
+
#<struct WeatherLink::Station::LocalSensor device=#<WeatherLink::Node device_id_hex=b (Station - Living Room)>, host="1.2.3.5">,
|
161
|
+
#<struct WeatherLink::Station::LocalSensor device=#<WeatherLink::Node device_id_hex=c (Station - Office)>, host="1.2.3.6">,
|
162
|
+
#<struct WeatherLink::Station::LocalSensor device=#<WeatherLink::Node device_id_hex=d (Station - Barn)>, host="1.2.3.7">]
|
163
|
+
```
|
164
|
+
|
165
|
+
## Development
|
166
|
+
|
167
|
+
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.
|
168
|
+
|
169
|
+
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
170
|
+
|
171
|
+
## Contributing
|
172
|
+
|
173
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jeremycole/weatherlink.
|
174
|
+
|
175
|
+
## License
|
176
|
+
|
177
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'weatherlink'
|
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
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/weatherlink.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'weatherlink/version'
|
4
|
+
require 'ruby-units'
|
5
|
+
|
6
|
+
module WeatherLink
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
UNIT_TYPES = %i[
|
10
|
+
temperature
|
11
|
+
humidity
|
12
|
+
wind_speed
|
13
|
+
pressure
|
14
|
+
wind_direction
|
15
|
+
rain_quantity
|
16
|
+
rain_rate
|
17
|
+
solar_radiation
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
Units = Struct.new(*UNIT_TYPES, keyword_init: true) do
|
21
|
+
def fetch(key)
|
22
|
+
return send(key.to_sym) if key && respond_to?(key.to_sym)
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
METRIC_WEATHER_UNITS = Units.new(
|
29
|
+
temperature: 'tempC',
|
30
|
+
humidity: '%',
|
31
|
+
pressure: 'hPa',
|
32
|
+
wind_speed: 'm/s',
|
33
|
+
wind_direction: 'deg',
|
34
|
+
rain_quantity: 'cm',
|
35
|
+
rain_rate: 'cm/h',
|
36
|
+
solar_radiation: 'W/m^2'
|
37
|
+
)
|
38
|
+
|
39
|
+
IMPERIAL_WEATHER_UNITS = Units.new(
|
40
|
+
temperature: 'tempF',
|
41
|
+
humidity: '%',
|
42
|
+
pressure: 'inHg',
|
43
|
+
wind_speed: 'mph',
|
44
|
+
wind_direction: 'deg',
|
45
|
+
rain_quantity: 'in',
|
46
|
+
rain_rate: 'in/h',
|
47
|
+
solar_radiation: 'W/m^2'
|
48
|
+
)
|
49
|
+
|
50
|
+
SystemType = Struct.new(:name, keyword_init: true)
|
51
|
+
|
52
|
+
RecordType = Struct.new(:id, :system, :name, :type, keyword_init: true) do
|
53
|
+
def description
|
54
|
+
"#{system.name} - #{name}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def current_conditions?
|
58
|
+
type == :current_conditions
|
59
|
+
end
|
60
|
+
|
61
|
+
def archive?
|
62
|
+
type == :archive
|
63
|
+
end
|
64
|
+
|
65
|
+
def health?
|
66
|
+
type == :health
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'weatherlink/hash_wrapper'
|
72
|
+
|
73
|
+
require 'weatherlink/api_v2'
|
74
|
+
require 'weatherlink/client'
|
75
|
+
|
76
|
+
require 'weatherlink/local_api_v1'
|
77
|
+
require 'weatherlink/local_client'
|
78
|
+
|
79
|
+
require 'weatherlink/data_record'
|
80
|
+
require 'weatherlink/sensor_data'
|
81
|
+
require 'weatherlink/sensor_record'
|
82
|
+
require 'weatherlink/sensor_data_collection'
|
83
|
+
|
84
|
+
require 'weatherlink/station'
|
85
|
+
require 'weatherlink/node'
|
86
|
+
require 'weatherlink/sensor'
|
@@ -0,0 +1,282 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module WeatherLink
|
8
|
+
class APIv2
|
9
|
+
BASE_URI = 'https://api.weatherlink.com/v2'
|
10
|
+
|
11
|
+
SYSTEM_TYPES = {
|
12
|
+
Legacy: SystemType.new(name: 'Legacy'),
|
13
|
+
EnviroMonitor: SystemType.new(name: 'EnviroMonitor'),
|
14
|
+
WeatherLinkLive: SystemType.new(name: 'WeatherLink Live'),
|
15
|
+
AirLink: SystemType.new(name: 'AirLink'),
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
RECORD_TYPES = [
|
19
|
+
RecordType.new(
|
20
|
+
id: 1,
|
21
|
+
system: SYSTEM_TYPES[:Legacy],
|
22
|
+
name: 'Current Conditions Record - Revision A',
|
23
|
+
type: :current_conditions
|
24
|
+
),
|
25
|
+
RecordType.new(
|
26
|
+
id: 2,
|
27
|
+
system: SYSTEM_TYPES[:Legacy],
|
28
|
+
name: 'Current Conditions Record - Revision B',
|
29
|
+
type: :current_conditions
|
30
|
+
),
|
31
|
+
RecordType.new(
|
32
|
+
id: 3,
|
33
|
+
system: SYSTEM_TYPES[:Legacy],
|
34
|
+
name: 'Archive Record - Revision A',
|
35
|
+
type: :archive
|
36
|
+
),
|
37
|
+
RecordType.new(
|
38
|
+
id: 4,
|
39
|
+
system: SYSTEM_TYPES[:Legacy],
|
40
|
+
name: 'Archive Record - Revision B',
|
41
|
+
type: :archive
|
42
|
+
),
|
43
|
+
RecordType.new(
|
44
|
+
id: 5,
|
45
|
+
system: SYSTEM_TYPES[:Legacy],
|
46
|
+
name: 'High/Low Record (deprecated)',
|
47
|
+
type: :high_low
|
48
|
+
),
|
49
|
+
RecordType.new(
|
50
|
+
id: 6,
|
51
|
+
system: SYSTEM_TYPES[:EnviroMonitor],
|
52
|
+
name: 'ISS Current Conditions Record',
|
53
|
+
type: :current_conditions
|
54
|
+
),
|
55
|
+
RecordType.new(
|
56
|
+
id: 7,
|
57
|
+
system: SYSTEM_TYPES[:EnviroMonitor],
|
58
|
+
name: 'ISS Archive Record',
|
59
|
+
type: :archive
|
60
|
+
),
|
61
|
+
RecordType.new(
|
62
|
+
id: 8,
|
63
|
+
system: SYSTEM_TYPES[:EnviroMonitor],
|
64
|
+
name: 'ISS High/Low Record (deprecated)',
|
65
|
+
type: :archive
|
66
|
+
),
|
67
|
+
RecordType.new(
|
68
|
+
id: 9,
|
69
|
+
system: SYSTEM_TYPES[:EnviroMonitor],
|
70
|
+
name: 'non-ISS Record',
|
71
|
+
type: :unknown
|
72
|
+
),
|
73
|
+
RecordType.new(
|
74
|
+
id: 10,
|
75
|
+
system: SYSTEM_TYPES[:WeatherLinkLive],
|
76
|
+
name: 'ISS Current Conditions Record',
|
77
|
+
type: :current_conditions
|
78
|
+
),
|
79
|
+
RecordType.new(
|
80
|
+
id: 11,
|
81
|
+
system: SYSTEM_TYPES[:WeatherLinkLive],
|
82
|
+
name: 'ISS Archive Record',
|
83
|
+
type: :archive
|
84
|
+
),
|
85
|
+
RecordType.new(
|
86
|
+
id: 12,
|
87
|
+
system: SYSTEM_TYPES[:WeatherLinkLive],
|
88
|
+
name: 'non-ISS Current Conditions Record',
|
89
|
+
type: :current_conditions
|
90
|
+
),
|
91
|
+
RecordType.new(
|
92
|
+
id: 13,
|
93
|
+
system: SYSTEM_TYPES[:WeatherLinkLive],
|
94
|
+
name: 'non-ISS Archive Record',
|
95
|
+
type: :archive
|
96
|
+
),
|
97
|
+
RecordType.new(
|
98
|
+
id: 14,
|
99
|
+
system: SYSTEM_TYPES[:EnviroMonitor],
|
100
|
+
name: 'Health Record',
|
101
|
+
type: :health
|
102
|
+
),
|
103
|
+
RecordType.new(
|
104
|
+
id: 15,
|
105
|
+
system: SYSTEM_TYPES[:WeatherLinkLive],
|
106
|
+
name: 'Health Record',
|
107
|
+
type: :health
|
108
|
+
),
|
109
|
+
RecordType.new(
|
110
|
+
id: 16,
|
111
|
+
system: SYSTEM_TYPES[:AirLink],
|
112
|
+
name: 'Current Conditions Record',
|
113
|
+
type: :current_conditions
|
114
|
+
),
|
115
|
+
RecordType.new(
|
116
|
+
id: 17,
|
117
|
+
system: SYSTEM_TYPES[:AirLink],
|
118
|
+
name: 'Archive Record',
|
119
|
+
type: :archive
|
120
|
+
),
|
121
|
+
RecordType.new(
|
122
|
+
id: 18,
|
123
|
+
system: SYSTEM_TYPES[:AirLink],
|
124
|
+
name: 'Health Record',
|
125
|
+
type: :health
|
126
|
+
),
|
127
|
+
].freeze
|
128
|
+
|
129
|
+
RECORD_TYPES_BY_ID = RECORD_TYPES.each_with_object({}) { |r, h| h[r.id] = r }
|
130
|
+
|
131
|
+
def self.record_type(id)
|
132
|
+
RECORD_TYPES_BY_ID[id]
|
133
|
+
end
|
134
|
+
|
135
|
+
# TODO: Eliminate duplicate data e.g. rain_rate_last_{in,mm,clicks}
|
136
|
+
# TODO: Wind speeds are actually in mph not m/s?
|
137
|
+
RECORD_FIELD_UNITS = {
|
138
|
+
temp: :temperature,
|
139
|
+
temp_in: :temperature,
|
140
|
+
dew_point: :temperature,
|
141
|
+
dew_point_in: :temperature,
|
142
|
+
wet_bulb: :temperature,
|
143
|
+
heat_index: :temperature,
|
144
|
+
heat_index_in: :temperature,
|
145
|
+
wind_chill: :temperature,
|
146
|
+
thw_index: :temperature,
|
147
|
+
thsw_index: :temperature,
|
148
|
+
hum: :humidity,
|
149
|
+
hum_in: :humidity,
|
150
|
+
bar_sea_level: :pressure,
|
151
|
+
bar_absolute: :pressure,
|
152
|
+
bar_trend: :pressure,
|
153
|
+
wind_speed_last: :wind_speed,
|
154
|
+
wind_speed_avg_last_1_min: :wind_speed,
|
155
|
+
wind_speed_avg_last_2_min: :wind_speed,
|
156
|
+
wind_speed_avg_last_10_min: :wind_speed,
|
157
|
+
wind_speed_hi_last_2_min: :wind_speed,
|
158
|
+
wind_speed_hi_last_10_min: :wind_speed,
|
159
|
+
wind_dir_last: :wind_direction,
|
160
|
+
wind_dir_scalar_avg_last_1_min: :wind_direction,
|
161
|
+
wind_dir_scalar_avg_last_2_min: :wind_direction,
|
162
|
+
wind_dir_scalar_avg_last_10_min: :wind_direction,
|
163
|
+
wind_dir_at_hi_speed_last_2_min: :wind_direction,
|
164
|
+
wind_dir_at_hi_speed_last_10_min: :wind_direction,
|
165
|
+
rain_rate_last: :rain_rate,
|
166
|
+
rain_rate_hi: :rain_rate,
|
167
|
+
rain_rate_hi_last_15_min: :rain_rate,
|
168
|
+
rainfall_last_15_min: :rain_quantity,
|
169
|
+
rainfall_last_60_min: :rain_quantity,
|
170
|
+
rainfall_last_24_hr: :rain_quantity,
|
171
|
+
rainfall_daily: :rain_quantity,
|
172
|
+
rainfall_monthly: :rain_quantity,
|
173
|
+
rainfall_year: :rain_quantity,
|
174
|
+
rain_storm: :rain_quantity,
|
175
|
+
rain_storm_last: :rain_quantity,
|
176
|
+
solar_rad: :solar_radiation,
|
177
|
+
}.freeze
|
178
|
+
|
179
|
+
attr_reader :api_key, :api_secret, :units
|
180
|
+
|
181
|
+
def initialize(api_key:, api_secret:, units: IMPERIAL_WEATHER_UNITS)
|
182
|
+
@api_key = api_key
|
183
|
+
@api_secret = api_secret
|
184
|
+
@units = units
|
185
|
+
end
|
186
|
+
|
187
|
+
def type_for(field)
|
188
|
+
return nil unless [String, Symbol].include?(field.class)
|
189
|
+
|
190
|
+
RECORD_FIELD_UNITS.fetch(field.to_sym, nil)
|
191
|
+
end
|
192
|
+
|
193
|
+
def unit_for(field)
|
194
|
+
units.fetch(type_for(field))
|
195
|
+
end
|
196
|
+
|
197
|
+
def sensor_catalog
|
198
|
+
request(path: 'sensor-catalog')
|
199
|
+
end
|
200
|
+
|
201
|
+
def stations(ids = nil)
|
202
|
+
request(path: 'stations', path_params: { 'station-ids' => optional_array_param(ids) })
|
203
|
+
end
|
204
|
+
|
205
|
+
alias station stations
|
206
|
+
|
207
|
+
def nodes(ids = nil)
|
208
|
+
request(path: 'nodes', path_params: { 'node-ids' => optional_array_param(ids) })
|
209
|
+
end
|
210
|
+
|
211
|
+
alias node nodes
|
212
|
+
|
213
|
+
def sensors(ids = nil)
|
214
|
+
request(path: 'sensors', path_params: { 'sensor-ids' => optional_array_param(ids) })
|
215
|
+
end
|
216
|
+
|
217
|
+
alias sensor sensors
|
218
|
+
|
219
|
+
def sensor_activity(ids = nil)
|
220
|
+
request(path: 'sensor-activity', path_params: { 'sensor-ids' => optional_array_param(ids) })
|
221
|
+
end
|
222
|
+
|
223
|
+
def current(id)
|
224
|
+
request(path: 'current', path_params: { 'station-id' => id })
|
225
|
+
end
|
226
|
+
|
227
|
+
def historic(id, start_timestamp, end_timestamp)
|
228
|
+
request(
|
229
|
+
path: 'historic',
|
230
|
+
path_params: { 'station-id' => id },
|
231
|
+
query_params: { 'start-timestamp' => start_timestamp, 'end-timestamp' => end_timestamp }
|
232
|
+
)
|
233
|
+
end
|
234
|
+
|
235
|
+
def last_seconds(id, seconds)
|
236
|
+
historic(id, Time.now.to_i - seconds, Time.now.to_i)
|
237
|
+
end
|
238
|
+
|
239
|
+
def last_hour(id)
|
240
|
+
last_seconds(id, 3600)
|
241
|
+
end
|
242
|
+
|
243
|
+
def last_day(id)
|
244
|
+
last_seconds(id, 86_400)
|
245
|
+
end
|
246
|
+
|
247
|
+
def request(path:, path_params: {}, query_params: {})
|
248
|
+
uri = request_uri(path: path, path_params: path_params, query_params: query_params)
|
249
|
+
response = Net::HTTP.get_response(uri)
|
250
|
+
JSON.parse(response.body)
|
251
|
+
end
|
252
|
+
|
253
|
+
def request_uri(path:, path_params: {}, query_params: {})
|
254
|
+
used_path_params = path_params.compact
|
255
|
+
|
256
|
+
request_params = query_params.merge({ 't' => Time.now.to_i, 'api-key' => api_key })
|
257
|
+
request_params.merge!(
|
258
|
+
{
|
259
|
+
'api-signature' => api_signature(path_params: used_path_params, query_params: request_params),
|
260
|
+
}
|
261
|
+
)
|
262
|
+
|
263
|
+
uri = ([BASE_URI, path] + Array(used_path_params.values)).compact.join('/')
|
264
|
+
|
265
|
+
URI("#{uri}?#{URI.encode_www_form(request_params)}")
|
266
|
+
end
|
267
|
+
|
268
|
+
# private
|
269
|
+
|
270
|
+
def optional_array_param(param)
|
271
|
+
param.is_a?(Array) ? param.join(',') : param
|
272
|
+
end
|
273
|
+
|
274
|
+
def stuffed_params(params)
|
275
|
+
params.sort_by { |k, _| k }.map { |k, v| k.to_s + v.to_s }.join
|
276
|
+
end
|
277
|
+
|
278
|
+
def api_signature(path_params: {}, query_params: {})
|
279
|
+
OpenSSL::HMAC.hexdigest('SHA256', api_secret, stuffed_params(path_params.merge(query_params)))
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|