rustle 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a832c77bff50413388dc4b5e7930c623a8807c5e
4
+ data.tar.gz: 4565b1dfd7ed5ddae95fe044b3419a3dfa850224
5
+ SHA512:
6
+ metadata.gz: ab2b36845325b8f6ad07c9c78ffbb83c6372b7c167c213d1444ffb1f3c9c18c7ba0fb42f5726c023a04cc2384b450ccf5596129764d41741a0d402bcf56a4774
7
+ data.tar.gz: 5eeea99ff6e98b0e7d310aa55f5a0b603a5feffa132dc915d6e2d2870c820dd8cc4a010aea53cdfd53a64d1e3dae4cb6f37106573e98d91cf0900a7c8b92bf32
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ dsl_example.rb
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format doc
2
+ --color
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Rustle Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ #### New
6
+
7
+ * Strip now relies on a new class, Frame, to handle its state changes. With this system in place, serialization of strip state is much more efficient, and thus, timing is more precise, and much higher framerates are achievable. Strip objects now also maintain a queue of their future frames, and expose methods which allow frames to be advanced through.
8
+ * Code is now documented.
9
+
10
+ #### Fixed
11
+
12
+ * An oversight where the Strip class tried to call `#off` on itself, which raised a NilClass exception.
13
+
14
+ #### Deprecated
15
+
16
+ * `Color#brighten` and `Color#darken` have both been removed due to their apparent uselessness. They may be restored in future versions should a use for them be found.
17
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rustle.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrew Hamon and Steven Petryk
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ ![Rustle](rustle_logo.png "Rustle Logo")
2
+
3
+ Rustle is a gem that aims to allow you to control an addressable RGB LED strip in Ruby (using an Arduino as a middleman). Run it on your computer, a Raspberry Pi, or whatever you want! All you need is a strip, an Arduino, and a serial connection.
4
+
5
+ ## Installation
6
+
7
+ It's pretty easy.
8
+
9
+ $ gem install rustle
10
+
11
+ ## Getting Started
12
+
13
+ ### 1. Set up your Arduino
14
+
15
+ Upload the [basic.ino](sketches/basic.ino) sketch to your Arduino. Make sure that you take a look at the file first and modify it to suit your physical setup.
16
+
17
+ **Gotcha**: the Arduino resets itself upon any serial communication. We are currently considering switching to the [firmata protocol](http://firmata.org/wiki/Main_Page) in order to work around this problem. For now, you will need to **place a 10μF capacitor between the `RESET` and `GND` pins on your Arduino after uploading the sketch** if you want to use Rustle from a static Ruby script.
18
+
19
+ The sketch itself is still being worked on. Our goal, in the future, is to simply allow you to call `rustle arduino:prepare` to automatically generate and upload the sketch (with the number of LEDs, frames per second, etc) to the Arduino using [Ino](http://inotool.org/).
20
+
21
+ ### 2. Connect to the Arduino
22
+
23
+ The majority of Rustle's actions are performed by calling methods on a receiver's LED strip. First, connect to your receiver:
24
+
25
+ ```ruby
26
+ arduino = Rustle::Receiver.new do
27
+ # replace this path with the one specified by the Arduino IDE
28
+ port_file "/dev/[PORTFILE]"
29
+
30
+ # replace this with the number of LEDs connected to your receiver
31
+ num_leds 30
32
+ end
33
+ ```
34
+
35
+ ### 3. Throw a party
36
+
37
+ That's it! You can now control your LED strip with Ruby. Try out some of the following (this might be easier in a Ruby console):
38
+
39
+ ```ruby
40
+ # Adding on to the code from earlier...
41
+ strip = arduino.strip
42
+
43
+ # Make the strip red
44
+ strip.to Color.hex('f00')
45
+
46
+ # Make a fancy-schmancy wipe transition to white
47
+ strip.transition :wipe_to, 2000, color: white
48
+ ```
49
+
50
+ Want to make a custom transition? It's easy! See the [Transition class](lib/rustle/transition.rb) for a quick guide. If you want to make it a part of Rustle, throw it into the [lib/transitions](lib/transitions) folder, and submit a pull request.
51
+
52
+ ## Documentation
53
+
54
+ $ yard
55
+
56
+ Alternatively, view it on [RubyDoc.info](http://rubydoc.info/github/stevenpetryk/rustle).
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,230 @@
1
+ module Rustle
2
+ class Color
3
+ # @return [Fixnum] the red value of the color
4
+ attr_reader :r
5
+
6
+ # @return [Fixnum] the green value of the color
7
+ attr_reader :g
8
+
9
+ # @return [Fixnum] the blue value of the color
10
+ attr_reader :b
11
+
12
+ # The Regex which verifies that a valid hex color code is supplied. It
13
+ # merely checks that a string is 6 characters long and consists of
14
+ # hexadecimal characters.
15
+ VALID_HEX_COLOR = /^[0-9a-f]{6}$/i
16
+
17
+ # Creates a new Color object given RGB values in the range +0..255+. Other
18
+ # values are accepted, but they will be coerced into this range (this is
19
+ # preferable to throwing exceptions, because there's a slim chance that an
20
+ # internal method will have some rounding errors).
21
+ #
22
+ # @param [Fixnum] r the red value
23
+ # @param [Fixnum] g the green value
24
+ # @param [Fixnum] b the blue value
25
+ #
26
+ # @return [Color] a new Color object with the given RGB values.
27
+ def initialize(r, g, b)
28
+ @r = normalize_rgb_param(r)
29
+ @g = normalize_rgb_param(g)
30
+ @b = normalize_rgb_param(b)
31
+ end
32
+
33
+ # Creates a new Color object given RGB values. Equivalent to {#initialize}.
34
+ def self.rgb(r, g, b)
35
+ self.new(r, g, b)
36
+ end
37
+
38
+ # @return [Array<Fixnum>] the Color's RGB values as an array with the
39
+ # format +[r, g, b]+
40
+ def to_a
41
+ [@r, @g, @b].map(&:floor)
42
+ end
43
+
44
+ # Serializes a color into a string of +chars+.
45
+ # Note that each color is coerced into the range +0..254+, since our
46
+ # end-of-frame code is 255.
47
+ #
48
+ # @return [String] a string representation of the color.
49
+ def serialize
50
+ to_a.map { |c| self.class.fit_within_range(c, 0, 254).chr }.join
51
+ end
52
+
53
+ # Checks if a color is equal to another color.
54
+ #
55
+ # @param [Color] other the other Color object
56
+ #
57
+ # @example
58
+ # red_rgb = Rustle::Color.rgb(255, 0, 0)
59
+ # red_hex = Rustle::Color.hex('f00')
60
+ # blue = Rustle::Color.rgb(0, 0, 255)
61
+ #
62
+ # red_rgb.eql? red_hex # => true
63
+ # red_rgb.eql? blue # => false
64
+ #
65
+ # @return [Boolean] true if the colors are equal, false if they are not.
66
+ def eql?(other)
67
+ [:r, :g, :b].all? { |c| self.send(c) == other.send(c) }
68
+ end
69
+
70
+ # Creates a new color object given a hexadecimal value.
71
+ #
72
+ # @param [String] hex_value the hexadecimal representation of a color
73
+ #
74
+ # @return [Color] a new Color object based on the given hexadecimal value.
75
+ #
76
+ # @example
77
+ # # Valid
78
+ # Color.hex('#faa')
79
+ # Color.hex('fa03f2')
80
+ # Color.hex('#fa03f2')
81
+ #
82
+ # # Invalid (will raise)
83
+ # Color.hex('fzx')
84
+ # Color.hex('af3d')
85
+ def self.hex(hex_value)
86
+ # Remove leading hash
87
+ hex_value = hex_value[1..-1] if hex_value[0] == '#'
88
+
89
+ # Convert f00 to ff0000
90
+ if hex_value.length == 3
91
+ hex_value = hex_value.split("").map { |s| s+s }.join
92
+ end
93
+
94
+ # Validate the format
95
+ raise InvalidHexColorCode unless hex_value =~ VALID_HEX_COLOR
96
+
97
+ self.new *( hex_value.scan(/.{2}/).map(&:hex) )
98
+ end
99
+
100
+ # Creates a new Color object given HSB color values.
101
+ #
102
+ # @param [Fixnum] hue the color's hue (between 0 and 360)
103
+ # @param [Float] sat the color's saturation (between 0 and 1)
104
+ # @param [Float] bri the color's brightness (between 0 and 1)
105
+ #
106
+ # @return [Color] a new color object based on the given HSB values.
107
+ def self.hsb(hue, sat, bri)
108
+ hue = fit_within_range(hue, 0, 360)
109
+ sat = fit_within_range(sat, 0, 1)
110
+ bri = fit_within_range(bri, 0, 1)
111
+
112
+ if sat == 0
113
+ r = g = b = bri
114
+ else
115
+ hh = hue % 360
116
+ hh /= 60.0
117
+ i = hh.to_i
118
+ f = hh - i
119
+
120
+ p = bri * (1 - sat)
121
+ q = bri * (1 - sat * f)
122
+ t = bri * (1 - sat * (1 - f))
123
+
124
+ case i
125
+ when 0
126
+ r = bri
127
+ g = t
128
+ b = p
129
+ when 1
130
+ r = q
131
+ g = bri
132
+ b = p
133
+ when 2
134
+ r = p
135
+ g = bri
136
+ b = t
137
+ when 3
138
+ r = p
139
+ g = q
140
+ b = bri
141
+ when 4
142
+ r = t
143
+ g = p
144
+ b = bri
145
+ else
146
+ r = bri
147
+ g = p
148
+ b = q
149
+ end
150
+ end
151
+
152
+ self.new *( [r, g, b].map { |c| (c * 255 + 0.5) } )
153
+ end
154
+
155
+ # Converts the Color object to an array of HSB values in the format
156
+ # [hue, saturation, brightness].
157
+ #
158
+ # @return [Array<Fixnum>] the color as an HSB array
159
+ def to_hsb
160
+ r = @r / 255.0
161
+ g = @g / 255.0
162
+ b = @b / 255.0
163
+ max = [r, g, b].max
164
+ min = [r, g, b].min
165
+ delta = max - min
166
+ hue = 0.0
167
+ brightness = max
168
+ saturation = max == 0 ? 0 : (max - min) / max
169
+
170
+ if delta != 0
171
+ if r == max
172
+ hue = (g - b) / delta
173
+ else
174
+ if g == max
175
+ hue = 2 + (b - r) / delta
176
+ else
177
+ hue = 4 + (r - g) / delta
178
+ end
179
+ end
180
+
181
+ hue *= 60
182
+ hue += 360 if hue < 0
183
+ end
184
+ [hue, saturation, brightness]
185
+ end
186
+
187
+ # Creates an intermediate between the current Color object and another
188
+ # Color object. It does so by simple linear RGB transitioning, which seems
189
+ # to work just fine when working with RGB LED strips (even though, in most)
190
+ # cases, HSB transitioning would be better, it is not worth the extra
191
+ # processing time).
192
+ #
193
+ # @param [Color] other the other Color object
194
+ # @param [Float] amount the degree to which the color should be transitioned
195
+ # +(0..1)+.
196
+ #
197
+ # @return [Color] the new, partially transitioned Color object
198
+ #
199
+ # @example
200
+ # red = Color.new(255, 0, 0)
201
+ # blue = Color.new(0, 0, 255)
202
+ #
203
+ # red.transition_to(blue, 0.5) # => <Color r=127, g=0, b=127>
204
+ def transition_to(other, amount)
205
+ old_col = self.to_a
206
+ new_col = other.to_a
207
+
208
+ diff = old_col.each_with_index.map { |v, i| (new_col[i] - v) * amount }
209
+
210
+ self.class.rgb *old_col.each_with_index.map { |v, i| v + diff[i] }
211
+ end
212
+
213
+ private
214
+
215
+ # Forces `val` into the range 0..255
216
+ def normalize_rgb_param(value)
217
+ self.class.fit_within_range(value, 0, 255)
218
+ end
219
+
220
+ # Coerces `value` into the range given
221
+ def self.fit_within_range(value, min_allowed, max_allowed)
222
+ if (value <= max_allowed) && (value >= min_allowed)
223
+ value
224
+ else
225
+ value > max_allowed ? max_allowed : min_allowed
226
+ end
227
+ end
228
+
229
+ end
230
+ end
@@ -0,0 +1,10 @@
1
+ module Rustle
2
+ # A general Rustle exception
3
+ class Error < StandardError; end
4
+
5
+ # Raised when user fails to provide a port_file
6
+ class PortFileNotSpecified < Error; end
7
+
8
+ # Raised when a user provides an invalid hexadecimal color
9
+ class InvalidHexColorCode < Error; end
10
+ end
@@ -0,0 +1,42 @@
1
+ module Rustle
2
+ # = Frames
3
+ #
4
+ # All strip changes are sent by serializing frames and sending them to the
5
+ # receiver. Serialization of a frame is done by serializing all {Color}
6
+ # objects in the +leds+ array (see {Color#serialize}), and then appending the
7
+ # +char+ 255 to the end. Serialized frames are, thus, strings of +chars+,
8
+ # where each +char+ is a single RGB color channel.
9
+ #
10
+ # It is worth noting that, since 255 is our end-of-frame code, all color data
11
+ # sent as an integer in the range +0..254+. It is safe to assume that an
12
+ # approximate 1/255th drop in brightness is not noticeable. There is, however,
13
+ # the option of modifying the sketch to multiply each RGB channel by
14
+ # 1.004 and +floor+ing the value.
15
+ #
16
+ # Coercion of RGB values into the range +0..254+ is handled in
17
+ # {Color#serialize}.
18
+ class Frame
19
+ # LEDs in a frame are represented as an array of {Color} objects. Each Color
20
+ # object corresponds to a given LED. The idea is that any physical LED
21
+ # matches its corresponding Color object.
22
+ #
23
+ # @return [Array<Color>] an array of {Color} objects (LEDs).
24
+ attr_reader :leds
25
+
26
+ # Initializes a new frame.
27
+ #
28
+ # @param [Array] leds n array of {Color} objects, whose length is the same
29
+ # as the number of LEDs connected to the strip.
30
+ def initialize(leds)
31
+ @leds = leds
32
+ end
33
+
34
+ # Serializes all LED {Color} objects (see {Color#serialize}) and appends
35
+ # the end-of-frame code (+255.chr+)
36
+ #
37
+ # @return [String] the serialized frame, represented as a string of +chars+.
38
+ def serialize
39
+ @leds.map(&:serialize).join + 255.chr
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,73 @@
1
+ require 'serialport'
2
+
3
+ module Rustle
4
+ # = Receivers
5
+ #
6
+ # Your receiver is, effectively, your Arduino. The receiver class's purpose is
7
+ # to manage the serial communication between Ruby and your Arduino, and to
8
+ # ensure that changes made to a Strip object are reflected immediately on the
9
+ # actual LED strip. This class, however, is *not* responsible for actual
10
+ # serialization; the Frame and Color classes serialize themselves.
11
+ #
12
+ # The Receiver class is initialized using a block.
13
+ #
14
+ # bedroom_arduino = Rustle::Receiver.new do
15
+ # port_file "/dev/[PORT_FILE]"
16
+ # num_leds 30
17
+ # end
18
+ #
19
+ # Your port file can be found using the Arduino IDE.
20
+ class Receiver
21
+ # When your receiver is instantiated, a {Strip} object is automatically
22
+ # created alongside it.
23
+ # @return [Strip] the strip associated with the Receiver
24
+ attr_reader :strip
25
+
26
+ # Initializes a new receiver.
27
+ #
28
+ # @example
29
+ # kitchen_arduino = Rustle::Receiver.new do
30
+ # port_file "/dev/tty.usbmodem411" # replace this
31
+ # num_leds 60
32
+ # end
33
+ #
34
+ # @yieldparam [String] port_file the location of serial port file. It can be
35
+ # found in the Arduino IDE (or via Ino).
36
+ # @yieldparam [Fixnum] baud (optional) the serial baud rate (make sure it matches the one in
37
+ # your sketch)
38
+ # @yieldparam [Fixnum] num_leds (optional) the total number of LEDs connected to your
39
+ # receiver. Note that this attribute only affects how the {Strip} object
40
+ # is instantiated; the number of LEDs is not actually stored in the
41
+ # Receiver object.
42
+ def initialize(&block)
43
+ # Default value
44
+ @baud = 921_600
45
+
46
+ instance_eval &block
47
+
48
+ init_serialport
49
+ end
50
+
51
+ # Serializes +frame+ and sends it off to the the serial port. +push_frame+
52
+ # is used internally, so its direct use is discouraged, but it would work
53
+ # in theory.
54
+ # @param [Frame] frame a frame object
55
+ def push_frame(frame)
56
+ @port.write frame.serialize
57
+ end
58
+
59
+ private
60
+
61
+ # instance_eval-specific methods for our DSL
62
+ def port_file(file); @port_file = file; end
63
+ def baud(int); @baud = int; end
64
+ def num_leds(count)
65
+ @strip = Strip.new(self, count)
66
+ end
67
+
68
+ # Instantiates a new serial port
69
+ def init_serialport
70
+ @port = SerialPort.new @port_file, @baud, 8, 1, SerialPort::NONE
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,95 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Rustle
4
+ # = Strips
5
+ #
6
+ # The Strip class' purpose is to respond to requests to change the color of
7
+ # the physical strip, and send changes to the {Receiver} when appropriate.
8
+ # All Strip objects also have a buffer, which is simply an array of {Frame}
9
+ # objects. Calling any method which advances to the next frame causes the
10
+ # Strip to request the Receiver class to push an update over the serial port.
11
+ #
12
+ # New strips cannot be instantiated outside of the {Receiver} class.
13
+ # See {Receiver#initialize} for details on how Strips are instantiated.
14
+ class Strip
15
+ # @return [Receiver] the receiver associated with the strip
16
+ attr_reader :receiver
17
+
18
+ # @return [Fixnum] the number of LEDs connected to the strip
19
+ attr_reader :num_leds
20
+
21
+ # Initializes a new strip given a {Receiver} object and the number of LEDs
22
+ # attached to the strip.
23
+ #
24
+ # @param [Receiver] receiver
25
+ # @param [Fixnum] num_leds the number of LEDs connected to the strip.
26
+ def initialize(receiver, num_leds = 32)
27
+ @receiver = receiver
28
+ @num_leds = num_leds
29
+ @queue = []
30
+ end
31
+
32
+ # @return [Frame] the next frame in the buffer. If the buffer only has one
33
+ # frame, it returns the current frame.
34
+ def next_frame
35
+ @queue[1] || current_frame
36
+ end
37
+
38
+ # Advances the strip to the next frame.
39
+ def next_frame!
40
+ @receiver.push_frame(next_frame)
41
+ @queue.shift if @queue.length > 1
42
+ end
43
+
44
+ # @return [Frame] the current frame being displayed by the strip.
45
+ def current_frame
46
+ @queue.first
47
+ end
48
+
49
+ # Queues an array of frames for transitions. Note: this does *not*
50
+ # cause any physical changes; it merely prepares a list thereof.
51
+ #
52
+ # @param [Array<Frame>] frames an array of frames to load into the queue.
53
+ def queue_frames(frames)
54
+ @queue += frames
55
+ end
56
+
57
+ # Turns off all LEDs on the strip
58
+ def off!
59
+ @queue << Frame.new([Color.new(0,0,0)] * @num_leds)
60
+ next_frame!
61
+ end
62
+
63
+ # Changes all LEDs to a particular color
64
+ #
65
+ # @param [Color] color
66
+ def to(color)
67
+ @queue << Frame.new([color] * @num_leds)
68
+ next_frame!
69
+ end
70
+
71
+ # Instantiates and executes a {Transition} subclass.
72
+ #
73
+ # @example
74
+ # strip = # [initialized strip...]
75
+ #
76
+ # # Transition the strip to red in 2 seconds using the wipe-to transition
77
+ # strip.transition :wipe_to, 2000, color: Rustle::Color.new(255, 0, 0)
78
+ #
79
+ # @param [Symbol] klass a symbol representing the name of the class (e.g.
80
+ # for {WipeToTransition}, the corresponding symbol would be +:wipe_to+).
81
+ # @param [Fixnum] duration the duration of the transition in milliseconds
82
+ # @param [Hash] opts a hash containing transition-specific options
83
+ #
84
+ # @return [Transition] an instance of a Transition subclass
85
+ def transition(klass, duration, opts = {})
86
+ # Default to white
87
+ opts[:color] ||= Color.rgb(255, 255, 255)
88
+
89
+ # :fade_to => FadeToTransition
90
+ klass = "#{klass.to_s.camelize}Transition".constantize
91
+
92
+ klass.new(self, duration, opts).frames
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,86 @@
1
+ module Rustle
2
+ # = Transitions
3
+ #
4
+ # Transitions are created by subclassing this class. See {FadeToTransition}
5
+ # for the most basic example.
6
+ #
7
+ # == Creating a Transition
8
+ # You'll need to create a subclass of {Transition} and require it in your
9
+ # project.
10
+ #
11
+ # === Override the subclass's {#setup} and {#animate} methods.
12
+ # Use +setup+ to create any instance variables you may need to calculate
13
+ # before the transitio renders. The +opts+ passed in when calling
14
+ # {Strip#transition} are preserved and sent to this method.
15
+ #
16
+ # {#animate} is called once-per-LED-per-frame, and expects you to return a
17
+ # {Color object}.
18
+ class Transition
19
+ attr_reader :frames, :duration
20
+
21
+ def initialize(strip, duration, opts = {})
22
+ @strip = strip
23
+ @duration = duration
24
+ @total_frames = (duration.to_f / (1000/Rustle::FRAME_RATE.to_f)).ceil
25
+ @frame_duration = 1.0/Rustle::FRAME_RATE
26
+
27
+ self.setup(opts)
28
+
29
+ render
30
+ end
31
+
32
+ # Gets called before the transition begins. Override it to set up any
33
+ # instance variables you need in the {#animate} method.
34
+ #
35
+ # @param [Hash] opts the options hash that is passed in when calling
36
+ # {Strip#serialize}
37
+ #
38
+ # Within this method, you also have the following available:
39
+ # * +@total_frames+: the total number of frames in the animation
40
+ # * +@duration+: the duration of the animation in milliseconds
41
+ # * +@frame_duration+: the duration of an individual frame (generally only
42
+ # used internally)
43
+ def setup(opts); end
44
+
45
+ # Gets called once-per-LED-per-frame. Override it, and do your calculations
46
+ # therein. You will have access to any instance variables you declare in
47
+ # {#setup}.
48
+ #
49
+ # @param [Fixnum] led_index the index of the current LED, starting at 0.
50
+ # @param [Color] starting_color the color that the current LED was
51
+ # displaying before the transition began
52
+ # @param [Fixnum] frame_num the current frame number in the transition
53
+ #
54
+ # @return [Color] a color object corresponding to what the new LED color
55
+ # should be.
56
+ #
57
+ # Within this method, you also have the following available:
58
+ # * +@total_frames+: the total number of frames in the animation
59
+ # * +@duration+: the duration of the animation in milliseconds
60
+ # * +@frame_duration+: the duration of an individual frame (generally only
61
+ # used internally)
62
+ def animate(led_index, starting_color, frame_num); end
63
+
64
+ private
65
+
66
+ def render
67
+ starting_frame = @strip.current_frame
68
+
69
+ @frames = Array.new(@total_frames) do |frame_num|
70
+ Frame.new(
71
+ Array.new(@strip.num_leds) do |led_index|
72
+ # Gets called once per LED per frame
73
+ animate(led_index, starting_frame.leds[led_index], frame_num)
74
+ end
75
+ )
76
+ end
77
+
78
+ @strip.queue_frames @frames
79
+
80
+ @total_frames.times do
81
+ @strip.next_frame!
82
+ sleep @frame_duration
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,14 @@
1
+ class FadeToTransition < Rustle::Transition
2
+ def name
3
+ :fade_to
4
+ end
5
+
6
+ def setup(opts)
7
+ @new_color = opts[:color]
8
+ end
9
+
10
+ # Return a color object
11
+ def animate(led_index, start_color, frame_num)
12
+ start_color.transition_to @new_color, (frame_num.to_f+1) / @total_frames.to_f
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ class WipeToTransition < Rustle::Transition
2
+ def name
3
+ :wipe_to
4
+ end
5
+
6
+ def setup(opts)
7
+ @adj_factor = @total_frames.to_f/@strip.num_leds.to_f
8
+ @new_color = opts[:color]
9
+ end
10
+
11
+ def animate(led_index, start_color, frame_num)
12
+ amount = 1.0 /
13
+ (@total_frames-(led_index)*@adj_factor) *
14
+ ((frame_num+1) - (led_index)*@adj_factor)
15
+
16
+ amount = [amount, 0.0].max
17
+
18
+ start_color.transition_to @new_color, amount
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Rustle
2
+ VERSION = "0.1.1"
3
+ end
data/lib/rustle.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "rustle/version"
2
+
3
+ require "rustle/receiver"
4
+ require "rustle/strip"
5
+ require "rustle/color"
6
+ require "rustle/frame"
7
+ require "rustle/transition"
8
+ require "rustle/transitions/wipe_to_transition"
9
+ require "rustle/transitions/fade_to_transition"
10
+ require "rustle/exceptions"
11
+
12
+ module Rustle
13
+ FRAME_RATE = 60
14
+ end
data/rustle.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rustle/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rustle"
8
+ spec.version = Rustle::VERSION
9
+ spec.authors = ["Andrew Hamon", "Steven Petryk"]
10
+ spec.email = ["and.ham95@gmail.com", "petryk.steven@gmail.com"]
11
+ spec.description = %q{Rustle is a gem that allows you to control an individually-addressable RGB LED strip via Ruby (with an Arduino acting as a middleman for PWM controls).}
12
+ spec.summary = %q{Control an individually-addressable RGB LED strip in Ruby.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib", "lib/transitions"]
20
+
21
+ spec.add_runtime_dependency 'active_support', '~> 3.0', '>= 3.0.0'
22
+ spec.add_runtime_dependency 'serialport', '~> 1.3', '>= 1.3.0'
23
+
24
+ spec.add_development_dependency 'bundler', "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency 'rspec', '~> 2.14', '>= 2.14.1'
27
+ end
data/rustle_logo.png ADDED
Binary file
@@ -0,0 +1,82 @@
1
+ /**
2
+ * A basic, one-way Rustle template sketch.
3
+ *
4
+ * LICENSE: This source file is subject to the MIT License, which is available
5
+ * through the World Wide Web at http://opensource.org/licenses/MIT, and is also
6
+ * included as a part of this repository.
7
+ *
8
+ * @author Andrew Hamon <and.ham95@gmail.com>
9
+ * @author Steven Petryk <petryk.steven@gmail.com>
10
+ * @copyright 2014 Andrew Hamon
11
+ * @license MIT License
12
+ */
13
+
14
+ #include <Adafruit_NeoPixel.h>
15
+
16
+ /*
17
+ * Ensure that these directives match your physical setup.
18
+ *
19
+ * Note: The baud rate may need to be decreased depending on your setup. The Uno
20
+ * seems to handle 921,600 just fine, and it's what Rustle defaults to.
21
+ */
22
+ #define STRIP_LENGTH 30
23
+ #define STRIP_PIN 6
24
+ #define BAUD 921600
25
+
26
+ // Initialize LED strip
27
+ Adafruit_NeoPixel myStrip = Adafruit_NeoPixel(STRIP_LENGTH, STRIP_PIN,
28
+ NEO_GRB + NEO_KHZ800);
29
+
30
+ // Current LED index within a frame
31
+ byte led_index;
32
+
33
+ // Each LED is an RGB array. `k` is just a counter (0,1,2 == R,G,B).
34
+ byte current_led[3];
35
+ byte k;
36
+
37
+ void setup() {
38
+ // Ensure this matches the baud in your receiver initialization.
39
+ Serial.begin(BAUD);
40
+
41
+ // Initialize counters
42
+ led_index = 0;
43
+ k = 0;
44
+
45
+ // Here we go
46
+ myStrip.begin();
47
+ myStrip.show();
48
+ }
49
+
50
+ void serialEvent() {
51
+ while(Serial.available()) {
52
+ byte x = Serial.read();
53
+
54
+ // 255 is the end-of-frame code, so we can assume that all other values
55
+ // are actual RGB values.
56
+ if (x == 255) {
57
+ myStrip.show();
58
+
59
+ k = led_index = 0;
60
+ }
61
+
62
+ else if (x < 255) {
63
+ // Data is sent [r,g,b,r,g,b,...] so we must accumulate an entire RGB
64
+ // triplet before writing to the LED strip.
65
+ current_led[k] = x;
66
+ k++;
67
+
68
+ // If we've accumulated a triplet...
69
+ if(k > 2) {
70
+ // ... we send the pixel.
71
+ myStrip.setPixelColor(led_index,
72
+ current_led[0], current_led[1], current_led[2]);
73
+
74
+ k = 0;
75
+ led_index++;
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ // Empty loop because sketch is entirely serial event driven
82
+ void loop() {};
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rustle::Color do
4
+
5
+ let(:color) { Rustle::Color.rgb(128,128,128) }
6
+
7
+ context "when initialized" do
8
+ let(:color_with_invalid_params) { Rustle::Color.new(-100,1000,0) }
9
+
10
+ it "corrects negative rgb values to zero" do
11
+ (color_with_invalid_params.r).should eq(0)
12
+ end
13
+
14
+ it "corrects very large rgb values to 255" do
15
+ (color_with_invalid_params.g).should eq(255)
16
+ end
17
+
18
+ it "makes no changes to rgb values between 0 and 255" do
19
+ color1 = Rustle::Color.new(0,0,0)
20
+ (color1.r).should eq(0)
21
+ (color1.g).should eq(0)
22
+ (color1.b).should eq(0)
23
+
24
+ color2 = Rustle::Color.new(255,255,255)
25
+ (color2.r).should eq(255)
26
+ (color2.g).should eq(255)
27
+ (color2.b).should eq(255)
28
+ end
29
+ end
30
+
31
+ describe "#to_a" do
32
+ it "returns an array" do
33
+ (color.to_a).should be_a(Array)
34
+ end
35
+
36
+ it "the array has the correct [r,g,b] values" do
37
+ (color.to_a).should eq([128,128,128])
38
+ end
39
+
40
+ it "rounds decimal values down to the nearest whole number" do
41
+ fractional_rgbs = Rustle::Color.rgb(0.5,10.7,99.9)
42
+ (fractional_rgbs.to_a).should eq([0, 10, 99])
43
+ end
44
+ end
45
+
46
+ describe "#to_s" do
47
+ it "returns a string with information about the color" do
48
+ (color.to_s).should eq("rgb(128, 128, 128)")
49
+ end
50
+
51
+ it "pads strings so that even small rgb values align properly" do
52
+ other_color = Rustle::Color.rgb(0,1,2)
53
+ (other_color.to_s).should eq("rgb( 0, 1, 2)")
54
+ end
55
+ end
56
+
57
+ describe "#eql?" do
58
+ let(:colorA) { Rustle::Color.rgb(128,128,128) }
59
+ let(:another_colorA) { Rustle::Color.rgb(128,128,128) }
60
+ let(:colorB) { Rustle::Color.rgb(0, 0, 0) }
61
+
62
+ it "returns true if the reciever and parameter objects hold equivalent rgb values" do
63
+ (colorA.eql?(another_colorA)).should be_true
64
+ end
65
+
66
+ it "returns false if the reciever and parameter objects differ in rgb values" do
67
+ (colorA.eql?(colorB)).should be_false
68
+ end
69
+ end
70
+
71
+ describe "Rustle::Color.hex factory method" do
72
+ let(:badass_rgb) { Rustle::Color.rgb(186, 218, 85) }
73
+
74
+ context "understands hex color codes WITHOUT a leading hash" do
75
+ let (:badass) { Rustle::Color.hex "BADA55" }
76
+
77
+ it "creates a color object" do
78
+ (badass).should be_a(Rustle::Color)
79
+ end
80
+
81
+ it "has the proper rgb values" do
82
+ (badass).should eql(badass_rgb)
83
+ end
84
+ end
85
+
86
+ context "understands hex color codes WITH a leading hash" do
87
+ let(:badass) { Rustle::Color.hex "#BADA55" }
88
+
89
+ it "creates a color object" do
90
+ (badass).should be_a(Rustle::Color)
91
+ end
92
+
93
+ it "has the right rgb values" do
94
+ (badass).should eql(badass_rgb)
95
+ end
96
+ end
97
+
98
+ context "understands hex color codes in three character format" do
99
+ let(:bad_hex) { Rustle::Color.hex "#BAD" }
100
+ let(:bad_rgb) { Rustle::Color.rgb 187, 170, 221 }
101
+
102
+ it "creates a color object" do
103
+ (bad_hex).should be_a(Rustle::Color)
104
+ end
105
+
106
+ it "should have the correct rgb values" do
107
+ (bad_hex).should eql(bad_rgb)
108
+ end
109
+ end
110
+
111
+ it "is case insensitive" do
112
+ first_badass = Rustle::Color.hex "#BADa55"
113
+ second_badass = Rustle::Color.hex "#badA55"
114
+ (first_badass).should eql(second_badass)
115
+ end
116
+
117
+ it "raises an error when given an invalid color code string" do
118
+ expect { Rustle::Color.hex "#ABCDEFABCDEF" }.to raise_error(Rustle::InvalidHexColorCode)
119
+ end
120
+ end
121
+
122
+ describe "Rustle::Color.hsb factory method" do
123
+ let(:rgb) { Rustle::Color.rgb 187, 170, 221 }
124
+ let(:hsb) { Rustle::Color.hsb 260, 0.23, 0.866 }
125
+ # Color: #BBAADD
126
+ # RGB: (187, 170, 221)
127
+ # HSV: 260 (260), saturation: 23 (23.0769), value: 87 (86.6667)
128
+
129
+ it "produces a color object" do
130
+ hsb.should be_a(Rustle::Color)
131
+ end
132
+
133
+ it "has the correct rgb properties" do
134
+ # Use be_within to give some allowance for rounting errors
135
+ (hsb.r).should be_within(3).of(rgb.r)
136
+ (hsb.g).should be_within(3).of(rgb.g)
137
+ (hsb.b).should be_within(3).of(rgb.b)
138
+ end
139
+ end
140
+
141
+ describe "#to_hsb" do
142
+ let(:hsb) { Rustle::Color.hsb 180, 0.5, 0.8 }
143
+
144
+ it "returns an array of [h,s,v] coordinates" do
145
+ color.to_hsb.should be_a(Array)
146
+ end
147
+
148
+ it "should have the (approximate) [h,s,v] coordinates" do
149
+ hsb_arr = hsb.to_hsb
150
+ hsb_arr[0].should be_within(1).of(180)
151
+ hsb_arr[1].should be_within(0.1).of(0.5)
152
+ hsb_arr[2].should be_within(0.1).of(0.8)
153
+ end
154
+
155
+ end
156
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rustle::Receiver do
4
+ it "does nothing"
5
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rustle'
5
+
6
+ RSpec.configure do |config|
7
+
8
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rustle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Hamon
8
+ - Steven Petryk
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: active_support
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '3.0'
21
+ - - '>='
22
+ - !ruby/object:Gem::Version
23
+ version: 3.0.0
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: '3.0'
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ - !ruby/object:Gem::Dependency
35
+ name: serialport
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - - '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.3.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ~>
49
+ - !ruby/object:Gem::Version
50
+ version: '1.3'
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.3.0
54
+ - !ruby/object:Gem::Dependency
55
+ name: bundler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: '1.3'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: '1.3'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: '2.14'
89
+ - - '>='
90
+ - !ruby/object:Gem::Version
91
+ version: 2.14.1
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ~>
97
+ - !ruby/object:Gem::Version
98
+ version: '2.14'
99
+ - - '>='
100
+ - !ruby/object:Gem::Version
101
+ version: 2.14.1
102
+ description: Rustle is a gem that allows you to control an individually-addressable
103
+ RGB LED strip via Ruby (with an Arduino acting as a middleman for PWM controls).
104
+ email:
105
+ - and.ham95@gmail.com
106
+ - petryk.steven@gmail.com
107
+ executables: []
108
+ extensions: []
109
+ extra_rdoc_files: []
110
+ files:
111
+ - .gitignore
112
+ - .rspec
113
+ - CHANGELOG.md
114
+ - Gemfile
115
+ - LICENSE
116
+ - README.md
117
+ - Rakefile
118
+ - lib/rustle.rb
119
+ - lib/rustle/color.rb
120
+ - lib/rustle/exceptions.rb
121
+ - lib/rustle/frame.rb
122
+ - lib/rustle/receiver.rb
123
+ - lib/rustle/strip.rb
124
+ - lib/rustle/transition.rb
125
+ - lib/rustle/transitions/fade_to_transition.rb
126
+ - lib/rustle/transitions/wipe_to_transition.rb
127
+ - lib/rustle/version.rb
128
+ - rustle.gemspec
129
+ - rustle_logo.png
130
+ - sketches/basic.ino
131
+ - spec/color_spec.rb
132
+ - spec/receiver_spec.rb
133
+ - spec/spec_helper.rb
134
+ homepage: ''
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ - lib/transitions
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.2.2
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Control an individually-addressable RGB LED strip in Ruby.
159
+ test_files:
160
+ - spec/color_spec.rb
161
+ - spec/receiver_spec.rb
162
+ - spec/spec_helper.rb
163
+ has_rdoc: