blinkytape-orb 0.0.1

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.
@@ -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