blinkytape-orb 0.0.1 → 0.0.2

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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- M2U5ZTcxOWE3OGMxYTllMWY1YTMzNTY4MGZjMzJkYzRmNDdiNjk3Yg==
4
+ NmMxYTllNTkyNTllOTk2ZTgwMWQxYTgzZGEwODFkNGI0NjRhMGFiYw==
5
5
  data.tar.gz: !binary |-
6
- MTA2NDAwNjY4YTZjNWM4MjZlNDBkMTcxNjU3NzkxOGEyYzliNDY3Mg==
6
+ MTg3NDhlZGFiZGM4NzBlNzgyN2MyOTU0ODYwOTlkMjAwNmFjMTlkMA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- ZWUzZTk1YzEwYWMxMzJiMGJiZTU0NjdjMjFiMjRjOTdmOGU2Zjk3NDkwM2U2
10
- MTExY2U4ODdlZDFhYjE4NTNlYTcwMTQ5NWMxOTRiZjY0OWVlMDFhMGRiNjNj
11
- NjYzYTY4YTk4ZmQ4ZDc1OTA2N2IxMjZlOTlmMTRiYzg0ZTU4Y2E=
9
+ ZTUyNDAwYjJiNjI1OWNjYjllYThlMTNmMWVhMGY5NDQwOGJkYTU1MzEyMzg3
10
+ YTFkNGY1OTAyYmU4MmEyYjc1ODcxY2U1Yzc4NjFkZWFhYmE0MzAwZGMxYjUz
11
+ MDdlOTAxNGE1Y2JiMDc0MjE4NGIzYzdiZjEzODZjNzlhOTQ3NWU=
12
12
  data.tar.gz: !binary |-
13
- ZjdjYTNmZGZmYmI3MGE3N2ZmYWUwMDViNWUxMjAzMzlmZmIwYWIyNzFkNDky
14
- ZjM1NDM0N2I2ODllNDhlMmM3YTdhNWExNTMwMzgxZjY3NDlhYWMyYzAxMjA4
15
- YTdjZTRlNzUzZjljM2MxZjAzNmZhMjQwNjEzOGJkZTlmOTUzMWQ=
13
+ ZDM2NTlhMTg4YjQ3YWUxODhiMmFkZDU4MTQ4ZWI1NTdkM2M5OGM5MDI2NGJk
14
+ ZjE2ZjYzMmUxMmUyMjZjMDhmNTNiNzE5MjQ0NzUzOTI3MGViZmUxNjY5NTYy
15
+ NTIzYzI2MjljOWMzOTM2ZGMwY2NiNWM1NGQ4YjIxMDFkNmRkMjM=
@@ -0,0 +1,48 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Jon Tai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
24
+
25
+ Hardware initialization and interrupt handling arduino code was copied from
26
+ https://github.com/Blinkinlabs/BlinkyTape_Arduino, which has the following
27
+ license:
28
+
29
+ The MIT License (MIT)
30
+
31
+ Copyright (c) 2013 Blinkinlabs, LLC
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
34
+ this software and associated documentation files (the "Software"), to deal in
35
+ the Software without restriction, including without limitation the rights to
36
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
37
+ the Software, and to permit persons to whom the Software is furnished to do so,
38
+ subject to the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be included in all
41
+ copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
45
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
46
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
47
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
48
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,12 +1,53 @@
1
1
  BlinkyTape Orb
2
2
  ==============
