hello-sense 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/CHANGELOG.md +9 -0
- data/CONTRIBUTING.md +56 -0
- data/LICENSE +21 -0
- data/README.md +55 -0
- data/hello_sense.gemspec +24 -0
- data/lib/hello_sense.rb +30 -0
- data/lib/hello_sense/account.rb +130 -0
- data/lib/hello_sense/alarms.rb +59 -0
- data/lib/hello_sense/alerts.rb +14 -0
- data/lib/hello_sense/api_error.rb +20 -0
- data/lib/hello_sense/app.rb +21 -0
- data/lib/hello_sense/client.rb +142 -0
- data/lib/hello_sense/constants.rb +4 -0
- data/lib/hello_sense/devices.rb +74 -0
- data/lib/hello_sense/expansions.rb +53 -0
- data/lib/hello_sense/firmware.rb +20 -0
- data/lib/hello_sense/insights.rb +57 -0
- data/lib/hello_sense/notifications.rb +36 -0
- data/lib/hello_sense/questions.rb +44 -0
- data/lib/hello_sense/sensors.rb +125 -0
- data/lib/hello_sense/session.rb +26 -0
- data/lib/hello_sense/sharing.rb +9 -0
- data/lib/hello_sense/sleep_sounds.rb +101 -0
- data/lib/hello_sense/speech.rb +14 -0
- data/lib/hello_sense/stats.rb +21 -0
- data/lib/hello_sense/store.rb +9 -0
- data/lib/hello_sense/support.rb +23 -0
- data/lib/hello_sense/timeline.rb +85 -0
- data/lib/hello_sense/trends.rb +109 -0
- data/lib/hello_sense/version.rb +5 -0
- metadata +197 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module Sense
|
7
|
+
class Client
|
8
|
+
API_HOST = 'https://api.hello.is'.freeze
|
9
|
+
|
10
|
+
include Account
|
11
|
+
include Alarms
|
12
|
+
include Alerts
|
13
|
+
include App
|
14
|
+
include Devices
|
15
|
+
include Expansions
|
16
|
+
include Firmware
|
17
|
+
include Insights
|
18
|
+
include Notifications
|
19
|
+
include Questions
|
20
|
+
include Sensors
|
21
|
+
include Session
|
22
|
+
include Sharing
|
23
|
+
include SleepSounds
|
24
|
+
include Speech
|
25
|
+
include Stats
|
26
|
+
include Store
|
27
|
+
include Support
|
28
|
+
include Timeline
|
29
|
+
include Trends
|
30
|
+
|
31
|
+
attr_reader :access_token
|
32
|
+
|
33
|
+
# XXX
|
34
|
+
#
|
35
|
+
# @param options [Hash]
|
36
|
+
# @option options [String] :access_token
|
37
|
+
# @option options [String] :client_id
|
38
|
+
# @option options [String] :client_secret
|
39
|
+
# @option options [String] :password
|
40
|
+
# @option options [String] :username
|
41
|
+
def initialize(options = {})
|
42
|
+
@access_token = options[:access_token]
|
43
|
+
@client_id = options[:client_id]
|
44
|
+
@client_secret = options[:client_secret]
|
45
|
+
@password = options[:password]
|
46
|
+
@username = options[:username]
|
47
|
+
|
48
|
+
@access_token = authorize_with_password! if @access_token.nil?
|
49
|
+
puts "** Using #{@access_token} as Sense access token"
|
50
|
+
end
|
51
|
+
|
52
|
+
# XXX
|
53
|
+
#
|
54
|
+
# @param path [String] relative path to use for the request
|
55
|
+
def delete(path)
|
56
|
+
response = connection.delete(path, headers)
|
57
|
+
data_or_error(response)
|
58
|
+
end
|
59
|
+
|
60
|
+
# XXX
|
61
|
+
#
|
62
|
+
# @param path [String] relative path to use for the request
|
63
|
+
def get(path)
|
64
|
+
response = connection.get(path, headers)
|
65
|
+
data_or_error(response)
|
66
|
+
end
|
67
|
+
|
68
|
+
# XXX
|
69
|
+
#
|
70
|
+
# @param path [String] relative path to use for the request
|
71
|
+
# @param data [Hash] data to send
|
72
|
+
def patch(path, data)
|
73
|
+
# XXX may need +set_form_data+
|
74
|
+
response = connection.patch(path, data, headers)
|
75
|
+
data_or_error(response)
|
76
|
+
end
|
77
|
+
|
78
|
+
# XXX
|
79
|
+
#
|
80
|
+
# @param path [String] relative path to use for the request
|
81
|
+
# @param data [Hash] data to send
|
82
|
+
def post(path, data)
|
83
|
+
request = Net::HTTP::Post.new(path, headers)
|
84
|
+
request.set_form_data(data)
|
85
|
+
response = connection.request(request)
|
86
|
+
data_or_error(response)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param path [String] relative path to use for the request
|
90
|
+
# @param data [Hash] data to send
|
91
|
+
def put(path, data)
|
92
|
+
request = Net::HTTP::Put.new(path, headers)
|
93
|
+
request.set_form_data(data)
|
94
|
+
response = connection.request(request)
|
95
|
+
data_or_error(response)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# XXX
|
101
|
+
#
|
102
|
+
# @return [Net::HTTP]
|
103
|
+
def connection
|
104
|
+
@connection ||= begin
|
105
|
+
require 'openssl'
|
106
|
+
require 'uri'
|
107
|
+
|
108
|
+
uri = URI.parse(API_HOST)
|
109
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
110
|
+
http.ssl_version = :TLSv1_2
|
111
|
+
http.use_ssl = true
|
112
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
113
|
+
|
114
|
+
http
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# XXX
|
119
|
+
#
|
120
|
+
# @param response [Net::HTTPResponse]
|
121
|
+
# @return [Array, Hash, nil]
|
122
|
+
# @raise [APIError]
|
123
|
+
def data_or_error(response)
|
124
|
+
if response.code.to_i < 300
|
125
|
+
return response.body ? JSON.parse(response.body) : nil
|
126
|
+
end
|
127
|
+
|
128
|
+
raise APIError.new(response)
|
129
|
+
end
|
130
|
+
|
131
|
+
# XXX
|
132
|
+
#
|
133
|
+
# @return [Hash]
|
134
|
+
def headers
|
135
|
+
{
|
136
|
+
Accept: 'application/json',
|
137
|
+
Authorization: "Bearer #{@access_token}",
|
138
|
+
:'User-Agent' => "hello-sense Ruby gem/#{VERSION}",
|
139
|
+
}.freeze
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sense
|
4
|
+
module Devices
|
5
|
+
# @return [Hash]
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# {
|
9
|
+
# "senses" => [{
|
10
|
+
# "id" => "ABCDEF1234567890",
|
11
|
+
# "firmware_version" => "11a1",
|
12
|
+
# "state" => "NORMAL",
|
13
|
+
# "last_updated" => 1483257600000,
|
14
|
+
# "color" => "UNKNOWN",
|
15
|
+
# "wifi_info" => {
|
16
|
+
# "ssid" => "Wifi? Why not!",
|
17
|
+
# "rssi" => 0,
|
18
|
+
# "last_updated" => 1420099200000,
|
19
|
+
# "condition" => "GOOD"
|
20
|
+
# },
|
21
|
+
# "hw_version" => "SENSE"
|
22
|
+
# }],
|
23
|
+
# "pills" => [{
|
24
|
+
# "id" => "0987654321FEDCBA",
|
25
|
+
# "firmware_version" => "2",
|
26
|
+
# "battery_level" => 0,
|
27
|
+
# "last_updated" => 1483257600000,
|
28
|
+
# "state" => "NORMAL",
|
29
|
+
# "color" => "BLUE",
|
30
|
+
# "battery_type" => "REMOVABLE"
|
31
|
+
# }]
|
32
|
+
# }
|
33
|
+
|
34
|
+
def devices
|
35
|
+
get('/v2/devices')
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Hash]
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# {
|
42
|
+
# "sense_id" => "ABCDEF1234567890",
|
43
|
+
# "paired_accounts" => 1
|
44
|
+
# }
|
45
|
+
|
46
|
+
def devices_info
|
47
|
+
get('/v2/devices/info')
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_device(device_id)
|
51
|
+
delete("/v2/devices/sense/#{device_id}/all")
|
52
|
+
end
|
53
|
+
|
54
|
+
def remove_pill(pill_id)
|
55
|
+
delete("/v2/devices/pill/#{pill_id}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def remove_sense(pill_id)
|
59
|
+
delete("/v2/devices/sense/#{pill_id}")
|
60
|
+
end
|
61
|
+
|
62
|
+
def voice(device_id)
|
63
|
+
get("/v2/devices/sense/#{device_id}/voice")
|
64
|
+
end
|
65
|
+
|
66
|
+
def update_voice(device_id, data)
|
67
|
+
patch("/v2/devices/sense/#{device_id}/voice", data)
|
68
|
+
end
|
69
|
+
|
70
|
+
def swap_device(data)
|
71
|
+
put('/v2/devices/swap', data)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sense
|
4
|
+
module Expansions
|
5
|
+
# @return [Array<Hash>]
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# [
|
9
|
+
# {
|
10
|
+
# "id" => 1,
|
11
|
+
# "service_name" => "NEST",
|
12
|
+
# "device_name" => "Nest Thermostat",
|
13
|
+
# "company_name" => "Nest",
|
14
|
+
# "description" => "Connecting Sense to your Nest Learning Thermostat allows you to control your thermostat with Voice, or automatically set a specific temperature ahead of your Sense alarm, so you wake up to your ideal temperature every morning.",
|
15
|
+
# "icon" => {
|
16
|
+
# "phone_1x" => "https://hello-data.s3.amazonaws.com/expansions/icon-nest@1x.png",
|
17
|
+
# "phone_2x" => "https://hello-data.s3.amazonaws.com/expansions/icon-nest@2x.png",
|
18
|
+
# "phone_3x" => "https://hello-data.s3.amazonaws.com/expansions/icon-nest@3x.png"
|
19
|
+
# },
|
20
|
+
# "auth_uri" => "https://api.hello.is/v2/expansions/1/auth",
|
21
|
+
# "token_uri" => nil,
|
22
|
+
# "refresh_uri" => nil,
|
23
|
+
# "category" => "TEMPERATURE",
|
24
|
+
# "created" => 1495381368634,
|
25
|
+
# "completion_uri" => "https://api.hello.is/v2/expansions/redirect",
|
26
|
+
# "state" => "NOT_CONNECTED",
|
27
|
+
# "value_range" => {
|
28
|
+
# "min" => 9,
|
29
|
+
# "max" => 32
|
30
|
+
# }
|
31
|
+
# }
|
32
|
+
# ]
|
33
|
+
def expansions
|
34
|
+
get('/v2/expansions')
|
35
|
+
end
|
36
|
+
|
37
|
+
def expansion(expansion_id)
|
38
|
+
get("/v2/expansions/#{expansion_id}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_expansion(expansion_id, data)
|
42
|
+
patch("/v2/expansions/#{expansion_id}", data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def expansion_configurations(expansion_id)
|
46
|
+
get("/v2/expansions/#{expansion_id}/configurations")
|
47
|
+
end
|
48
|
+
|
49
|
+
def update_expansion_configurations(expansion_id, data)
|
50
|
+
patch("/v2/expansions/#{expansion_id}/configurations", data)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sense
|
4
|
+
module Firmware
|
5
|
+
def request_firmware_update
|
6
|
+
post('/v1/ota/request_ota')
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [Hash]
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# {
|
13
|
+
# "status" => "NOT_REQUIRED"
|
14
|
+
# }
|
15
|
+
|
16
|
+
def firmware_update_status
|
17
|
+
get('/v1/ota/status')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sense
|
4
|
+
module Insights
|
5
|
+
# @return [Array<Hash>]
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# [
|
9
|
+
# {
|
10
|
+
# "account_id" => 100000,
|
11
|
+
# "title" => "Still as the Night",
|
12
|
+
# "message" => "During the last 13 nights, you moved 23% less than the average person using Sense. About **9% of your sleep** consists of agitated sleep.",
|
13
|
+
# "category" => "SLEEP_QUALITY",
|
14
|
+
# "timestamp" => 1483257600000,
|
15
|
+
# "info_preview" => nil,
|
16
|
+
# "image" => {
|
17
|
+
# "phone_1x" => "https://s3.amazonaws.com/hello-data/insights_images/sleep_quality.png",
|
18
|
+
# "phone_2x" => "https://s3.amazonaws.com/hello-data/insights_images/sleep_quality@2x.png",
|
19
|
+
# "phone_3x" => "https://s3.amazonaws.com/hello-data/insights_images/sleep_quality@3x.png"
|
20
|
+
# },
|
21
|
+
# "category_name" => "Sleep Quality",
|
22
|
+
# "insight_type" => "DEFAULT",
|
23
|
+
# "id" => "fded667b-9e91-43f5-91de-258ac1fee9c2"
|
24
|
+
# }
|
25
|
+
# ]
|
26
|
+
|
27
|
+
def insights
|
28
|
+
get('/v2/insights')
|
29
|
+
end
|
30
|
+
|
31
|
+
# Known +category+s:
|
32
|
+
# * +AIR_QUALITY+
|
33
|
+
# * +BED_LIGHT_DURATION+
|
34
|
+
# * +HUMIDITY+
|
35
|
+
# * +LIGHT+
|
36
|
+
# * +SLEEP_QUALITY+
|
37
|
+
# * +SLEEP_TIME+
|
38
|
+
# * +TEMPERATURE+
|
39
|
+
# * +WAKE_VARIANCE+
|
40
|
+
#
|
41
|
+
# @param category [String]
|
42
|
+
# @return [Array<Hash>]
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# [{
|
46
|
+
# "id" => 5,
|
47
|
+
# "category" => "AIR_QUALITY",
|
48
|
+
# "title" => "Clean air, better sleep",
|
49
|
+
# "text" => "Clean air is an important part of a healthy environment. A high concentration of airborne particulates (microscopic fragments of matter that can penetrate deep into your lungs) can irritate your throat and airways, exacerbate asthma symptoms, and disrupt your sleep.\n\nParticulates can come from indoor sources of pollutants like smoke, cooking fumes, and even some household cleaners. You should always take care to minimize your exposure to these types of pollutants, and open a window to help with ventilation if necessary.\n\nParticulate pollution can also come from outdoor sources, both natural and artificial. You can check the AirNow website to see if there’s an air quality advisory for your area at any time. If so, you should follow EPA recommendations, and limit your time spent outdoors.",
|
50
|
+
# "image_url" => ""
|
51
|
+
# }]
|
52
|
+
|
53
|
+
def insight(category)
|
54
|
+
get("/v2/insights/info/#{category}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sense
|
4
|
+
module Notifications
|
5
|
+
# @return [Array<Hash>]
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# [
|
9
|
+
# {
|
10
|
+
# "type" => "SLEEP_SCORE",
|
11
|
+
# "enabled" => true,
|
12
|
+
# "name" => Sleep Score"
|
13
|
+
# }, {
|
14
|
+
# "type" => "SYSTEM",
|
15
|
+
# "enabled" => true,
|
16
|
+
# "name" => System Alerts"
|
17
|
+
# }, {
|
18
|
+
# "type" => "SLEEP_REMINDER",
|
19
|
+
# "enabled" => true,
|
20
|
+
# "name" => Sleep Reminder"
|
21
|
+
# }
|
22
|
+
# ]
|
23
|
+
|
24
|
+
def notifications
|
25
|
+
get('/v1/notifications')
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_notification(data)
|
29
|
+
put('/v1/notifications', data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_notifications(data)
|
33
|
+
post('/v1/notifications/registration', data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sense
|
4
|
+
module Questions
|
5
|
+
# @return [Array<Hash>]
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# [
|
9
|
+
# {
|
10
|
+
# "id" => 2,
|
11
|
+
# "account_question_id" => 100000,
|
12
|
+
# "text" => "How was your sleep last night?",
|
13
|
+
# "choices" => {
|
14
|
+
# "id" => 69,
|
15
|
+
# "text" => "Great",
|
16
|
+
# "question_id" => 2
|
17
|
+
# }, {
|
18
|
+
# "id" => 70,
|
19
|
+
# "text" => "Okay",
|
20
|
+
# "question_id" => 2
|
21
|
+
# }, {
|
22
|
+
# "id" => 71,
|
23
|
+
# "text" => "Poor",
|
24
|
+
# "question_id" => 2
|
25
|
+
# }],
|
26
|
+
# "ask_local_date" => 1483257600000,
|
27
|
+
# "type" => "CHOICE",
|
28
|
+
# "ask_time" => "MORNING"
|
29
|
+
# }
|
30
|
+
# ]
|
31
|
+
|
32
|
+
def questions
|
33
|
+
get('/v1/questions')
|
34
|
+
end
|
35
|
+
|
36
|
+
def skip_question(data)
|
37
|
+
put('/v1/questions/skip', data)
|
38
|
+
end
|
39
|
+
|
40
|
+
def update_questions(data)
|
41
|
+
post('/v1/questions/save', data)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sense
|
4
|
+
module Sensors
|
5
|
+
# Historical sensor data, sampled every five minutes.
|
6
|
+
#
|
7
|
+
# @param hours [Fixnum] how many hours of data to fetch
|
8
|
+
# @return [Hash]
|
9
|
+
#
|
10
|
+
# @note Seems to top out at around 920 hours -- higher numbers result in
|
11
|
+
# empty data; probably some timeout on the server cancels the lookup and
|
12
|
+
# falls back to empty data.
|
13
|
+
# @note If +from_utc+ is more than about 11 hours in the past the server
|
14
|
+
# will give a 400 Bad Request response. You can request far more than
|
15
|
+
# 11 +hours+, so +from_utc+ is always set to the current UTC timestamp.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# {
|
19
|
+
# "sound" => [
|
20
|
+
# {
|
21
|
+
# "datetime" => 1500001200000,
|
22
|
+
# "value" => 31.155998,
|
23
|
+
# "offset_millis" => -25200000
|
24
|
+
# },
|
25
|
+
# ...
|
26
|
+
# ],
|
27
|
+
# "humidity" => [
|
28
|
+
# {
|
29
|
+
# "datetime" => 1500001200000,
|
30
|
+
# "value" => 42.710575,
|
31
|
+
# "offset_millis" => -25200000
|
32
|
+
# },
|
33
|
+
# ...
|
34
|
+
# ],
|
35
|
+
# "light" => [
|
36
|
+
# {
|
37
|
+
# "datetime" => 1500001200000,
|
38
|
+
# "value" => 302.38342,
|
39
|
+
# "offset_millis" => -25200000
|
40
|
+
# },
|
41
|
+
# ...
|
42
|
+
# ],
|
43
|
+
# "temperature" => [
|
44
|
+
# {
|
45
|
+
# "datetime" => 1500001200000,
|
46
|
+
# "value" => 16.91,
|
47
|
+
# "offset_millis" => -25200000
|
48
|
+
# },
|
49
|
+
# ...
|
50
|
+
# ],
|
51
|
+
# "particulates" => [
|
52
|
+
# {
|
53
|
+
# "datetime" => 1500001200000,
|
54
|
+
# "value" => 7.8401413,
|
55
|
+
# "offset_millis" => -25200000
|
56
|
+
# },
|
57
|
+
# ...
|
58
|
+
# ]
|
59
|
+
# }
|
60
|
+
|
61
|
+
def sensors_historical(hours:)
|
62
|
+
require 'active_support/all'
|
63
|
+
timestamp = Time.now.utc.to_i * 1000
|
64
|
+
|
65
|
+
get("/v1/room/all_sensors/hours?quantity=#{hours}&from_utc=#{timestamp}")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Known +type+s:
|
69
|
+
# * +TEMPERATURE+
|
70
|
+
# * +HUMIDITY+
|
71
|
+
# * +LIGHT+
|
72
|
+
# * +PARTICULATES+
|
73
|
+
# * +SOUND+
|
74
|
+
#
|
75
|
+
# @return [Array<Hash>]
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# [
|
79
|
+
# {
|
80
|
+
# "name" => "Temperature",
|
81
|
+
# "type" => "TEMPERATURE",
|
82
|
+
# "unit" => "CELSIUS",
|
83
|
+
# "message" => "It's a bit warm.",
|
84
|
+
# "scale" => [{
|
85
|
+
# "name" => "Cold",
|
86
|
+
# "min" => nil,
|
87
|
+
# "max" => 9.99,
|
88
|
+
# "condition" => "ALERT"
|
89
|
+
# }, {
|
90
|
+
# "name" => "Cool",
|
91
|
+
# "min" => 10.0,
|
92
|
+
# "max" => 14.99,
|
93
|
+
# "condition" => "WARNING"
|
94
|
+
# }, {
|
95
|
+
# "name" => "Ideal",
|
96
|
+
# "min" => 15.0,
|
97
|
+
# "max" => 19.99,
|
98
|
+
# "condition" => "IDEAL"
|
99
|
+
# }, {
|
100
|
+
# "name" => "Warm",
|
101
|
+
# "min" => 20.0,
|
102
|
+
# "max" => 25.99,
|
103
|
+
# "condition" => "WARNING"
|
104
|
+
# }, {
|
105
|
+
# "name" => "Hot",
|
106
|
+
# "min" => 26.0,
|
107
|
+
# "max" => nil,
|
108
|
+
# "condition" => "ALERT"
|
109
|
+
# }],
|
110
|
+
# "condition" => "WARNING",
|
111
|
+
# "value" => 20.32
|
112
|
+
# }
|
113
|
+
# ...
|
114
|
+
# ]
|
115
|
+
|
116
|
+
def sensors
|
117
|
+
data = get('/v2/sensors')
|
118
|
+
data['sensors']
|
119
|
+
end
|
120
|
+
|
121
|
+
def update_sensors(data)
|
122
|
+
post('/v2/sensors', data)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|