zerbo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ /.rvmrc
2
+ /.bundle
3
+ /Gemfile.lock
4
+ /pkg/*
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Tim Pope
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
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,33 @@
1
+ = Zerbo
2
+
3
+ Interface with the Zeo Personal Sleep Coach over USB with Ruby.
4
+
5
+ == Prerequisites
6
+
7
+ * Depends on ruby-serialport, which is currently limited to Ruby 1.8.
8
+
9
+ * Requires constructing your own USB cable. See
10
+ http://zeorawdata.sourceforge.net/starting.html for details. You may need to
11
+ install the drivers from http://www.ftdichip.com/Drivers/VCP.htm as well.
12
+
13
+ * You may have to do some source diving, as the documentation is basically
14
+ limited to this file (don't worry, the source is pretty short, too).
15
+
16
+ == Usage
17
+
18
+ zeo = Zerbo.connect('/dev/zeo')
19
+
20
+ On OS X, the device you want can probably be found in
21
+ <tt>/dev/tty.usbserial*</tt>. On Linux, look at <tt>/dev/ttyUSB*</tt>. I
22
+ can't speak for Windows, but the Python library works there, so presumably
23
+ Zerbo can be made to work as well.
24
+
25
+ zeo.on_sleep_stage do |stage|
26
+ puts stage
27
+ end
28
+
29
+ zeo.on_event do |event|
30
+ p event
31
+ end
32
+
33
+ zeo.run
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/lib/zerbo.rb ADDED
@@ -0,0 +1,331 @@
1
+ class Zerbo
2
+
3
+ class Error < RuntimeError
4
+ end
5
+
6
+ # Bind to a serial port.
7
+ def self.connect(device='/dev/zeo')
8
+ new(device)
9
+ end
10
+
11
+ attr_reader :device
12
+
13
+ def initialize(device=nil)
14
+ device ||= '/dev/zeo'
15
+ if device.respond_to?(:read)
16
+ @device = device
17
+ else
18
+ require 'serialport'
19
+ @device = SerialPort.new(device, 38400)
20
+ unless RUBY_PLATFORM =~ /darwin/
21
+ @device.read_timeout = 0
22
+ end
23
+ end
24
+ @callbacks = []
25
+ end
26
+
27
+ def next_packet
28
+ true until read(1) == 'A'
29
+ version = read(1)
30
+ checksum, length, inverse = read(5).unpack('Cvv')
31
+ raise Error, "Invalid length" unless length ^ inverse == 65535
32
+ raise Error, "Unsupported version #{version}" unless version == '4'
33
+ time, subtime, sequence = read(4).unpack('CvC')
34
+ data = read(length)
35
+ sum = 0
36
+ data.each_byte do |b|
37
+ sum += b
38
+ end
39
+ raise Error, "Invalid checksum" unless sum % 256 == checksum
40
+ instantiate(time, subtime, sequence, data)
41
+ end
42
+ alias next next_packet
43
+
44
+ def read(*args)
45
+ device.read(*args)
46
+ end
47
+ protected :read
48
+
49
+ def instantiate(time, subtime, sequence, data)
50
+ type, rest = data.unpack('Ca*')
51
+ if type.zero?
52
+ type, rest = rest.unpack('Ca*')
53
+ end
54
+ klass = DATA_TYPE_CLASSES.detect {|c| c.id == type}
55
+ klass.new(self, time, subtime, sequence, rest)
56
+ end
57
+ protected :instantiate
58
+
59
+ def add_callback(klass = Object, &block)
60
+ @callbacks << [klass, block]
61
+ self
62
+ end
63
+
64
+ def on_event(&block)
65
+ add_callback(Event, &block)
66
+ end
67
+
68
+ def on_sleep_stage(&block)
69
+ add_callback(SleepStage, &block)
70
+ end
71
+
72
+ def run
73
+ loop do
74
+ packet = next_packet
75
+ @callbacks.each do |(klass, block)|
76
+ if packet.kind_of?(klass)
77
+ block.call(packet)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def inspect
84
+ "#<#{self.class.inspect} #{device.inspect}>"
85
+ end
86
+
87
+ protected
88
+
89
+
90
+ DATA_TYPE_CLASSES = []
91
+
92
+ class Packet
93
+
94
+ def self.inherited(klass)
95
+ DATA_TYPE_CLASSES << klass
96
+ end
97
+
98
+ class <<self
99
+ attr_accessor :id
100
+ end
101
+
102
+ attr_reader :owner, :type, :sequence, :data
103
+
104
+ def type
105
+ self.class.id
106
+ end
107
+
108
+ def initialize(owner, time, subtime, sequence, data)
109
+ @owner = owner
110
+ @sequence = sequence
111
+ @data = data
112
+ end
113
+
114
+ def guess_length
115
+ data.index('A')
116
+ end
117
+
118
+ def to_i
119
+ if data.length == 2
120
+ unpack('v').first
121
+ elsif data.length == 4
122
+ unpack('V').first
123
+ else
124
+ raise NotImplementedError
125
+ end
126
+ end
127
+
128
+ def inspect
129
+ format_inspect((to_i || data).inspect)
130
+ end
131
+
132
+ protected
133
+
134
+ def unpack(arg)
135
+ @data.unpack(arg)
136
+ end
137
+
138
+ def format_inspect(custom)
139
+ "#<#{self.class.inspect}(#{sequence}) #{custom}>"
140
+ end
141
+
142
+ end
143
+
144
+ class SliceEnd < Packet
145
+ self.id = 0x02
146
+ end
147
+
148
+ class Version < Packet
149
+ self.id = 0x03
150
+ end
151
+
152
+ class Waveform < Packet
153
+ self.id = 0x80
154
+ undef to_i
155
+
156
+ def raw
157
+ data.unpack('v128').map do |v|
158
+ v > 0x7fff ? -0x10000 ^ v : v
159
+ end
160
+ end
161
+
162
+ def filtered
163
+ unless @filtered
164
+ # blindly stolen from the Python library.
165
+ filter = [
166
+ 0.0056, 0.0190, 0.0113, -0.0106, 0.0029, 0.0041,
167
+ -0.0082, 0.0089, -0.0062, 0.0006, 0.0066, -0.0129,
168
+ 0.0157, -0.0127, 0.0035, 0.0102, -0.0244, 0.0336,
169
+ -0.0323, 0.0168, 0.0136, -0.0555, 0.1020, -0.1446,
170
+ 0.1743, 0.8150, 0.1743, -0.1446, 0.1020, -0.0555,
171
+ 0.0136, 0.0168, -0.0323, 0.0336, -0.0244, 0.0102,
172
+ 0.0035, -0.0127, 0.0157, -0.0129, 0.0066, 0.0006,
173
+ -0.0062, 0.0089, -0.0082, 0.0041, 0.0029, -0.0106,
174
+ 0.0113, 0.0190, 0.0056
175
+ ]
176
+ p = raw.length
177
+ q = filter.length
178
+ n = p + q - 1
179
+ @filtered = []
180
+ n.times do |k|
181
+ t = 0
182
+ lower = [0, k-(q-1)].max
183
+ upper = [p-1, k].min
184
+ lower.upto(upper) do |i|
185
+ t = t + raw[i] * filter[k-i]
186
+ end
187
+ @filtered << (t*1e6).round/1e6
188
+ end
189
+ end
190
+ @filtered
191
+ end
192
+
193
+ def to_a
194
+ filtered[90...218]
195
+ end
196
+
197
+ def inspect
198
+ format_inspect(raw.inspect[1..-2])
199
+ end
200
+ end
201
+
202
+ class FrequencyBins < Packet
203
+ self.id = 0x83
204
+ undef to_i
205
+
206
+ def to_a
207
+ unpack('v7')
208
+ end
209
+
210
+ def inspect
211
+ format_inspect(to_a.inspect[1..-2])
212
+ end
213
+ end
214
+
215
+ class SQI < Packet
216
+ self.id = 0x84
217
+ end
218
+
219
+ class ZeoTimeStamp < Packet
220
+ self.id = 0x8a
221
+
222
+ def to_time
223
+ Time.at(to_i).utc
224
+ end
225
+
226
+ def to_s
227
+ to_time.strftime('%Y-%m-%dT%H:%M:%S')
228
+ end
229
+
230
+ def inspect
231
+ format_inspect(to_s)
232
+ end
233
+ end
234
+
235
+ class Impedence < Packet
236
+ self.id = 0x97
237
+ end
238
+
239
+ class BadSignal < Packet
240
+ self.id = 0x9c
241
+
242
+ def to_b
243
+ !to_i.zero?
244
+ end
245
+
246
+ def inspect
247
+ format_inspect(to_b)
248
+ end
249
+ end
250
+
251
+ class SleepStage < Packet
252
+ self.id = 0x9d
253
+
254
+ LOOKUP = [
255
+ 'Undefined',
256
+ 'Awake',
257
+ 'REM',
258
+ 'Light',
259
+ 'Deep'
260
+ ]
261
+
262
+ def to_s
263
+ LOOKUP[to_i]
264
+ end
265
+
266
+ def inspect
267
+ format_inspect(to_s)
268
+ end
269
+
270
+ def awake?
271
+ to_s == 'Awake'
272
+ end
273
+
274
+ def asleep?
275
+ rem? || light? || deep?
276
+ end
277
+
278
+ def rem?
279
+ to_s == 'REM'
280
+ end
281
+
282
+ def light?
283
+ to_s == 'Light'
284
+ end
285
+
286
+ def deep?
287
+ to_s == 'Deep'
288
+ end
289
+ end
290
+
291
+ class Event < Packet
292
+ self.id = 0x00
293
+ end
294
+
295
+ class NightStart < Event
296
+ self.id = 0x05
297
+ end
298
+
299
+ class SleepOnset < Event
300
+ self.id = 0x07
301
+ end
302
+
303
+ class HeadbandDocked < Event
304
+ self.id = 0x0e
305
+ end
306
+
307
+ class HeadbandUnDocked < Event
308
+ self.id = 0x0f
309
+ end
310
+
311
+ class AlarmOff < Event
312
+ self.id = 0x10
313
+ end
314
+
315
+ class AlarmSnooze < Event
316
+ self.id = 0x11
317
+ end
318
+
319
+ class AlarmPlay < Event
320
+ self.id = 0x13
321
+ end
322
+
323
+ class NightEnd < Event
324
+ self.id = 0x15
325
+ end
326
+
327
+ class NewHeadband < Event
328
+ self.id = 0x24
329
+ end
330
+
331
+ end
@@ -0,0 +1,3 @@
1
+ module Zerbo
2
+ VERSION = "0.0.1"
3
+ end
data/zerbo.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "zerbo/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "zerbo"
7
+ s.version = Zerbo::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Tim Pope"]
10
+ s.email = "ruby@tpo"+'pe.org'
11
+ s.homepage = "http://github.com/tpope/zerbo"
12
+ s.summary = "Zeo Personal Sleep Coach Ruby Interface"
13
+ s.description = "Build a serial cable for your Zeo and interface with it with this library."
14
+
15
+ s.rubyforge_project = "zerbo"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_runtime_dependency("ruby-serialport")
23
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zerbo
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Tim Pope
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-07 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ruby-serialport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Build a serial cable for your Zeo and interface with it with this library.
36
+ email: ruby@tpope.org
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - MIT-LICENSE
47
+ - README.rdoc
48
+ - Rakefile
49
+ - lib/zerbo.rb
50
+ - lib/zerbo/version.rb
51
+ - zerbo.gemspec
52
+ has_rdoc: true
53
+ homepage: http://github.com/tpope/zerbo
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ requirements: []
80
+
81
+ rubyforge_project: zerbo
82
+ rubygems_version: 1.6.1
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Zeo Personal Sleep Coach Ruby Interface
86
+ test_files: []
87
+