itiscold 1.0.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 +6 -0
- data/LICENSE.md +21 -0
- data/Manifest.txt +9 -0
- data/README.md +48 -0
- data/Rakefile +19 -0
- data/bin/itiscold +59 -0
- data/lib/itiscold.rb +416 -0
- data/lib/itiscold/public/index.html +71 -0
- data/test/test_itiscold.rb +8 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 80b7a340af5d1b8a90b096f0f6484a566d3a75c7
|
4
|
+
data.tar.gz: 139a60181371e9e20fc7bcb1dbb5372982505e3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bdd0edbd38a6a30703c6350e2bad504481cf7f589045c3c37c0ebd9b7e31663003f9ccb543c9183292bf45d076e3bd67f3c6e8036b2b1e8b4fa2507d8d8b4750
|
7
|
+
data.tar.gz: b55e71a1b272bc680840c0cf540c85e7675067942db869d212df2a4bb3e818a575945d17e2daeaddd5eef6ecf020bc756aad198c10b0a4cd8ee883ed0b8ab88f
|
data/CHANGELOG.md
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2016 Aaron Patterson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/Manifest.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# itiscold
|
2
|
+
|
3
|
+
* https://github.com/tenderlove/itiscold
|
4
|
+
|
5
|
+
## DESCRIPTION:
|
6
|
+
|
7
|
+
A thing that reads data from Elitech RC-5 temp sensor. Protocol documentation
|
8
|
+
can be found here:
|
9
|
+
|
10
|
+
https://github.com/civic/elitech-datareader/blob/master/rc-4-data.md
|
11
|
+
|
12
|
+
I've tested this on my RC-5, but not an RC-4.
|
13
|
+
|
14
|
+
## SYNOPSIS:
|
15
|
+
|
16
|
+
Print device information
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
temp = Itiscold.open '/dev/tty.wchusbserial14140'
|
20
|
+
info = temp.device_info
|
21
|
+
puts Psych.dump info
|
22
|
+
```
|
23
|
+
|
24
|
+
Set device information (Note that this will clear the memory)
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
# Get the info
|
28
|
+
temp = Itiscold.open '/dev/tty.wchusbserial14140'
|
29
|
+
info = temp.device_info
|
30
|
+
|
31
|
+
# Mutate it and upload to the device
|
32
|
+
info.stop_button = :prohibit
|
33
|
+
temp.device_params = info
|
34
|
+
|
35
|
+
# Print info again
|
36
|
+
info = temp.device_info
|
37
|
+
puts Psych.dump info
|
38
|
+
```
|
39
|
+
|
40
|
+
Set device time
|
41
|
+
```ruby
|
42
|
+
temp = Itiscold.open '/dev/tty.wchusbserial14140'
|
43
|
+
temp.set_device_time! # defaults to Time.now
|
44
|
+
```
|
45
|
+
|
46
|
+
## INSTALL:
|
47
|
+
|
48
|
+
* gem install itiscold
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
Hoe.plugins.delete :rubyforge
|
7
|
+
Hoe.plugin :minitest
|
8
|
+
Hoe.plugin :gemspec # `gem install hoe-gemspec`
|
9
|
+
Hoe.plugin :git # `gem install hoe-git`
|
10
|
+
|
11
|
+
Hoe.spec 'itiscold' do
|
12
|
+
developer('Aaron Patterson', 'tenderlove@ruby-lang.org')
|
13
|
+
self.readme_file = 'README.md'
|
14
|
+
self.history_file = 'CHANGELOG.md'
|
15
|
+
self.extra_rdoc_files = FileList['*.md']
|
16
|
+
self.extra_deps << ['ruby-termios', '~> 1']
|
17
|
+
end
|
18
|
+
|
19
|
+
# vim: syntax=ruby
|
data/bin/itiscold
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'itiscold'
|
2
|
+
require 'optparse'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
options = {
|
6
|
+
tty: ARGV.shift,
|
7
|
+
command: ARGV.shift,
|
8
|
+
table_name: 'samples'
|
9
|
+
}
|
10
|
+
|
11
|
+
def quit op
|
12
|
+
puts op
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
op = OptionParser.new do |opts|
|
17
|
+
opts.banner = "Usage: itiscold path_to_tty [samples|info|server] [options]"
|
18
|
+
|
19
|
+
opts.on '--csv', 'output samples as CSV' do
|
20
|
+
quit(opts) if options[:format]
|
21
|
+
options[:format] = :csv
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on '--json', 'output samples as JSON' do
|
25
|
+
quit(opts) if options[:format]
|
26
|
+
options[:format] = :json
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
op.parse!
|
31
|
+
|
32
|
+
quit(op) unless options[:command] && options[:tty]
|
33
|
+
|
34
|
+
case options[:command]
|
35
|
+
when 'samples'
|
36
|
+
temp = Itiscold.open options[:tty]
|
37
|
+
case options[:format]
|
38
|
+
when :csv
|
39
|
+
require 'csv'
|
40
|
+
data = temp.samples
|
41
|
+
CSV { |csv| csv << ['SampleTime', 'Temp']; data.each { |s| csv << s } }
|
42
|
+
when :json
|
43
|
+
require 'json'
|
44
|
+
puts JSON.dump temp.device_info.to_h
|
45
|
+
temp.samples.each { |s|
|
46
|
+
puts JSON.dump({ time: s.first.iso8601, temp: s.last })
|
47
|
+
}
|
48
|
+
end
|
49
|
+
when 'info'
|
50
|
+
require 'psych'
|
51
|
+
puts Psych.dump temp.device_info
|
52
|
+
when 'server'
|
53
|
+
Itiscold::WebServer.start options[:tty]
|
54
|
+
else
|
55
|
+
puts op
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
|
59
|
+
# vim: syntax=ruby
|
data/lib/itiscold.rb
ADDED
@@ -0,0 +1,416 @@
|
|
1
|
+
require 'termios'
|
2
|
+
require 'fcntl'
|
3
|
+
require 'webrick'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class Itiscold
|
7
|
+
VERSION = '1.0.0'
|
8
|
+
|
9
|
+
class TTY
|
10
|
+
include Termios
|
11
|
+
|
12
|
+
def self.open filename, speed, mode
|
13
|
+
if mode =~ /^(\d)(\w)(\d)$/
|
14
|
+
t.data_bits = $1.to_i
|
15
|
+
t.stop_bits = $3.to_i
|
16
|
+
t.parity = { 'N' => :none, 'E' => :even, 'O' => :odd }[$2]
|
17
|
+
t.speed = speed
|
18
|
+
t.read_timeout = 5
|
19
|
+
t.reading = true
|
20
|
+
t.update!
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.data_bits t, val
|
25
|
+
t.cflag &= ~CSIZE # clear previous values
|
26
|
+
t.cflag |= const_get("CS#{val}") # Set the data bits
|
27
|
+
t
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.stop_bits t, val
|
31
|
+
case val
|
32
|
+
when 1 then t.cflag &= ~CSTOPB
|
33
|
+
when 2 then t.cflag |= CSTOPB
|
34
|
+
else
|
35
|
+
raise
|
36
|
+
end
|
37
|
+
t
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.parity t, val
|
41
|
+
case val
|
42
|
+
when :none
|
43
|
+
t.cflag &= ~PARENB
|
44
|
+
when :even
|
45
|
+
t.cflag |= PARENB # Enable parity
|
46
|
+
t.cflag &= ~PARODD # Make it not odd
|
47
|
+
when :odd
|
48
|
+
t.cflag |= PARENB # Enable parity
|
49
|
+
t.cflag |= PARODD # Make it odd
|
50
|
+
else
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
t
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.speed t, speed
|
57
|
+
t.ispeed = const_get("B#{speed}")
|
58
|
+
t.ospeed = const_get("B#{speed}")
|
59
|
+
t
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.read_timeout t, val
|
63
|
+
t.cc[VTIME] = val
|
64
|
+
t.cc[VMIN] = 0
|
65
|
+
t
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.reading t
|
69
|
+
t.cflag |= CLOCAL | CREAD
|
70
|
+
t
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.open filename, speed = 115200
|
75
|
+
f = File.open filename, File::RDWR|Fcntl::O_NOCTTY|Fcntl::O_NDELAY
|
76
|
+
f.sync = true
|
77
|
+
|
78
|
+
# enable blocking reads, otherwise read timeout won't work
|
79
|
+
f.fcntl Fcntl::F_SETFL, f.fcntl(Fcntl::F_GETFL, 0) & ~Fcntl::O_NONBLOCK
|
80
|
+
|
81
|
+
t = Termios.tcgetattr f
|
82
|
+
t = TTY.data_bits t, 8
|
83
|
+
t = TTY.stop_bits t, 1
|
84
|
+
t = TTY.parity t, :none
|
85
|
+
t = TTY.speed t, speed
|
86
|
+
t = TTY.read_timeout t, 5
|
87
|
+
t = TTY.reading t
|
88
|
+
|
89
|
+
Termios.tcsetattr f, Termios::TCSANOW, t
|
90
|
+
Termios.tcflush f, Termios::TCIOFLUSH
|
91
|
+
|
92
|
+
temp = new f
|
93
|
+
|
94
|
+
# Try reading the version a few times before giving up
|
95
|
+
temp.flush
|
96
|
+
temp
|
97
|
+
end
|
98
|
+
|
99
|
+
def initialize tty
|
100
|
+
@tty = tty
|
101
|
+
end
|
102
|
+
|
103
|
+
DeviceInfo = Struct.new :station_number,
|
104
|
+
:model_number,
|
105
|
+
:sample_interval, # in seconds
|
106
|
+
:upper_limit,
|
107
|
+
:lower_limit,
|
108
|
+
:last_online,
|
109
|
+
:work_status,
|
110
|
+
:start_time,
|
111
|
+
:stop_button,
|
112
|
+
:sample_count,
|
113
|
+
:current_time,
|
114
|
+
:user_info,
|
115
|
+
:number,
|
116
|
+
:delay_time, # in seconds
|
117
|
+
:tone_set,
|
118
|
+
:alarm,
|
119
|
+
:temp_unit,
|
120
|
+
:temp_calibration,
|
121
|
+
:new_station_number
|
122
|
+
|
123
|
+
def device_info
|
124
|
+
val = retry_comm(1) do
|
125
|
+
@tty.write with_checksum([0xCC, 0x00, 0x06, 0x00]).pack('C5')
|
126
|
+
@tty.read 160
|
127
|
+
end
|
128
|
+
_, # C set number
|
129
|
+
station_no, # C station number
|
130
|
+
_, # C
|
131
|
+
model_no, # C model number
|
132
|
+
_, # C
|
133
|
+
rec_int_h, # C record interval hour
|
134
|
+
rec_int_m, # C record interval min
|
135
|
+
rec_int_s, # C record interval sec
|
136
|
+
upper_limit, # n upper limit
|
137
|
+
lower_limit, # s> lower limit
|
138
|
+
last_online, # A7 (datetime)
|
139
|
+
ws, # C work status
|
140
|
+
start_time, # A7 (datetime)
|
141
|
+
sb, # C stop button
|
142
|
+
_, # C
|
143
|
+
sample_count, # n number of records
|
144
|
+
current_time, # A7 (datetime)
|
145
|
+
user_info, # A100
|
146
|
+
number, # A10
|
147
|
+
delaytime, # C
|
148
|
+
tone_set, # C
|
149
|
+
alrm, # C
|
150
|
+
tmp_unit, # C
|
151
|
+
temp_calib, # C temp calibration
|
152
|
+
_, # A6 padding
|
153
|
+
check, # C padding
|
154
|
+
= val.unpack('C8ns>A7CA7CCnA7A100A10C5A6C')
|
155
|
+
check_checksum check, checksum(val.bytes.first(val.bytesize - 1))
|
156
|
+
DeviceInfo.new station_no, model_no,
|
157
|
+
(rec_int_h * 3600 + rec_int_m * 60 + rec_int_s),
|
158
|
+
upper_limit / 10.0, lower_limit / 10.0, unpack_datetime(last_online),
|
159
|
+
work_status(ws), unpack_datetime(start_time), allowed_decode(sb),
|
160
|
+
sample_count, unpack_datetime(current_time), user_info, number,
|
161
|
+
delay_time_decode(delaytime), allowed_decode(tone_set), alrm, temp_unit_decode(tmp_unit),
|
162
|
+
temp_calib / 10.0
|
163
|
+
end
|
164
|
+
|
165
|
+
def device_params= values
|
166
|
+
data = [
|
167
|
+
0x33, # C
|
168
|
+
values.station_number, # C
|
169
|
+
0x05, 0x00, # CC
|
170
|
+
] + split_time(values.sample_interval) + # CCC
|
171
|
+
[
|
172
|
+
(values.upper_limit * 10).to_i, # n
|
173
|
+
(values.lower_limit * 10).to_i, # s>
|
174
|
+
values.new_station_number || values.station_number, # C
|
175
|
+
allowed_encode(values.stop_button), # C
|
176
|
+
delay_time_encode(values.delay_time), # C
|
177
|
+
allowed_encode(values.tone_set), # C
|
178
|
+
values.alarm, # C
|
179
|
+
temp_unit_encode(values.temp_unit), # C
|
180
|
+
(values.temp_calibration * 10).to_i, # C
|
181
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 # C6
|
182
|
+
]
|
183
|
+
retry_comm(1) do
|
184
|
+
@tty.write with_checksum(data).pack('CCCCCCCns>CCCCCCCC6C')
|
185
|
+
@tty.read 3
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def set_device_number station_number, dev_number
|
190
|
+
str = dev_number.bytes.first(10)
|
191
|
+
str.concat([0x00] * (10 - str.length))
|
192
|
+
data = [ 0x33, station_number, 0x0b, 0x00 ] + str
|
193
|
+
|
194
|
+
retry_comm(1) do
|
195
|
+
@tty.write with_checksum(data).pack("C#{data.length + 1}")
|
196
|
+
@tty.read 3
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def set_user_info info, station_number = device_info.station_number
|
201
|
+
str = info.bytes.first(100)
|
202
|
+
str.concat([0x00] * (100 - str.length))
|
203
|
+
data = [ 0x33, station_number, 0x09, 0x00 ] + str
|
204
|
+
|
205
|
+
retry_comm(1) do
|
206
|
+
@tty.write with_checksum(data).pack("C#{data.length + 1}")
|
207
|
+
@tty.read 3
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def set_device_time! time = Time.now, station_number = device_info.station_number
|
212
|
+
data = [0x33, station_number, 0x07, 0x00] + encode_datetime(time)
|
213
|
+
|
214
|
+
retry_comm(1) do
|
215
|
+
@tty.write with_checksum(data).pack("C4nC6")
|
216
|
+
@tty.read 3
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
DataHeader = Struct.new :sample_count, :start_time
|
221
|
+
|
222
|
+
def data_header station
|
223
|
+
buf = retry_comm(1) do
|
224
|
+
@tty.write with_checksum([0x33, station, 0x1, 0x0]).pack('C5')
|
225
|
+
@tty.read 11
|
226
|
+
end
|
227
|
+
_, sample_count, start_time, check = buf.unpack 'CnA7C'
|
228
|
+
|
229
|
+
check_checksum check, checksum(buf.bytes.first(buf.bytesize - 1))
|
230
|
+
|
231
|
+
DataHeader.new sample_count, unpack_datetime(start_time)
|
232
|
+
end
|
233
|
+
|
234
|
+
def data_body station, page
|
235
|
+
buf = retry_comm(1) do
|
236
|
+
@tty.write with_checksum([0x33, station, 0x2, page]).pack('C5')
|
237
|
+
@tty.read
|
238
|
+
end
|
239
|
+
temps = buf.unpack("Cn#{(buf.bytesize - 2) / 2}C")
|
240
|
+
temps.shift # header
|
241
|
+
temps.pop # checksum
|
242
|
+
temps.map { |t| t / 10.0 }
|
243
|
+
end
|
244
|
+
|
245
|
+
def samples
|
246
|
+
info = device_info
|
247
|
+
header = data_header info.station_number
|
248
|
+
records = header.sample_count
|
249
|
+
page = 0
|
250
|
+
list = []
|
251
|
+
while records > 0
|
252
|
+
samples = data_body info.station_number, page
|
253
|
+
records -= samples.length
|
254
|
+
list += samples
|
255
|
+
page += 1
|
256
|
+
end
|
257
|
+
st = header.start_time
|
258
|
+
list.map.with_index { |v, i| [st + (i * info.sample_interval), v] }
|
259
|
+
end
|
260
|
+
|
261
|
+
def flush
|
262
|
+
Termios.tcflush @tty, Termios::TCIOFLUSH
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def split_time time_s
|
268
|
+
h = time_s / 3600
|
269
|
+
time_s -= (h * 3600)
|
270
|
+
m = time_s / 60
|
271
|
+
time_s -= (m * 60)
|
272
|
+
[h, m, time_s]
|
273
|
+
end
|
274
|
+
|
275
|
+
def retry_comm times
|
276
|
+
x = nil
|
277
|
+
loop do
|
278
|
+
x = yield
|
279
|
+
break if x || times == 0
|
280
|
+
flush
|
281
|
+
times -= 1
|
282
|
+
end
|
283
|
+
x
|
284
|
+
end
|
285
|
+
|
286
|
+
def temp_unit_decode u
|
287
|
+
case u
|
288
|
+
when 0x13 then 'F'
|
289
|
+
when 0x31 then 'C'
|
290
|
+
else
|
291
|
+
raise u.to_s
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def temp_unit_encode u
|
296
|
+
case u
|
297
|
+
when 'F' then 0x13
|
298
|
+
when 'C' then 0x31
|
299
|
+
else
|
300
|
+
raise u.to_s
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def delay_time_decode dt
|
305
|
+
case dt
|
306
|
+
when 0x00 then 0
|
307
|
+
when 0x01 then 30 * 60 # 30min in sec
|
308
|
+
when 0x10 then 60 * 60 # 60min in sec
|
309
|
+
when 0x11 then 90 * 60 # 90min in sec
|
310
|
+
when 0x20 then 120 * 60 # 120min in sec
|
311
|
+
when 0x21 then 150 * 60 # 150min in sec
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def delay_time_encode dt
|
316
|
+
case dt
|
317
|
+
when 0 then 0x00
|
318
|
+
when 30 * 60 then 0x01 # 30min in sec
|
319
|
+
when 60 * 60 then 0x10 # 60min in sec
|
320
|
+
when 90 * 60 then 0x11 # 90min in sec
|
321
|
+
when 120 * 60 then 0x20 # 120min in sec
|
322
|
+
when 150 * 60 then 0x21 # 150min in sec
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def allowed_decode sb
|
327
|
+
case sb
|
328
|
+
when 0x13 then :permit
|
329
|
+
when 0x31 then :prohibit
|
330
|
+
else
|
331
|
+
raise sb.to_s
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def allowed_encode sb
|
336
|
+
case sb
|
337
|
+
when :permit then 0x13
|
338
|
+
when :prohibit then 0x31
|
339
|
+
else
|
340
|
+
raise sb.to_s
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def work_status ws
|
345
|
+
case ws
|
346
|
+
when 0 then :not_started
|
347
|
+
when 1 then :start
|
348
|
+
when 2 then :stop
|
349
|
+
when 3 then :unknown
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def check_checksum expected, actual
|
354
|
+
raise "invalid checksum #{expected} == #{actual}" unless expected == actual
|
355
|
+
end
|
356
|
+
|
357
|
+
def unpack_datetime bytes
|
358
|
+
Time.new(*bytes.unpack('nC5'))
|
359
|
+
end
|
360
|
+
|
361
|
+
def encode_datetime time
|
362
|
+
[time.year, time.month, time.day, time.hour, time.min, time.sec]
|
363
|
+
end
|
364
|
+
|
365
|
+
def with_checksum list
|
366
|
+
list + [checksum(list)]
|
367
|
+
end
|
368
|
+
|
369
|
+
def checksum list
|
370
|
+
list.inject(:+) % 256
|
371
|
+
end
|
372
|
+
|
373
|
+
module WebServer
|
374
|
+
class TTYServlet < WEBrick::HTTPServlet::AbstractServlet
|
375
|
+
def initialize server, tty, mutex
|
376
|
+
super server
|
377
|
+
@tty = tty
|
378
|
+
@mutex = mutex
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
class InfoServlet < TTYServlet
|
383
|
+
def do_GET request, response
|
384
|
+
response.status = 200
|
385
|
+
response['Content-Type'] = 'text/json'
|
386
|
+
@mutex.synchronize do
|
387
|
+
json = JSON.dump(@tty.device_info.to_h)
|
388
|
+
response.body = json
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
class SampleServlet < TTYServlet
|
394
|
+
def do_GET request, response
|
395
|
+
response.status = 200
|
396
|
+
response['Content-Type'] = 'text/json'
|
397
|
+
@mutex.synchronize do
|
398
|
+
response.body = JSON.dump @tty.samples.map { |s|
|
399
|
+
{ time: s.first.iso8601, temp: s.last }
|
400
|
+
}
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def self.start tty
|
406
|
+
root = File.expand_path(File.join File.dirname(__FILE__), 'itiscold', 'public')
|
407
|
+
mutex = Mutex.new
|
408
|
+
temp = Itiscold.open tty
|
409
|
+
server = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => root)
|
410
|
+
server.mount "/samples", SampleServlet, temp, mutex
|
411
|
+
server.mount "/info", InfoServlet, temp, mutex
|
412
|
+
trap "INT" do server.shutdown end
|
413
|
+
server.start
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<link href='https://cdnjs.cloudflare.com/ajax/libs/metrics-graphics/2.11.0/metricsgraphics.css' rel='stylesheet' type='text/css'>
|
4
|
+
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
|
5
|
+
|
6
|
+
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
|
7
|
+
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
|
8
|
+
<script src="https://d3js.org/d3.v4.min.js"></script>
|
9
|
+
<script src='https://cdnjs.cloudflare.com/ajax/libs/metrics-graphics/2.11.0/metricsgraphics.js'></script>
|
10
|
+
<script>
|
11
|
+
var info;
|
12
|
+
var samples;
|
13
|
+
function capitalizeFirstLetter(string) {
|
14
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
15
|
+
}
|
16
|
+
$.when($.getJSON("/info", function(data) {
|
17
|
+
info = data;
|
18
|
+
}), $.getJSON("/samples", function(data) {
|
19
|
+
console.log("hello");
|
20
|
+
samples = data.map(function(sample) {
|
21
|
+
return { time: new Date(sample.time), temp: sample.temp };
|
22
|
+
});
|
23
|
+
})
|
24
|
+
).then(function() {
|
25
|
+
console.log(samples);
|
26
|
+
console.log(info);
|
27
|
+
$(function() {
|
28
|
+
var table = $("#device-info > tbody");
|
29
|
+
for (var key in info) {
|
30
|
+
if (info.hasOwnProperty(key)) {
|
31
|
+
var value = info[key];
|
32
|
+
if (value) {
|
33
|
+
var words = key.split("_");
|
34
|
+
key = words.map(capitalizeFirstLetter).join(" ");
|
35
|
+
table.append("<tr><td>" + key + "</td><td>" + value + "</td></tr>");
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
MG.data_graphic({
|
40
|
+
title: "Temperature",
|
41
|
+
description: "This graph shose a time series of temperatures",
|
42
|
+
data: samples,
|
43
|
+
width: 600,
|
44
|
+
height: 250,
|
45
|
+
target: '#temp-graph',
|
46
|
+
x_accessor: 'time',
|
47
|
+
y_accessor: 'temp',
|
48
|
+
})
|
49
|
+
});
|
50
|
+
});
|
51
|
+
</script>
|
52
|
+
</head>
|
53
|
+
<body>
|
54
|
+
<div class="container">
|
55
|
+
<h1 class="text-center">Temperature Graph</h1>
|
56
|
+
|
57
|
+
<div class="row">
|
58
|
+
<div class="col-xs-4">
|
59
|
+
<h2>Device Info</h2>
|
60
|
+
<table id="device-info" class="table table-striped table-condensed table-bordered">
|
61
|
+
<tbody>
|
62
|
+
</tbody>
|
63
|
+
</table>
|
64
|
+
</div>
|
65
|
+
<div class="col-xs-8 text-center">
|
66
|
+
<div id='temp-graph'></div>
|
67
|
+
</div>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</body>
|
71
|
+
</html>
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: itiscold
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Patterson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ruby-termios
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rdoc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: hoe
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.16'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.16'
|
55
|
+
description: |-
|
56
|
+
A thing that reads data from Elitech RC-5 temp sensor. Protocol documentation
|
57
|
+
can be found here:
|
58
|
+
|
59
|
+
https://github.com/civic/elitech-datareader/blob/master/rc-4-data.md
|
60
|
+
|
61
|
+
I've tested this on my RC-5, but not an RC-4.
|
62
|
+
email:
|
63
|
+
- tenderlove@ruby-lang.org
|
64
|
+
executables:
|
65
|
+
- itiscold
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files:
|
68
|
+
- CHANGELOG.md
|
69
|
+
- LICENSE.md
|
70
|
+
- Manifest.txt
|
71
|
+
- README.md
|
72
|
+
files:
|
73
|
+
- CHANGELOG.md
|
74
|
+
- LICENSE.md
|
75
|
+
- Manifest.txt
|
76
|
+
- README.md
|
77
|
+
- Rakefile
|
78
|
+
- bin/itiscold
|
79
|
+
- lib/itiscold.rb
|
80
|
+
- lib/itiscold/public/index.html
|
81
|
+
- test/test_itiscold.rb
|
82
|
+
homepage: https://github.com/tenderlove/itiscold
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options:
|
88
|
+
- "--main"
|
89
|
+
- README.md
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.6.8
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: A thing that reads data from Elitech RC-5 temp sensor
|
108
|
+
test_files: []
|