blinkytape-orb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ M2U5ZTcxOWE3OGMxYTllMWY1YTMzNTY4MGZjMzJkYzRmNDdiNjk3Yg==
5
+ data.tar.gz: !binary |-
6
+ MTA2NDAwNjY4YTZjNWM4MjZlNDBkMTcxNjU3NzkxOGEyYzliNDY3Mg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZWUzZTk1YzEwYWMxMzJiMGJiZTU0NjdjMjFiMjRjOTdmOGU2Zjk3NDkwM2U2
10
+ MTExY2U4ODdlZDFhYjE4NTNlYTcwMTQ5NWMxOTRiZjY0OWVlMDFhMGRiNjNj
11
+ NjYzYTY4YTk4ZmQ4ZDc1OTA2N2IxMjZlOTlmMTRiYzg0ZTU4Y2E=
12
+ data.tar.gz: !binary |-
13
+ ZjdjYTNmZGZmYmI3MGE3N2ZmYWUwMDViNWUxMjAzMzlmZmIwYWIyNzFkNDky
14
+ ZjM1NDM0N2I2ODllNDhlMmM3YTdhNWExNTMwMzgxZjY3NDlhYWMyYzAxMjA4
15
+ YTdjZTRlNzUzZjljM2MxZjAzNmZhMjQwNjEzOGJkZTlmOTUzMWQ=
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ .rvmrc
4
+ .DS_Store
5
+ /coverage
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :test do
5
+ gem 'rspec', '~> 3.4.0'
6
+ gem 'rspec-instafail', '~> 0.4'
7
+ gem 'simplecov', :require => false
8
+ end
@@ -0,0 +1,45 @@
1
+ BlinkyTape Orb
2
+ ==============
3
+
4
+ Ruby code to interface with a [BlinkyTape controller board](http://blinkinlabs.myshopify.com/collections/frontpage/products/blinkytape-control-board)
5
+ running the custom firmware in the `arduino` directory of this repository. See
6
+ the [Blinkinlabs arduino instructions](http://blinkinlabs.com/blinkytape/arduino/)
7
+ for how to install the firmware. The firmware can be made to work on an Arduino
8
+ Uno R3 with some modifications, mainly around the button handling.
9
+
10
+ ```ruby
11
+ require 'blinkytape-orb'
12
+
13
+ # device is automatically detected on OS X
14
+ orb = BlinkyTapeOrb.new
15
+
16
+ # a device can be passed manually
17
+ #orb = BlinkyTapeOrb.new(:device => '/dev/ttyS0')
18
+
19
+ # a logger can also be passed
20
+ #orb = BlinkyTapeOrb.new(:logger => Logger.new(STDOUT))
21
+ #orb.logger.level = Logger::DEBUG
22
+
23
+ [
24
+ BlinkyTapeOrb::BRIGHTNESS_MIN,
25
+ BlinkyTapeOrb::BRIGHTNESS_MED,
26
+ BlinkyTapeOrb::BRIGHTNESS_MAX,
27
+ ].each do |brightness|
28
+ orb.setBrightness(brightness)
29
+ sleep(1)
30
+ end
31
+
32
+ sleep(3)
33
+
34
+ [
35
+ [ BlinkyTapeOrb::COLOR_GREEN, BlinkyTapeOrb::PULSE_NONE ],
36
+ [ BlinkyTapeOrb::COLOR_BLUE, BlinkyTapeOrb::PULSE_SLOW ],
37
+ [ BlinkyTapeOrb::COLOR_PURPLE, BlinkyTapeOrb::PULSE_SLOW ],
38
+ [ BlinkyTapeOrb::COLOR_YELLOW, BlinkyTapeOrb::PULSE_MED ],
39
+ [ BlinkyTapeOrb::COLOR_ORANGE, BlinkyTapeOrb::PULSE_MED ],
40
+ [ BlinkyTapeOrb::COLOR_RED, BlinkyTapeOrb::PULSE_FAST ],
41
+ ].each do |(color, animation)|
42
+ orb.update(color, animation)
43
+ sleep(10)
44
+ end
45
+ ```
@@ -0,0 +1,230 @@
1
+ #include "FastLED.h"
2
+
3
+ // hardware
4
+ #define NUM_LEDS 12
5
+ #define LED_CORRECTION TypicalSMD5050
6
+
7
+ #define DATA_PIN 13
8
+ #define BUTTON_IN 10
9
+ #define ANALOG_INPUT A9
10
+ #define EXTRA_PIN_A 7
11
+ #define EXTRA_PIN_B 11
12
+
13
+ // visuals
14
+ #define PULSE_MAX_VAL 255
15
+ #define PULSE_MIN_VAL 160
16
+ #define PULSE_DURATION_SLOW 3000
17
+ #define PULSE_DURATION_MED 1500
18
+ #define PULSE_DURATION_FAST 0
19
+
20
+ #define CHANGE_MIN_VAL 64
21
+ #define CHANGE_DURATION 800
22
+
23
+ #define BRIGHTNESS_MAX 255
24
+ #define BRIGHTNESS_MED 106
25
+ #define BRIGHTNESS_MIN 32
26
+
27
+ // state
28
+ CRGB leds[NUM_LEDS];
29
+
30
+ bool initialized;
31
+
32
+ uint8_t hue;
33
+ uint8_t val;
34
+ bool pulse;
35
+ long pulse_duration;
36
+
37
+ bool buttonDebounced;
38
+ uint8_t brightness;
39
+
40
+
41
+ void fade(uint8_t new_val, long duration) {
42
+ if (val == new_val) {
43
+ // setBrightness() requires a call to delay() periodically
44
+ FastLED.delay(duration);
45
+ return;
46
+ }
47
+
48
+ int incr;
49
+ long delay_ms;
50
+ long change_delay_ms;
51
+ if (val < new_val) {
52
+ incr = 1;
53
+ delay_ms = duration / (new_val - val);
54
+ if (CHANGE_DURATION / 2 < duration) {
55
+ change_delay_ms = (CHANGE_DURATION / 2) / (new_val - val);
56
+ } else {
57
+ change_delay_ms = delay_ms;
58
+ }
59
+ } else if (val > new_val) {
60
+ incr = -1;
61
+ delay_ms = duration / (val - new_val);
62
+ if (CHANGE_DURATION / 2 < duration) {
63
+ change_delay_ms = (CHANGE_DURATION / 2) / (val - new_val);
64
+ } else {
65
+ change_delay_ms = delay_ms;
66
+ }
67
+ }
68
+
69
+ for (uint8_t v = val; v != new_val; v += incr) {
70
+ for (int i = 0; i < NUM_LEDS; i++) {
71
+ if (initialized) {
72
+ leds[i] = CHSV(hue, 255, v);
73
+ } else {
74
+ leds[i] = CRGB(v, v, v);
75
+ }
76
+ }
77
+ FastLED.show();
78
+
79
+ // check to see if we've been given a new command, if so, hurry things along
80
+ if (Serial.available() > 0) {
81
+ FastLED.delay(change_delay_ms);
82
+ } else {
83
+ FastLED.delay(delay_ms);
84
+ }
85
+ }
86
+
87
+ val = new_val;
88
+ }
89
+
90
+ void setup() {
91
+ Serial.begin(57600);
92
+
93
+ FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
94
+ FastLED.setCorrection(LED_CORRECTION);
95
+
96
+ initialized = false;
97
+
98
+ val = PULSE_MAX_VAL;
99
+ pulse = true;
100
+ pulse_duration = PULSE_DURATION_MED;
101
+
102
+ brightness = BRIGHTNESS_MED;
103
+ FastLED.setBrightness(brightness);
104
+
105
+ for (int i = 0; i < NUM_LEDS; i++) {
106
+ leds[i] = CRGB(val, val, val);
107
+ }
108
+ FastLED.show();
109
+
110
+ pinMode(BUTTON_IN, INPUT_PULLUP);
111
+ pinMode(ANALOG_INPUT, INPUT_PULLUP);
112
+ pinMode(EXTRA_PIN_A, INPUT_PULLUP);
113
+ pinMode(EXTRA_PIN_B, INPUT_PULLUP);
114
+
115
+ // Interrupt set-up; see Atmega32u4 datasheet section 11
116
+ PCIFR |= (1 << PCIF0); // Just in case, clear interrupt flag
117
+ PCMSK0 |= (1 << PCINT6); // Set interrupt mask to the button pin (PCINT6)
118
+ PCICR |= (1 << PCIE0); // Enable interrupt
119
+ }
120
+
121
+ void loop() {
122
+ if (pulse) {
123
+ fade(PULSE_MIN_VAL, pulse_duration / 2);
124
+ }
125
+
126
+ if (Serial.available() > 0) {
127
+ byte c = Serial.read();
128
+ c -= 65; // align to ASCII "A"
129
+ switch (c) {
130
+ case 32:
131
+ brightness = BRIGHTNESS_MIN;
132
+ FastLED.setBrightness(brightness);
133
+ break;
134
+ case 33:
135
+ brightness = BRIGHTNESS_MED;
136
+ FastLED.setBrightness(brightness);
137
+ break;
138
+ case 34:
139
+ brightness = BRIGHTNESS_MAX;
140
+ FastLED.setBrightness(brightness);
141
+ break;
142
+ default:
143
+ if (c < 24) { // filter out garbage values
144
+ fade(CHANGE_MIN_VAL, CHANGE_DURATION / 2);
145
+
146
+ switch (c & B00000011) {
147
+ case 0:
148
+ pulse = false;
149
+ pulse_duration = CHANGE_DURATION;
150
+ break;
151
+ case 1:
152
+ pulse = true;
153
+ pulse_duration = PULSE_DURATION_SLOW;
154
+ break;
155
+ case 2:
156
+ pulse = true;
157
+ pulse_duration = PULSE_DURATION_MED;
158
+ break;
159
+ case 3:
160
+ pulse = true;
161
+ pulse_duration = PULSE_DURATION_FAST;
162
+ break;
163
+ }
164
+
165
+ switch ((c & B00011100) >> 2) {
166
+ case 0:
167
+ hue = HUE_RED;
168
+ break;
169
+ case 1:
170
+ hue = HUE_ORANGE;
171
+ break;
172
+ case 2:
173
+ hue = HUE_YELLOW;
174
+ break;
175
+ case 3:
176
+ hue = HUE_GREEN;
177
+ break;
178
+ case 4:
179
+ hue = HUE_BLUE;
180
+ break;
181
+ case 5:
182
+ hue = HUE_PURPLE;
183
+ break;
184
+ }
185
+
186
+ initialized = true;
187
+
188
+ fade(PULSE_MIN_VAL, CHANGE_DURATION / 2);
189
+ }
190
+ break;
191
+ }
192
+ }
193
+
194
+ fade(PULSE_MAX_VAL, pulse_duration / 2);
195
+ }
196
+
197
+ // Called when the button is both pressed and released
198
+ ISR(PCINT0_vect){
199
+ if (!(PINB & (1 << PINB6))) {
200
+ buttonDebounced = false;
201
+
202
+ // Configure and start timer4 interrupt.
203
+ TCCR4B = 0x0F; // Slowest prescaler
204
+ TCCR4D = _BV(WGM41) | _BV(WGM40); // Fast PWM mode
205
+ OCR4C = 0x10; // some random percentage of the clock
206
+ TCNT4 = 0; // Reset the counter
207
+ TIMSK4 = _BV(TOV4); // turn on the interrupt
208
+ } else {
209
+ TIMSK4 = 0; // turn off the interrupt
210
+ }
211
+ }
212
+
213
+ // Called every xx ms while the button is being held down
214
+ ISR(TIMER4_OVF_vect) {
215
+ if (buttonDebounced == false) {
216
+ buttonDebounced = true;
217
+ switch (brightness) {
218
+ case BRIGHTNESS_MIN:
219
+ brightness = BRIGHTNESS_MED;
220
+ break;
221
+ case BRIGHTNESS_MED:
222
+ brightness = BRIGHTNESS_MAX;
223
+ break;
224
+ case BRIGHTNESS_MAX:
225
+ brightness = BRIGHTNESS_MIN;
226
+ break;
227
+ }
228
+ FastLED.setBrightness(brightness);
229
+ }
230
+ }
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require 'blinkytape-orb/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "blinkytape-orb"
8
+ s.version = BlinkyTapeOrb::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Jon Tai"]
11
+ s.email = ["jon.tai@gmail.com"]
12
+ s.homepage = "https://github.com/jtai/blinkytape-orb"
13
+ s.summary = "Ruby code to interface with BlinkyTape controller board running custom firmware"
14
+ s.description = "Other hardware (e.g., arduino) may be supported with minor firmware modifications"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- spec/*`.split("\n")
18
+ s.require_path = 'lib'
19
+
20
+ s.add_runtime_dependency 'serialport', '>= 1.3.1'
21
+ end
@@ -0,0 +1,92 @@
1
+ require 'blinkytape-orb/version'
2
+ require 'logger'
3
+ require 'serialport'
4
+
5
+ class BlinkyTapeOrb
6
+
7
+ SERIAL_PORT_BAUD_RATE = 57600
8
+
9
+ COLOR_RED = 0
10
+ COLOR_ORANGE = 1
11
+ COLOR_YELLOW = 2
12
+ COLOR_GREEN = 3
13
+ COLOR_BLUE = 4
14
+ COLOR_PURPLE = 5
15
+
16
+ PULSE_NONE = 0
17
+ PULSE_SLOW = 1
18
+ PULSE_MED = 2
19
+ PULSE_FAST = 3
20
+
21
+ BRIGHTNESS_MIN = 0
22
+ BRIGHTNESS_MED = 1
23
+ BRIGHTNESS_MAX = 2
24
+
25
+ def initialize(options = {})
26
+ @opts = options
27
+ end
28
+
29
+ def device
30
+ @device ||= begin
31
+ dev = @opts[:device] || autodetect_device
32
+
33
+ unless dev && File.exist?(dev) && File.stat(dev).chardev?
34
+ raise ArgumentError, 'invalid device'
35
+ end
36
+
37
+ logger.info("selected device #{dev}")
38
+
39
+ dev
40
+ end
41
+ end
42
+
43
+ def logger
44
+ @logger ||= begin
45
+ @opts[:logger] || Logger.new(nil)
46
+ end
47
+ end
48
+
49
+ def update(color, pulse)
50
+ if (color < 0 || color > 5)
51
+ raise ArgumentError, 'color must be between 0 and 5'
52
+ end
53
+
54
+ if (pulse < 0 || pulse > 3)
55
+ raise ArgumentError, 'pulse must be between 0 and 3'
56
+ end
57
+
58
+ logger.info("updating with color=#{color}, pulse=#{pulse}")
59
+
60
+ send((65 + (color * 4) + pulse).chr)
61
+ end
62
+
63
+ def setBrightness(brightness)
64
+ if (brightness < 0 || brightness > 2)
65
+ raise ArgumentError, 'brightness must be between 0 and 2'
66
+ end
67
+
68
+ logger.info("setting brightness=#{brightness}")
69
+
70
+ send((65 + 32 + brightness).chr)
71
+ end
72
+
73
+ private
74
+
75
+ def autodetect_device
76
+ devices = Dir.glob('/dev/*.usbmodem*') + Dir.glob('/dev/*.usbserial*')
77
+
78
+ devices.each do |dev|
79
+ logger.debug("autodetect_device found #{dev}")
80
+ end
81
+
82
+ devices.first
83
+ end
84
+
85
+ def send(command)
86
+ SerialPort.open(device, SERIAL_PORT_BAUD_RATE) do |serial_port|
87
+ logger.debug("send \"#{command}\"")
88
+ serial_port.write(command)
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,3 @@
1
+ class BlinkyTapeOrb
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+
3
+ describe BlinkyTapeOrb do
4
+
5
+ let (:options) { {} }
6
+ let (:orb) { BlinkyTapeOrb.new(options) }
7
+
8
+ describe '#device' do
9
+
10
+ context 'with valid device option' do
11
+ let (:options) { { :device => '/dev/zero' } }
12
+
13
+ it 'returns device' do
14
+ expect(orb.device).to eq('/dev/zero')
15
+ end
16
+ end
17
+
18
+ context 'with non-existent device' do
19
+ let (:options) { { :device => '/dev/nosuchdevice' } }
20
+
21
+ it 'raises ArgumentError' do
22
+ expect {
23
+ orb.device
24
+ }.to raise_error(ArgumentError)
25
+ end
26
+ end
27
+
28
+ context 'with block device' do
29
+ let (:options) { { :device => `df #{__FILE__} | tail -1 | cut -d ' ' -f 1` } }
30
+
31
+ it 'raises ArgumentError' do
32
+ expect {
33
+ orb.device
34
+ }.to raise_error(ArgumentError)
35
+ end
36
+ end
37
+
38
+ context 'with autodetected device' do
39
+ it 'returns first device matching pattern' do
40
+ expect(Dir).to receive(:glob).twice.and_return(['/dev/urandom', '/dev/random'])
41
+ expect(orb.device).to eq('/dev/urandom')
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ describe '#update' do
48
+
49
+ let (:color) { BlinkyTapeOrb::COLOR_RED }
50
+ let (:pulse) { BlinkyTapeOrb::PULSE_NONE }
51
+
52
+ context 'with out of range color' do
53
+ let (:color) { 6 }
54
+
55
+ it 'raises ArgumentError' do
56
+ expect {
57
+ orb.update(color, pulse)
58
+ }.to raise_error(ArgumentError)
59
+ end
60
+ end
61
+
62
+ context 'with out of range pulse' do
63
+ let (:pulse) { 4 }
64
+
65
+ it 'raises ArgumentError' do
66
+ expect {
67
+ orb.update(color, pulse)
68
+ }.to raise_error(ArgumentError)
69
+ end
70
+ end
71
+
72
+ context 'with valid arguments' do
73
+ it 'sends correct update' do
74
+ expect(orb).to receive(:send).with('A')
75
+ orb.update(color, pulse)
76
+ end
77
+ end
78
+
79
+ context 'with purple color' do
80
+ let (:color) { BlinkyTapeOrb::COLOR_PURPLE }
81
+
82
+ it 'sends correct update' do
83
+ expect(orb).to receive(:send).with('U')
84
+ orb.update(color, pulse)
85
+ end
86
+ end
87
+
88
+ context 'with fast pulse' do
89
+ let (:pulse) { BlinkyTapeOrb::PULSE_FAST }
90
+
91
+ it 'sends correct update' do
92
+ expect(orb).to receive(:send).with('D')
93
+ orb.update(color, pulse)
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+ describe '#setBrightness' do
100
+
101
+ let (:brightness) { BlinkyTapeOrb::BRIGHTNESS_MAX }
102
+
103
+ context 'with out of range brightness' do
104
+ let (:brightness) { 3 }
105
+
106
+ it 'raises ArgumentError' do
107
+ expect {
108
+ orb.setBrightness(brightness)
109
+ }.to raise_error(ArgumentError)
110
+ end
111
+ end
112
+
113
+ context 'with valid arguments' do
114
+ it 'sends correct update' do
115
+ expect(orb).to receive(:send).with('c')
116
+ orb.setBrightness(brightness)
117
+ end
118
+ end
119
+
120
+ context 'with min brightness' do
121
+ let (:brightness) { BlinkyTapeOrb::BRIGHTNESS_MIN }
122
+
123
+ it 'sends correct update' do
124
+ expect(orb).to receive(:send).with('a')
125
+ orb.setBrightness(brightness)
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ describe '#send' do
132
+
133
+ let (:options) { { :device => '/dev/zero' } }
134
+ let (:io) { IO.new }
135
+
136
+ it 'sends commands' do
137
+ io = double('serial_port')
138
+ expect(io).to receive(:write).with('A')
139
+ expect(SerialPort).to receive(:open).and_yield(io)
140
+ orb.update(BlinkyTapeOrb::COLOR_RED, BlinkyTapeOrb::PULSE_NONE)
141
+ end
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,13 @@
1
+ require 'simplecov'
2
+
3
+ SimpleCov.start do
4
+ add_filter 'spec'
5
+ end
6
+
7
+ SimpleCov.use_merging false
8
+
9
+ require 'blinkytape-orb'
10
+
11
+ RSpec.configure do |config|
12
+ config.color = true
13
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blinkytape-orb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jon Tai
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: serialport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.3.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.3.1
27
+ description: Other hardware (e.g., arduino) may be supported with minor firmware modifications
28
+ email:
29
+ - jon.tai@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - README.md
37
+ - arduino/blinkytape_orb.ino
38
+ - blinkytape-orb.gemspec
39
+ - lib/blinkytape-orb.rb
40
+ - lib/blinkytape-orb/version.rb
41
+ - spec/lib/.blinkytape-orb_spec.rb.swp
42
+ - spec/lib/blinkytape-orb_spec.rb
43
+ - spec/spec_helper.rb
44
+ homepage: https://github.com/jtai/blinkytape-orb
45
+ licenses: []
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.0.0
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Ruby code to interface with BlinkyTape controller board running custom firmware
67
+ test_files:
68
+ - spec/lib/.blinkytape-orb_spec.rb.swp
69
+ - spec/lib/blinkytape-orb_spec.rb
70
+ - spec/spec_helper.rb