3
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/)
4
+ The code in this repository makes a [BlinkyTape controller board](http://blinkinlabs.myshopify.com/collections/frontpage/products/blinkytape-control-board)
5
+ act like an ambientdevices Ambient Orb and allows controlling it from Ruby over
6
+ USB. Controlling the Orb from other languages should be simple—it only requires
7
+ sending a few ASCII characters over the serial interface.
8
+
9
+ Ambient Orbs are great for displaying background information such as stocks,
10
+ the weather, or whether or not the `master` branch of your repository is
11
+ passing tests.
12
+
13
+ ## Custom Firmware for BlinkyTape controller board
14
+
15
+ A BlinkyTape has multiple individually-addressable LEDs connected to it. It can
16
+ recieve commands over the serial interface. The default firmware expects 3
17
+ bytes per pixel (values for red, green, and blue), followed by a `255` byte to
18
+ display everything. This protocol is required for compatibility with BlinkyTape
19
+ software such as [PatternPaint](http://blinkinlabs.com/blinkytape/patternpaint/).
20
+
21
+ On the other hand, the Ambient Orb accepts a color and animation, encoded as
22
+ ASCII. After recieving an update, the Orb continues to animate (pulse) on its
23
+ own. Like on the BlinkyTape, the button is used to cycle through several
24
+ brightness levels.
25
+
26
+ The `arduino` directory in this repository contains custom firmware for the
27
+ BlinkyTape that makes it act more like an Ambient Orb. There are some
28
+ differences:
29
+
30
+ * Only 6 colors and 4 pulse speeds are accepted (vs. 37 and 10,
31
+ respectively)
32
+ * As a result, the serial protocol is simplified to 1 ASCII character per
33
+ update
34
+ * Brightness can be controlled via the serial protocol as well as via the
35
+ hardware button (it's unclear if the Orb allowed this as well)
36
+ * Animations were recreated from memory without comparing them side by side
37
+ with the Orb, so timing and transitions may vary.
38
+
39
+ See the [Blinkinlabs arduino instructions](http://blinkinlabs.com/blinkytape/arduino/)
7
40
  for how to install the firmware. The firmware can be made to work on an Arduino
8
41
  Uno R3 with some modifications, mainly around the button handling.
9
42
 
43
+ ## Ruby Gem
44
+
45
+ The `blinkytape-orb` gem is a thin wrapper around the serial protocol
46
+ (described below). It also does automatic device detection if you only have one
47
+ Orb connected.
48
+
49
+ Here's how you might use it:
50
+
10
51
  ```ruby
11
52
  require 'blinkytape-orb'
12
53
 
@@ -43,3 +84,79 @@ sleep(3)
43
84
  sleep(10)
44
85
  end
45
86
  ```
87
+
88
+ # Serial Protocol
89
+
90
+ The custom firmware accepts a single ASCII character per update. Regular
91
+ updates encode both a color and pulse speed. Some characters are set aside to
92
+ allow controlling brightness.
93
+
94
+ ## Regular Updates (Color and Pulse Speed)
95
+
96
+ The two lowest bits encode the pulse speed:
97
+
98
+ * `00` - No pulsing
99
+ * `01` - Slow pulsing
100
+ * `10` - Medium pulsing
101
+ * `11` - Fast pulsing
102
+
103
+ The next three bits encode the color:
104
+
105
+ * `000` - Red
106
+ * `001` - Orange
107
+ * `010` - Yellow
108
+ * `011` - Green
109
+ * `100` - Blue
110
+ * `101` - Purple
111
+
112
+ To make the protocol easier to type by hand in a serial monitor, commands are
113
+ "shifted up" by 65 (ASCII "A"). For example, green and medium pulsing would be
114
+ encoded as `01110` (14), but this is sent as 79 (65+4), which is "O".
115
+
116
+ This encoding can be done easily in Ruby:
117
+
118
+ ```ruby
119
+ ('A'.ord + (color * 4) + pulse).chr
120
+ ```
121
+
122
+ In other words, "A" is red with no pulsing, followed by "B" for slow pulsing,
123
+ "C" for medium pulsing, and "D" for fast pulsing. "E" is orange with no
124
+ pulsing, "F" is orange with slow pulsing, etc.
125
+
126
+ ## Brightness Updates
127
+
128
+ There are three settings encoded in two bits:
129
+
130
+ * `00` - Dim
131
+ * `01` - Medium
132
+ * `10` - Bright
133
+
134
+ These are "shifted up" by 97 (ASCII "a"). So "a" is dim, "b" is medium, and "c"
135
+ is bright. The Ruby one-liner for encoding is:
136
+
137
+ ```ruby
138
+ ('a'.ord + brightness).chr
139
+ ```
140
+
141
+ # Hardware Setup
142
+
143
+ I stuck the [BlinkyTape controller board](http://blinkinlabs.myshopify.com/collections/frontpage/products/blinkytape-control-board)
144
+ on the plastic base of the Orb with a small styrofoam riser (poor man's 3D
145
+ printer) and some double-sided tape. The [adafruit NeoPixel 12-LED ring](https://www.adafruit.com/product/1643)
146
+ easily fits on the plastic base and in the hole in the bottom of the glass part
147
+ of the orb. The ring rests on slightly higher styrofoam risers, above the
148
+ controller board. I got the JST connector version of the controller board,
149
+ snipped off the connector, and soldered the wires directly to the NeoPixel
150
+ ring. The glass orb simply rests on the plastic base since there isn't anything
151
+ for the base to screw into anymore. (The wires kind of help keep the base
152
+ attached to the glass.) I considered getting a NeoPixel 7-LED jewel to sit in
153
+ the center of the ring, but the 12 LEDs are plenty bright even in daylight.
154
+
155
+ ![Hardware, Side View](https://raw.githubusercontent.com/jtai/blinkytape-orb/master/hardware/photos/hardware-side.jpg)
156
+ ![Hardware, Top View](https://raw.githubusercontent.com/jtai/blinkytape-orb/master/hardware/photos/hardware-top.jpg)
157
+
158
+ adafruit makes NeoPixel LEDs in a variety of form factors. I built another
159
+ "Orb" using the [8-LED strip](https://www.adafruit.com/products/1426) that has
160
+ no enclosure—it is just attached to the underside of my monitor and lights up
161
+ the monitor stand. The setup is relatively cheap, easy to assemble, and small
162
+ enough to install just about anywhere.
@@ -13,8 +13,8 @@
13
13
  // visuals
14
14
  #define PULSE_MAX_VAL 255
15
15
  #define PULSE_MIN_VAL 160
16
- #define PULSE_DURATION_SLOW 3000
17
- #define PULSE_DURATION_MED 1500
16
+ #define PULSE_DURATION_SLOW 6000
17
+ #define PULSE_DURATION_MED 3000
18
18
  #define PULSE_DURATION_FAST 0
19
19
 
20
20
  #define CHANGE_MIN_VAL 64
@@ -27,6 +27,8 @@
27
27
  // state
28
28
  CRGB leds[NUM_LEDS];
29
29
 
30
+ byte prev_command;
31
+ byte command;
30
32
  bool initialized;
31
33
 
32
34
  uint8_t hue;
@@ -38,10 +40,46 @@ bool buttonDebounced;
38
40
  uint8_t brightness;
39
41
 
40
42
 
43
+ void checkSerial() {
44
+ if (Serial.available() > 0) {
45
+ command = Serial.read();
46
+ }
47
+
48
+ // handle brightness changes immediately
49
+ // if command was a brightness change or an invalid command,
50
+ // swallow it to prevent rushing through the next fade
51
+ switch (command) {
52
+ case 97:
53
+ brightness = BRIGHTNESS_MIN;
54
+ FastLED.setBrightness(brightness);
55
+ command = prev_command;
56
+ break;
57
+ case 98:
58
+ brightness = BRIGHTNESS_MED;
59
+ FastLED.setBrightness(brightness);
60
+ command = prev_command;
61
+ break;
62
+ case 99:
63
+ brightness = BRIGHTNESS_MAX;
64
+ FastLED.setBrightness(brightness);
65
+ command = prev_command;
66
+ break;
67
+ default:
68
+ if (command - 65 >= 24) { // swallow garbage values
69
+ command = prev_command;
70
+ }
71
+ break;
72
+ }
73
+ }
74
+
41
75
  void fade(uint8_t new_val, long duration) {
42
76
  if (val == new_val) {
77
+ checkSerial();
78
+
43
79
  // setBrightness() requires a call to delay() periodically
80
+ // without this line, we can't change the brightness when we're not pulsing
44
81
  FastLED.delay(duration);
82
+
45
83
  return;
46
84
  }
47
85
 
@@ -76,8 +114,10 @@ void fade(uint8_t new_val, long duration) {
76
114
  }
77
115
  FastLED.show();
78
116
 
79
- // check to see if we've been given a new command, if so, hurry things along
80
- if (Serial.available() > 0) {
117
+ // check to see if we've been given a new hue or pulse command
118
+ // if so, hurry things along
119
+ checkSerial();
120
+ if (command != prev_command) {
81
121
  FastLED.delay(change_delay_ms);
82
122
  } else {
83
123
  FastLED.delay(delay_ms);
@@ -93,6 +133,8 @@ void setup() {
93
133
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
94
134
  FastLED.setCorrection(LED_CORRECTION);
95
135
 
136
+ prev_command = 122; // initialize to invalid command so any valid command will trigger action
137
+ command = 122;
96
138
  initialized = false;
97
139
 
98
140
  val = PULSE_MAX_VAL;
@@ -123,72 +165,55 @@ void loop() {
123
165
  fade(PULSE_MIN_VAL, pulse_duration / 2);
124
166
  }
125
167
 
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);
168
+ if (command != prev_command) {
169
+ fade(CHANGE_MIN_VAL, CHANGE_DURATION / 2);
170
+
171
+ prev_command = command;
172
+ initialized = true;
173
+
174
+ byte c = command - 65; // align to ASCII "A"
175
+
176
+ switch ((c & B00011100) >> 2) {
177
+ case 0:
178
+ hue = HUE_RED;
133
179
  break;
134
- case 33:
135
- brightness = BRIGHTNESS_MED;
136
- FastLED.setBrightness(brightness);
180
+ case 1:
181
+ hue = HUE_ORANGE;
137
182
  break;
138
- case 34:
139
- brightness = BRIGHTNESS_MAX;
140
- FastLED.setBrightness(brightness);
183
+ case 2:
184
+ hue = HUE_YELLOW;
185
+ break;
186
+ case 3:
187
+ hue = HUE_GREEN;
188
+ break;
189
+ case 4:
190
+ hue = HUE_BLUE;
191
+ break;
192
+ case 5:
193
+ hue = HUE_PURPLE;
141
194
  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
- }
195
+ }
196
+
197
+ switch (c & B00000011) {
198
+ case 0:
199
+ pulse = false;
200
+ pulse_duration = CHANGE_DURATION;
201
+ break;
202
+ case 1:
203
+ pulse = true;
204
+ pulse_duration = PULSE_DURATION_SLOW;
205
+ break;
206
+ case 2:
207
+ pulse = true;
208
+ pulse_duration = PULSE_DURATION_MED;
209
+ break;
210
+ case 3:
211
+ pulse = true;
212
+ pulse_duration = PULSE_DURATION_FAST;
190
213
  break;
191
214
  }
215
+
216
+ fade(PULSE_MIN_VAL, CHANGE_DURATION / 2);
192
217
  }
193
218
 
194
219
  fade(PULSE_MAX_VAL, pulse_duration / 2);
@@ -57,7 +57,7 @@ class BlinkyTapeOrb
57
57
 
58
58
  logger.info("updating with color=#{color}, pulse=#{pulse}")
59
59
 
60
- send((65 + (color * 4) + pulse).chr)
60
+ send(('A'.ord + (color * 4) + pulse).chr)
61
61
  end
62
62
 
63
63
  def setBrightness(brightness)
@@ -67,7 +67,7 @@ class BlinkyTapeOrb
67
67
 
68
68
  logger.info("setting brightness=#{brightness}")
69
69
 
70
- send((65 + 32 + brightness).chr)
70
+ send(('a'.ord + brightness).chr)
71
71
  end
72
72
 
73
73
  private
@@ -1,3 +1,3 @@
1
1
  class BlinkyTapeOrb
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blinkytape-orb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Tai
@@ -33,12 +33,14 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - .gitignore
35
35
  - Gemfile
36
+ - LICENSE.md
36
37
  - README.md
37
38
  - arduino/blinkytape_orb.ino
38
39
  - blinkytape-orb.gemspec
40
+ - hardware/photos/hardware-side.jpg
41
+ - hardware/photos/hardware-top.jpg
39
42
  - lib/blinkytape-orb.rb
40
43
  - lib/blinkytape-orb/version.rb
41
- - spec/lib/.blinkytape-orb_spec.rb.swp
42
44
  - spec/lib/blinkytape-orb_spec.rb
43
45
  - spec/spec_helper.rb
44
46
  homepage: https://github.com/jtai/blinkytape-orb
@@ -65,6 +67,5 @@ signing_key:
65
67
  specification_version: 4
66
68
  summary: Ruby code to interface with BlinkyTape controller board running custom firmware
67
69
  test_files:
68
- - spec/lib/.blinkytape-orb_spec.rb.swp
69
70
  - spec/lib/blinkytape-orb_spec.rb
70
71
  - spec/spec_helper.rb