ws2812 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/LICENSE.txt +339 -0
- data/README.md +41 -0
- data/Rakefile +39 -0
- data/examples/basic.rb +45 -0
- data/examples/binaryclock.rb +161 -0
- data/examples/digiclock.rb +167 -0
- data/examples/gamma-vs-direct.rb +31 -0
- data/examples/unicornhat-test.rb +47 -0
- data/ext/ws2812/COMPILING.txt +4 -0
- data/ext/ws2812/SOURCES.txt +2 -0
- data/ext/ws2812/board_info.c +143 -0
- data/ext/ws2812/board_info.h +6 -0
- data/ext/ws2812/clk.h +60 -0
- data/ext/ws2812/dma.c +79 -0
- data/ext/ws2812/dma.h +126 -0
- data/ext/ws2812/extconf.rb +24 -0
- data/ext/ws2812/gamma.h +20 -0
- data/ext/ws2812/gpio.h +108 -0
- data/ext/ws2812/lowlevel.i +48 -0
- data/ext/ws2812/lowlevel_wrap.c +3189 -0
- data/ext/ws2812/mailbox.c +311 -0
- data/ext/ws2812/mailbox.h +53 -0
- data/ext/ws2812/pwm.c +112 -0
- data/ext/ws2812/pwm.h +123 -0
- data/ext/ws2812/ws2811.c +685 -0
- data/ext/ws2812/ws2811.h +69 -0
- data/lib/ws2812.rb +20 -0
- data/lib/ws2812/basic.rb +195 -0
- data/lib/ws2812/color.rb +33 -0
- data/lib/ws2812/gamma_correction.rb +72 -0
- data/lib/ws2812/unicornhat.rb +209 -0
- metadata +114 -0
data/ext/ws2812/ws2811.h
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
/*
|
2
|
+
* ws2811.h
|
3
|
+
*
|
4
|
+
* Copyright (c) 2014 Jeremy Garff <jer @ jers.net>
|
5
|
+
*
|
6
|
+
* All rights reserved.
|
7
|
+
*
|
8
|
+
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
9
|
+
* provided that the following conditions are met:
|
10
|
+
*
|
11
|
+
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
12
|
+
* conditions and the following disclaimer.
|
13
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
14
|
+
* of conditions and the following disclaimer in the documentation and/or other materials
|
15
|
+
* provided with the distribution.
|
16
|
+
* 3. Neither the name of the owner nor the names of its contributors may be used to endorse
|
17
|
+
* or promote products derived from this software without specific prior written permission.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
20
|
+
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
21
|
+
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
22
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
24
|
+
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
26
|
+
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
*
|
28
|
+
*/
|
29
|
+
|
30
|
+
|
31
|
+
#ifndef __WS2811_H__
|
32
|
+
#define __WS2811_H__
|
33
|
+
|
34
|
+
#include "pwm.h"
|
35
|
+
|
36
|
+
|
37
|
+
#define WS2811_TARGET_FREQ 800000 // Can go as low as 400000
|
38
|
+
|
39
|
+
struct ws2811_device;
|
40
|
+
|
41
|
+
typedef uint32_t ws2811_led_t; //< 0x00RRGGBB
|
42
|
+
typedef struct
|
43
|
+
{
|
44
|
+
int gpionum; //< GPIO Pin with PWM alternate function, 0 if unused
|
45
|
+
int invert; //< Invert output signal
|
46
|
+
int count; //< Number of LEDs, 0 if channel is unused
|
47
|
+
int brightness; //< Brightness value between 0 and 255
|
48
|
+
ws2811_led_t *leds; //< LED buffers, allocated by driver based on count
|
49
|
+
} ws2811_channel_t;
|
50
|
+
|
51
|
+
typedef struct
|
52
|
+
{
|
53
|
+
struct ws2811_device *device; //< Private data for driver use
|
54
|
+
uint32_t freq; //< Required output frequency
|
55
|
+
int dmanum; //< DMA number _not_ already in use
|
56
|
+
ws2811_channel_t channel[RPI_PWM_CHANNELS];
|
57
|
+
} ws2811_t;
|
58
|
+
|
59
|
+
|
60
|
+
int ws2811_init(ws2811_t *ws2811); //< Initialize buffers/hardware
|
61
|
+
void ws2811_fini(ws2811_t *ws2811); //< Tear it all down
|
62
|
+
extern uint8_t ws2811_direct_colors; //< Set to non-zero to bypass "brightness" [gamma correction] altogether (default is 0)
|
63
|
+
int ws2811_render(ws2811_t *ws2811); //< Send LEDs off to hardware
|
64
|
+
extern uint32_t ws2811_dma_error; //< DMA errors from ws2811_wait go here
|
65
|
+
int ws2811_wait(ws2811_t *ws2811); //< Wait for DMA completion
|
66
|
+
|
67
|
+
|
68
|
+
#endif /* __WS2811_H__ */
|
69
|
+
|
data/lib/ws2812.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Ws2812
|
2
|
+
VERSION = "0.0.2"
|
3
|
+
end
|
4
|
+
|
5
|
+
# to make it all less confusing
|
6
|
+
WS2812 = Ws2812
|
7
|
+
|
8
|
+
if $__WS2812_SKIP_LL
|
9
|
+
module Ws2812
|
10
|
+
module Lowlevel
|
11
|
+
# XXX: could provide full blown emulation of all calls? :)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
else
|
15
|
+
require 'ws2812/lowlevel'
|
16
|
+
end
|
17
|
+
require 'ws2812/color'
|
18
|
+
require 'ws2812/basic'
|
19
|
+
require 'ws2812/unicornhat'
|
20
|
+
require 'ws2812/gamma_correction'
|
data/lib/ws2812/basic.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
module Ws2812
|
2
|
+
##
|
3
|
+
# Provides basic interface for so called NeoPixels (ws2812 RGB LED
|
4
|
+
# chips)
|
5
|
+
#
|
6
|
+
# This library internally uses C (and SWIG) extension from Richard Hirst's
|
7
|
+
# version of Jeremy Garff's rpi_ws281x library.
|
8
|
+
#
|
9
|
+
# And this particular class is heavily inspired by the included
|
10
|
+
# <tt>neopixel.py</tt> Python class within these projects.
|
11
|
+
#
|
12
|
+
# See:
|
13
|
+
# [jgarff] https://github.com/jgarff/rpi_ws281x
|
14
|
+
# [richardghirst] https://github.com/richardghirst/rpi_ws281x
|
15
|
+
# [pimoroni] https://github.com/pimoroni/unicorn-hat/tree/master/python/rpi-ws281x
|
16
|
+
#
|
17
|
+
#
|
18
|
+
class Basic
|
19
|
+
include Ws2812::Lowlevel
|
20
|
+
|
21
|
+
##
|
22
|
+
# Initializes the basic ws2812 driver for +num+ leds at given +pin+,
|
23
|
+
# with initial +brightness+ (0..255)
|
24
|
+
#
|
25
|
+
# The +options+ hash can contain various additional options
|
26
|
+
# (all keys as symbols):
|
27
|
+
#
|
28
|
+
# [freq] frequency (Hz) to communicate at, defaults to 800_000
|
29
|
+
# [dma] dma channel to use, defaults to 5
|
30
|
+
# [invert] use inverted logic, defaults to false
|
31
|
+
# [channel] which channel to use, defaults to 0 (permissible 0, 1)
|
32
|
+
def initialize(num, pin, brightness = 50, options = {})
|
33
|
+
|
34
|
+
freq = options.fetch(:freq) { 800_000 }
|
35
|
+
dma = options.fetch(:dma) { 5 }
|
36
|
+
invert = options.fetch(:invert) { false }
|
37
|
+
channel = options.fetch(:channel) { 0 }
|
38
|
+
|
39
|
+
@leds = Ws2811_t.new
|
40
|
+
@leds.freq = freq
|
41
|
+
@leds.dmanum = dma
|
42
|
+
|
43
|
+
@channel = ws2811_channel_get(@leds, channel)
|
44
|
+
@channel.count = num
|
45
|
+
@channel.gpionum = pin
|
46
|
+
@channel.invert = invert ? 1 : 0
|
47
|
+
@channel.brightness = brightness
|
48
|
+
|
49
|
+
@count = num
|
50
|
+
|
51
|
+
at_exit { self.close }
|
52
|
+
|
53
|
+
@open = false
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Is gamma correction (brightness) bypassed?
|
58
|
+
def direct?
|
59
|
+
!Ws2812::Lowlevel.ws2811_direct_colors.zero?
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Instruct lowlevel driver to bypass gamma correction (brightness)
|
64
|
+
# and use the color values straight as they are given
|
65
|
+
def direct=(value)
|
66
|
+
Ws2812::Lowlevel.ws2811_direct_colors = !!value ? 1 : 0
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Actually opens (initializes) communication with the LED strand
|
71
|
+
#
|
72
|
+
# Raises an exception when the initialization fails.
|
73
|
+
#
|
74
|
+
# Failure is usually because you don't have root permissions
|
75
|
+
# which are needed to access /dev/mem and to create special
|
76
|
+
# devices.
|
77
|
+
def open
|
78
|
+
return nil if @open
|
79
|
+
resp = ws2811_init(@leds)
|
80
|
+
fail "init failed with code: " + resp.to_s + ", perhaps you need to run as root?" unless resp.zero?
|
81
|
+
@open = true
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Closes (deinitializes) communication with the LED strand
|
87
|
+
#
|
88
|
+
# Can be called on already closed device just fine
|
89
|
+
def close
|
90
|
+
if @open && @leds
|
91
|
+
@open = false
|
92
|
+
ws2811_fini(@leds)
|
93
|
+
@channel = nil # will GC the memory
|
94
|
+
@leds = nil
|
95
|
+
# Note: ws2811_fini will free mem used by led_data internally
|
96
|
+
end
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
##
|
102
|
+
# Apply all changes since last show
|
103
|
+
#
|
104
|
+
# This method renders all changes (brightness, pixels) done to the
|
105
|
+
# strand since last show
|
106
|
+
def show
|
107
|
+
resp = ws2811_render(@leds)
|
108
|
+
fail "show failed with code: " + resp.to_s unless resp.zero?
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Set given pixel identified by +index+ to +color+
|
113
|
+
#
|
114
|
+
# See +set+ for a method that takes individual +r+, +g+, +b+
|
115
|
+
# components
|
116
|
+
def []=(index, color)
|
117
|
+
if index.respond_to?(:to_a)
|
118
|
+
index.to_a.each do |i|
|
119
|
+
check_index(i)
|
120
|
+
ws2811_led_set(@channel, i, color.to_i)
|
121
|
+
end
|
122
|
+
else
|
123
|
+
check_index(index)
|
124
|
+
ws2811_led_set(@channel, index, color.to_i)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Set given pixel identified by +index+ to +r+, +g+, +b+
|
130
|
+
#
|
131
|
+
# See <tt>[]=</tt> for a method that takes +Color+ instance instead
|
132
|
+
# of individual components
|
133
|
+
def set(index, r, g, b)
|
134
|
+
check_index(index)
|
135
|
+
self[index] = Color.new(r, g, b)
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Set brightness used for all pixels
|
140
|
+
#
|
141
|
+
# The value is from +0+ to +255+ and is internally used as a scaler
|
142
|
+
# for all colors values that are supplied via <tt>[]=</tt>
|
143
|
+
def brightness=(val)
|
144
|
+
@channel.brightness = val
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Return brightness used for all pixels
|
149
|
+
#
|
150
|
+
# The value is from +0+ to +255+ and is internally used as a scaler
|
151
|
+
# for all colors values that are supplied via <tt>[]=</tt>
|
152
|
+
def brightness
|
153
|
+
@channel.brightness
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Number of leds it's initialized for
|
158
|
+
#
|
159
|
+
# Method actually passes to low-level implementation for this
|
160
|
+
# value; it doesn't use the parameter passed during construction
|
161
|
+
def count
|
162
|
+
@channel.count
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# Return +Color+ of led located at given index
|
167
|
+
#
|
168
|
+
# Indexed from 0 upto <tt>#count - 1</tt>
|
169
|
+
def [](index)
|
170
|
+
if index.respond_to?(:to_a)
|
171
|
+
index.to_a.map do |i|
|
172
|
+
check_index(i)
|
173
|
+
Color.from_i(ws2811_led_get(@channel, i))
|
174
|
+
end
|
175
|
+
else
|
176
|
+
check_index(index)
|
177
|
+
Color.from_i(ws2811_led_get(@channel, index))
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Verify supplied index
|
183
|
+
#
|
184
|
+
# Raises ArgumentError if the supplied index is invalid
|
185
|
+
# (doesn't address configured pixel)
|
186
|
+
def check_index(index)
|
187
|
+
if 0 <= index && index < @count
|
188
|
+
true
|
189
|
+
else
|
190
|
+
fail ArgumentError, "index #{index} outside of permitted range [0..#{count})"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
private :check_index
|
194
|
+
end
|
195
|
+
end
|
data/lib/ws2812/color.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Ws2812
|
2
|
+
##
|
3
|
+
# Simple wrapper class around RGB based color
|
4
|
+
class Color
|
5
|
+
def initialize(r, g, b)
|
6
|
+
@r, @g, @b = r, g, b
|
7
|
+
end
|
8
|
+
attr_accessor :r, :g, :b
|
9
|
+
|
10
|
+
##
|
11
|
+
# Converts color to integer by encoding +r+, +g+, +b+
|
12
|
+
# as 8bit values (in this order)
|
13
|
+
#
|
14
|
+
# Thus <tt>Color.new(1,2,3).to_i # => 66051</tt>
|
15
|
+
def to_i
|
16
|
+
((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Converts color from integer +i+ by taking the least significant
|
21
|
+
# 24 bits and using them for r(8), g(8), b(8); in this order.
|
22
|
+
def self.from_i(i)
|
23
|
+
Color.new((i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Makes sense to represent color as hex, right?
|
28
|
+
def to_hex
|
29
|
+
"#%02x%02x%02x" % [r & 0xff, g & 0xff, b & 0xff]
|
30
|
+
end
|
31
|
+
alias_method :to_s, :to_hex
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Ws2812
|
2
|
+
##
|
3
|
+
# This class implements (on Ruby level) the gamma correction that is
|
4
|
+
# internally used as part of the +brightness+ setup.
|
5
|
+
#
|
6
|
+
# It's especially useful if you want to mess around with pixel brightness
|
7
|
+
# in <em>direct mode</em>. See the <em>digiclock</em> example for more info.
|
8
|
+
class GammaCorrection
|
9
|
+
# correction table thanks to http://rgb-123.com/ws2812-color-output/
|
10
|
+
GAMMA_TABLE = [
|
11
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
12
|
+
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
|
13
|
+
2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
|
14
|
+
6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11,
|
15
|
+
11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18,
|
16
|
+
19, 19, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28,
|
17
|
+
29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40,
|
18
|
+
40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54,
|
19
|
+
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
|
20
|
+
71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 88, 89,
|
21
|
+
90, 91, 93, 94, 95, 96, 98, 99,100,102,103,104,106,107,109,110,
|
22
|
+
111,113,114,116,117,119,120,121,123,124,126,128,129,131,132,134,
|
23
|
+
135,137,138,140,142,143,145,146,148,150,151,153,155,157,158,160,
|
24
|
+
162,163,165,167,169,170,172,174,176,178,179,181,183,185,187,189,
|
25
|
+
191,193,194,196,198,200,202,204,206,208,210,212,214,216,218,220,
|
26
|
+
222,224,227,229,231,233,235,237,239,241,244,246,248,250,252,255
|
27
|
+
]
|
28
|
+
|
29
|
+
def initialize(brightness)
|
30
|
+
self.brightness = brightness
|
31
|
+
end
|
32
|
+
|
33
|
+
def brightness=(value)
|
34
|
+
raise ArgumentError, "brightness should be within (0..255)" unless (0..255).include?(value.to_i)
|
35
|
+
@brightness = value.to_i
|
36
|
+
end
|
37
|
+
attr_reader :brightness
|
38
|
+
|
39
|
+
def correct(value)
|
40
|
+
if value.kind_of?(Color)
|
41
|
+
Color.new(
|
42
|
+
self.correct(value.r),
|
43
|
+
self.correct(value.g),
|
44
|
+
self.correct(value.b))
|
45
|
+
else
|
46
|
+
raise ArgumentError, "value should be within (0..255)" unless (0..255).include?(value.to_i)
|
47
|
+
(GAMMA_TABLE[value.to_i] * (@brightness + 1)) >> 8
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def correct_with_min_max(value, min = nil, max = nil)
|
52
|
+
trim(correct(value), min, max)
|
53
|
+
end
|
54
|
+
|
55
|
+
def trim(value, min = nil, max = nil)
|
56
|
+
if value.kind_of?(Color)
|
57
|
+
Color.new(
|
58
|
+
self.trim(value.r, min && min.r, max && max.r),
|
59
|
+
self.trim(value.g, min && min.g, max && max.g),
|
60
|
+
self.trim(value.b, min && min.b, max && max.b))
|
61
|
+
else
|
62
|
+
if min && value.to_i < min.to_i
|
63
|
+
min.to_i
|
64
|
+
elsif max && value.to_i > max.to_i
|
65
|
+
max.to_i
|
66
|
+
else
|
67
|
+
value.to_i
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module Ws2812
|
2
|
+
##
|
3
|
+
# Provides interface for *Unicorn HAT* (a 8x8 ws2812 matrix by Pimoroni)
|
4
|
+
#
|
5
|
+
# This particular class is heavily inspired by the <tt>unicornhat.py</tt>
|
6
|
+
# class from UnicornHat's repo.
|
7
|
+
#
|
8
|
+
# See:
|
9
|
+
# [unicornhat] https://github.com/pimoroni/unicorn-hat
|
10
|
+
#
|
11
|
+
class UnicornHAT
|
12
|
+
UHAT_MAP = [
|
13
|
+
[7 ,6 ,5 ,4 ,3 ,2 ,1 ,0 ],
|
14
|
+
[8 ,9 ,10,11,12,13,14,15],
|
15
|
+
[23,22,21,20,19,18,17,16],
|
16
|
+
[24,25,26,27,28,29,30,31],
|
17
|
+
[39,38,37,36,35,34,33,32],
|
18
|
+
[40,41,42,43,44,45,46,47],
|
19
|
+
[55,54,53,52,51,50,49,48],
|
20
|
+
[56,57,58,59,60,61,62,63]
|
21
|
+
]
|
22
|
+
##
|
23
|
+
# Initializes the matrix ws2812 driver at given +pin+,
|
24
|
+
# with initial +brightness+ (0..255)
|
25
|
+
#
|
26
|
+
# The +options+ hash can contain various additional options,
|
27
|
+
# see +Basic+ class' description of options for more details.
|
28
|
+
def initialize(pin = 18, brightness = 50, options = {})
|
29
|
+
@hat = Basic.new(64, pin, brightness, options)
|
30
|
+
@hat.open
|
31
|
+
|
32
|
+
@rotation = 0
|
33
|
+
@pixels = Array.new(8) { |x| Array.new(8) { |y| Color.new(0,0,0) } }
|
34
|
+
push_all_pixels
|
35
|
+
rescue Object
|
36
|
+
@hat = nil
|
37
|
+
raise
|
38
|
+
end
|
39
|
+
|
40
|
+
# Query direct mode. See <em>Ws2812::Basic</em> for explanation.
|
41
|
+
def direct?
|
42
|
+
@hat.direct?
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Set direct mode. See <em>Ws2812::Basic</em> for explanation.
|
47
|
+
def direct=(value)
|
48
|
+
@hat.direct = value
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Closes (deinitializes) communication with the matrix
|
53
|
+
#
|
54
|
+
# Can be called on already closed device just fine
|
55
|
+
def close
|
56
|
+
if @hat
|
57
|
+
@hat.close
|
58
|
+
@hat = nil
|
59
|
+
end
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Apply all changes since last show
|
65
|
+
#
|
66
|
+
# This method renders all changes (brightness, pixels, rotation)
|
67
|
+
# done to the strand since last show
|
68
|
+
def show
|
69
|
+
@hat.show
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Set given pixel identified by +x+, +y+ to +color+
|
74
|
+
#
|
75
|
+
# See +set+ for a method that takes individual +r+, +g+, +b+
|
76
|
+
# components.
|
77
|
+
#
|
78
|
+
# You still have to call +show+ to make the changes visible.
|
79
|
+
def []=(x, y, color)
|
80
|
+
check_coords(x, y)
|
81
|
+
@pixels[x][y] = color
|
82
|
+
@hat[map_coords(x, y)] = color
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Set given pixel identified by +x+, +y+ to +r+, +g+, +b+
|
87
|
+
#
|
88
|
+
# See <tt>[]=</tt> for a method that takes +Color+ instance instead
|
89
|
+
# of individual components.
|
90
|
+
#
|
91
|
+
# You still have to call +show+ to make the changes visible.
|
92
|
+
def set(x, y, r, g, b)
|
93
|
+
check_coords(x, y)
|
94
|
+
self[x, y] = Color.new(r, g, b)
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Set brightness used for all pixels
|
99
|
+
#
|
100
|
+
# The value is from +0+ to +255+ and is internally used as a scaler
|
101
|
+
# for all colors values that are supplied via <tt>[]=</tt>
|
102
|
+
#
|
103
|
+
# You still have to call +show+ to make the changes visible.
|
104
|
+
def brightness=(val)
|
105
|
+
@hat.brightness = val
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Return brightness used for all pixels
|
110
|
+
#
|
111
|
+
# The value is from +0+ to +255+ and is internally used as a scaler
|
112
|
+
# for all colors values that are supplied via <tt>[]=</tt>
|
113
|
+
def brightness
|
114
|
+
@hat.brightness
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Return +Color+ of led located at given +x+, +y+
|
119
|
+
def [](x, y)
|
120
|
+
@pixels[x][y]
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Returns current rotation as integer; one of: [0, 90, 180, 270]
|
125
|
+
def rotation
|
126
|
+
@rotation
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Set rotation of the Unicorn HAT to +val+
|
131
|
+
#
|
132
|
+
# Permissible values for rotation are [0, 90, 180, 270] (mod 360).
|
133
|
+
#
|
134
|
+
# You still have to call +show+ to make the changes visible.
|
135
|
+
def rotation=(val)
|
136
|
+
permissible = [0, 90, 180, 270]
|
137
|
+
fail ArgumentError, "invalid rotation, permissible: #{permissible.join(', ')}" unless permissible.include?(val % 360)
|
138
|
+
@rotation = val % 360
|
139
|
+
push_all_pixels
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Clears all pixels (sets them to black) and calls +show+ if +do_show+
|
144
|
+
def clear(do_show = true)
|
145
|
+
set_all(Color.new(0, 0, 0))
|
146
|
+
show if do_show
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Sets all pixels to +color+
|
151
|
+
#
|
152
|
+
# You still have to call +show+ to make the changes visible.
|
153
|
+
def set_all(color)
|
154
|
+
0.upto(7) do |x|
|
155
|
+
0.upto(7) do |y|
|
156
|
+
self[x, y] = color
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Pushes all pixels from buffer to the lower level (physical device)
|
163
|
+
#
|
164
|
+
# This is internally used when changing rotation but it can be useful
|
165
|
+
# when you set several pixels to the same Color instance and then
|
166
|
+
# manipulate those pixels' color all at once.
|
167
|
+
def push_all_pixels
|
168
|
+
0.upto(7) do |x|
|
169
|
+
0.upto(7) do |y|
|
170
|
+
@hat[map_coords(x, y)] = @pixels[x][y]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
##
|
177
|
+
# Maps +x+, +y+ coordinates to index on the physical matrix
|
178
|
+
# (takes rotation into account)
|
179
|
+
def map_coords(x, y)
|
180
|
+
check_coords(x, y)
|
181
|
+
y = 7 - y
|
182
|
+
case rotation
|
183
|
+
when 90
|
184
|
+
x, y = y, 7 - x
|
185
|
+
when 180
|
186
|
+
x, y = 7 - x, 7 - y
|
187
|
+
when 270
|
188
|
+
x, y = 7 - y, x
|
189
|
+
end
|
190
|
+
|
191
|
+
UHAT_MAP[x][y]
|
192
|
+
end
|
193
|
+
private :map_coords
|
194
|
+
|
195
|
+
##
|
196
|
+
# Verify supplied coords +x+ and +y+
|
197
|
+
#
|
198
|
+
# Raises ArgumentError if the supplied coords are invalid
|
199
|
+
# (doesn't address configured pixel)
|
200
|
+
def check_coords(x, y)
|
201
|
+
if 0 <= x && x < 8 && 0 <= y && y < 8
|
202
|
+
true
|
203
|
+
else
|
204
|
+
fail ArgumentError, "coord (#{x},#{y}) outside of permitted range ((0..7), (0..7))"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
private :check_coords
|
208
|
+
end
|
209
|
+
end
|