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