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.
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ws2812'
4
+
5
+ # Single 1x4 pixels binary "digit"
6
+ class BinaryDigit
7
+ def initialize(hat, x, y, color, off_color = nil)
8
+ off_color ||= Ws2812::Color.new(0, 0, 0)
9
+ @hat, @x, @y, @color, @off_color = hat, x, y, color, off_color
10
+ end
11
+ attr_reader :hat
12
+ attr_accessor :x, :y, :color, :off_color
13
+
14
+ def show(value, do_show = false, &pixel_set)
15
+ pixel_set ||= method(:default_pixel_set)
16
+ raise ArgumentError, "invalid value" unless (0..9).include?(value)
17
+ x = 0
18
+ 0.upto(3) do |y|
19
+ if value[3-y].zero?
20
+ pixel_set.call(@hat,@x + x, @y + y, @off_color)
21
+ else
22
+ pixel_set.call(@hat,@x + x, @y + y, @color)
23
+ end
24
+ end
25
+ @hat.show if do_show
26
+ end
27
+
28
+ def default_pixel_set(hat, x, y, color)
29
+ hat[x, y] = color
30
+ end
31
+ private :default_pixel_set
32
+ end
33
+
34
+ # Two BinaryDigits next to each other with no pixel gap
35
+ class TwoBinaryDigits
36
+ def initialize(hat, x, y, color, off_color = nil)
37
+ @tens = BinaryDigit.new(hat, x, y, color, off_color)
38
+ @singles = BinaryDigit.new(hat, x + 1, y, color, off_color)
39
+ @hat, @x, @y, @color = hat, x, y, color
40
+ end
41
+ attr_reader :hat, :x, :y, :color, :off_color
42
+
43
+ def show(value, do_show = false, &pixel_set)
44
+ tens_value = value / 10
45
+ @tens.show(tens_value, false, &pixel_set)
46
+ @singles.show(value % 10, false, &pixel_set)
47
+ @hat.show if do_show
48
+ end
49
+
50
+ def off_color
51
+ @tens.off_color
52
+ end
53
+
54
+ def off_color=(val)
55
+ @tens.off_color = @singles.off_color = val
56
+ end
57
+
58
+ def color=(val)
59
+ @tens.color = @singles.color = val
60
+ end
61
+
62
+ def x=(val)
63
+ @tens.x = val
64
+ @singles.x = val + 1
65
+ end
66
+
67
+ def y=(val)
68
+ @tens.y = @singles.y = val
69
+ end
70
+ end
71
+
72
+ # A 6x4 matrix that represents binary clock
73
+ class BinaryClock
74
+ def initialize(hat, x, y, color)
75
+ @h = TwoBinaryDigits.new(hat, x, y, color)
76
+ @m = TwoBinaryDigits.new(hat, x+2, y, color)
77
+ @s = TwoBinaryDigits.new(hat, x+4, y, color)
78
+ end
79
+
80
+ def show(time)
81
+ @h.show(time.hour, false)
82
+ @m.show(time.min, false)
83
+ @s.show(time.sec, false)
84
+ end
85
+ end
86
+
87
+ # A 8x4 matrix that represents binary calendar (ddmmyyyy)
88
+ class BinaryCalendar
89
+ def initialize(hat, x, y, color)
90
+ @d = TwoBinaryDigits.new(hat, x, y, color)
91
+ @m = TwoBinaryDigits.new(hat, x+2, y, color)
92
+ @y1 = TwoBinaryDigits.new(hat, x+4, y, color)
93
+ @y2 = TwoBinaryDigits.new(hat, x+6, y, color)
94
+ end
95
+
96
+ def show(time)
97
+ @d.show(time.day, false)
98
+ @m.show(time.month, false)
99
+ @y1.show(time.year/100, false)
100
+ @y2.show(time.year%100, false)
101
+ end
102
+ end
103
+
104
+ if __FILE__ == $0
105
+ # Init
106
+ hat = Ws2812::UnicornHAT.new
107
+ hat.rotation = 180
108
+ hat.brightness = 20
109
+
110
+ frame = Ws2812::Color.new(0x00, 0x00, 0x66)
111
+ red = Ws2812::Color.new(0xff, 0, 0)
112
+ green = Ws2812::Color.new(0, 0xff, 0)
113
+ if true
114
+ # Calendar + Clock
115
+ calendar = BinaryCalendar.new(hat, 0, 0, green)
116
+ clock = BinaryClock.new(hat, 1, 4, red)
117
+ show_calendar = true
118
+ frame_proc = proc do
119
+ 4.upto(7) do |y|
120
+ hat[0, y] = hat[7, y] = frame
121
+ end
122
+ end
123
+ else
124
+ # Just clock, with a frame around it
125
+ clock = BinaryClock.new(hat, 1, 2, red)
126
+ show_calendar = false
127
+ frame_proc = proc do
128
+ 0.upto(7) do |x|
129
+ #hat[x, 7] = hat[x, 0] = frame
130
+ hat[x, 6] = hat[x, 1] = frame
131
+ end
132
+ 2.upto(5) do |y|
133
+ hat[0, y] = hat[7, y] = frame
134
+ end
135
+ end
136
+ end
137
+ ot = nil
138
+
139
+ # Show it...
140
+ begin
141
+ loop do
142
+ t = Time.now
143
+
144
+ # set the display anew if hour/minute/sec changed
145
+ if ot.nil? || (ot.hour != t.hour || ot.min != t.min || ot.sec != t.sec)
146
+ ot = t
147
+ hat.clear(false)
148
+ clock.show(t)
149
+ calendar.show(t) if show_calendar
150
+ frame_proc.call
151
+ hat.show
152
+ end
153
+
154
+ sleep 0.1
155
+ end
156
+ rescue Interrupt
157
+ end
158
+
159
+ # cleanup
160
+ hat.clear
161
+ end
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ws2812'
4
+
5
+ # Single 3x5 pixels "digit" (think 7-segment display)
6
+ class Digit
7
+ n = nil # just to have the table below pretty
8
+ VALUES = {
9
+ n => [ 0b000, 0b000, 0b000, 0b000, 0b000, ],
10
+ 0 => [ 0b111, 0b101, 0b101, 0b101, 0b111, ],
11
+ #1 => [ 0b010, 0b110, 0b010, 0b010, 0b010, ], # 1 in the middle
12
+ 1 => [ 0b001, 0b011, 0b001, 0b001, 0b001, ], # 1 at the left
13
+ 2 => [ 0b111, 0b001, 0b111, 0b100, 0b111, ],
14
+ 3 => [ 0b111, 0b001, 0b111, 0b001, 0b111, ],
15
+ 4 => [ 0b101, 0b101, 0b111, 0b001, 0b001, ],
16
+ 5 => [ 0b111, 0b100, 0b111, 0b001, 0b111, ],
17
+ #6 => [ 0b111, 0b100, 0b111, 0b101, 0b111, ], # "full" six
18
+ 6 => [ 0b100, 0b100, 0b111, 0b101, 0b111, ], # "sparse" six
19
+ 7 => [ 0b111, 0b001, 0b001, 0b001, 0b001, ],
20
+ 8 => [ 0b111, 0b101, 0b111, 0b101, 0b111, ],
21
+ #9 => [ 0b111, 0b101, 0b111, 0b001, 0b111, ], # "full" nine
22
+ 9 => [ 0b111, 0b101, 0b111, 0b001, 0b001, ], # "sparse" nine
23
+ }
24
+
25
+ def initialize(hat, x, y, color, off_color = nil)
26
+ off_color ||= Ws2812::Color.new(0, 0, 0)
27
+ @hat, @x, @y, @color, @off_color = hat, x, y, color, off_color
28
+ end
29
+ attr_reader :hat
30
+ attr_accessor :x, :y, :color, :off_color
31
+
32
+ def show(value, do_show = false, &pixel_set)
33
+ pixel_set ||= method(:default_pixel_set)
34
+ raise ArgumentError, "invalid value" unless VALUES[value]
35
+ 0.upto(4) do |y|
36
+ 0.upto(2) do |x|
37
+ if VALUES[value][y][2-x].zero?
38
+ pixel_set.call(@hat,@x + x, @y + y, @off_color)
39
+ else
40
+ pixel_set.call(@hat,@x + x, @y + y, @color)
41
+ end
42
+ end
43
+ end
44
+ @hat.show if do_show
45
+ end
46
+
47
+ def default_pixel_set(hat, x, y, color)
48
+ hat[x, y] = color
49
+ end
50
+ private :default_pixel_set
51
+ end
52
+
53
+ # Two Digits next to each other with one pixel gap
54
+ # (think two 7-segment displays next to each other)
55
+ class TwoDigits
56
+ def initialize(hat, x, y, color, off_color = nil)
57
+ @tens = Digit.new(hat, x, y, color, off_color)
58
+ @singles = Digit.new(hat, x + 4, y, color, off_color)
59
+ @hat, @x, @y, @color = hat, x, y, color
60
+ @leading_zero = false
61
+ end
62
+ attr_reader :hat, :x, :y, :color, :off_color
63
+ attr_accessor :leading_zero
64
+
65
+ def show(value, do_show = false, &pixel_set)
66
+ tens_value = value / 10
67
+ tens_value = nil if !@leading_zero && tens_value.zero?
68
+ @tens.show(tens_value, false, &pixel_set)
69
+ @singles.show(value % 10, false, &pixel_set)
70
+ @hat.show if do_show
71
+ end
72
+
73
+ def off_color
74
+ @tens.off_color
75
+ end
76
+
77
+ def off_color=(val)
78
+ @tens.off_color = @singles.off_color = val
79
+ end
80
+
81
+ def color=(val)
82
+ @tens.color = @singles.color = val
83
+ end
84
+
85
+ def x=(val)
86
+ @tens.x = val
87
+ @singles.x = val + 4
88
+ end
89
+
90
+ def y=(val)
91
+ @tens.y = @singles.y = val
92
+ end
93
+ end
94
+
95
+ if __FILE__ == $0
96
+ # Init
97
+ hat = Ws2812::UnicornHAT.new
98
+ hat.rotation = 180
99
+
100
+ gc = Ws2812::GammaCorrection.new(20) # => "brightness" set to 20
101
+ hat.direct = true
102
+ # Note: I'm using direct mode with custom +gc+ here so I can better
103
+ # "animate" the pixels where hours and minutes overlap (both set)
104
+ #
105
+ # Without direct mode you're flying blind and changing the uncorrected
106
+ # values, while possible, looks fugly in lower brightness (i.e. 20)
107
+ black = gc.correct(Ws2812::Color.new(0, 0, 0))
108
+ red = gc.correct(Ws2812::Color.new(0xff, 0, 0))
109
+ green = gc.correct(Ws2812::Color.new(0, 0xaa, 0))
110
+ both = gc.correct(Ws2812::Color.new(0xff, 0xaa, 0))
111
+ h = TwoDigits.new(hat, 0, 0, red, black)
112
+ m = TwoDigits.new(hat, 1, 3, green, black)
113
+ m.leading_zero = true
114
+
115
+ # The following proc block allows us to merge individual red+green pixels
116
+ # into one aggregate "both" pixel (same Ws2812::Color instance). Thus
117
+ # allowing us to change one value (and then call +#push_all_pixels+) for
118
+ # update of the whole array.
119
+ green_ps = proc do |hat, x, y, color|
120
+ if hat[x, y] == red
121
+ hat[x, y] = both if color == green
122
+ # otherwise stays red
123
+ else
124
+ hat[x, y] = color
125
+ end
126
+ end
127
+
128
+ begin
129
+ ticks = 25
130
+ tick = 0
131
+ direction = 1
132
+ ot = nil
133
+ loop do
134
+ t = Time.now
135
+
136
+ # set the display anew if hour/minute changed
137
+ if ot.nil? || (ot.hour != t.hour || ot.min != t.min)
138
+ ot = t
139
+ hat.clear(false)
140
+ h.show(t.hour, false)
141
+ m.show(t.min, false, &green_ps)
142
+ end
143
+
144
+
145
+ # animate "both" pixels (where hour & minute overlay)
146
+ both.r = (red.r * tick.to_f/ticks).to_i
147
+ both.g = (green.g * ((ticks - tick).to_f/ticks)).to_i
148
+ both.r = both.g = 1 if both.r.zero? && both.g.zero?
149
+ hat.push_all_pixels
150
+
151
+ if direction > 0
152
+ direction = -1 if tick + 1 >= ticks
153
+ tick = tick + 1
154
+ else
155
+ direction = 1 if tick - 1 <= 0
156
+ tick = tick - 1
157
+ end
158
+
159
+ hat.show
160
+ sleep 0.1
161
+ end
162
+ rescue Interrupt
163
+ end
164
+
165
+ # cleanup
166
+ hat.clear
167
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ws2812'
4
+
5
+ # Init
6
+ n = 64 # num leds
7
+ ws = Ws2812::Basic.new(n, 18) # +n+ leds at pin 18, using defaults
8
+ ws.open
9
+ ws.brightness = 255
10
+ if ARGV.first
11
+ puts 'Using direct mode.'
12
+ puts 'Remove all positional parameters to switch to gamma-corrected mode.'
13
+ ws.direct = true
14
+ else
15
+ puts 'Using gamma-corrected mode.'
16
+ puts 'Add (any) positional parameter to switch to direct mode.'
17
+ end
18
+
19
+ # up...
20
+ 0.upto(255) do |i|
21
+ ws[0..63] = Ws2812::Color.new(i, i, i)
22
+ ws.show
23
+ sleep 0.01
24
+ end
25
+
26
+ # and down...
27
+ 255.downto(0) do |i|
28
+ ws[0..63] = Ws2812::Color.new(i, i, i)
29
+ ws.show
30
+ sleep 0.01
31
+ end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ws2812'
4
+
5
+ # Init
6
+ hat = Ws2812::UnicornHAT.new
7
+
8
+ # first corner set to red
9
+ red = Ws2812::Color.new(0xff, 0, 0)
10
+ hat[0, 0] = red
11
+ hat[0, 1] = red
12
+ hat[1, 0] = red
13
+
14
+ # second to green
15
+ green = Ws2812::Color.new(0, 0xff, 0)
16
+ hat[6, 7] = green
17
+ hat[7, 7] = green
18
+ hat[7, 6] = green
19
+
20
+ # middle part
21
+ hat[3, 3] = red
22
+ hat[4, 4] = green
23
+
24
+
25
+ # show it
26
+ hat.show
27
+
28
+ sleep 0.5
29
+
30
+ # rotate around till ^C
31
+ puts "Spinning... ^C to terminate"
32
+ begin
33
+ loop do
34
+ # rotate around
35
+ for rot in [90, 180, 270, 0]
36
+ hat.rotation = rot
37
+ hat.show
38
+
39
+ sleep 0.5
40
+ end
41
+ end
42
+ rescue Interrupt
43
+ end
44
+
45
+ # Clear the display at the end
46
+ hat.clear
47
+ hat.show
@@ -0,0 +1,4 @@
1
+ # make sure you have swig2.0 if you're planning to regen
2
+ # lowlevel_wrap.c from lowlevel.i
3
+ ruby extconf.rb
4
+ make
@@ -0,0 +1,2 @@
1
+ lowlevel.i is https://github.com/pimoroni/unicorn-hat/blob/master/python/rpi-ws281x/rpi_ws281x.i
2
+ *.[ch] are https://github.com/pimoroni/unicorn-hat/tree/master/python/rpi-ws281x/lib/*.[ch]
@@ -0,0 +1,143 @@
1
+ /*
2
+ * This needs cleaning up and expanding. The MODEL_* defines shoudl be in
3
+ * board_info.h, it currently identifies everything not a B2 as a B, it
4
+ * should have an accessor function for the board model and revision, etc.
5
+ */
6
+
7
+ #include <stdio.h>
8
+ #include <stdlib.h>
9
+ #include <stdarg.h>
10
+ #include <string.h>
11
+
12
+ #include "board_info.h"
13
+
14
+ enum
15
+ {
16
+ MODEL_UNKNOWN,
17
+ MODEL_A,
18
+ MODEL_A_PLUS,
19
+ MODEL_B,
20
+ MODEL_B_PLUS,
21
+ MODEL_B_2,
22
+ };
23
+
24
+ static int board_info_initialised = 0;
25
+ static int board_model = MODEL_UNKNOWN;
26
+ static int board_revision;
27
+
28
+ static void
29
+ fatal(char *fmt, ...)
30
+ {
31
+ va_list ap;
32
+
33
+ va_start(ap, fmt);
34
+ vfprintf(stderr, fmt, ap);
35
+ va_end(ap);
36
+ exit(1);
37
+ }
38
+
39
+ static unsigned get_dt_ranges(const char *filename, unsigned offset)
40
+ {
41
+ unsigned address = ~0;
42
+ FILE *fp = fopen(filename, "rb");
43
+ if (fp)
44
+ {
45
+ unsigned char buf[4];
46
+ fseek(fp, offset, SEEK_SET);
47
+ if (fread(buf, 1, sizeof buf, fp) == sizeof buf)
48
+ address = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0;
49
+ fclose(fp);
50
+ }
51
+ return address;
52
+ }
53
+
54
+ uint32_t board_info_peripheral_base_addr(void)
55
+ {
56
+ unsigned address = get_dt_ranges("/proc/device-tree/soc/ranges", 4);
57
+
58
+ board_info_init();
59
+
60
+ if (address == ~0)
61
+ {
62
+ if (board_model == MODEL_B_2)
63
+ return 0x3f000000;
64
+ else
65
+ return 0x20000000;
66
+ }
67
+ else
68
+ {
69
+ return address;
70
+ }
71
+ }
72
+
73
+ uint32_t board_info_sdram_address(void)
74
+ {
75
+ unsigned address = get_dt_ranges("/proc/device-tree/axi/vc_mem/reg", 8);
76
+
77
+ board_info_init();
78
+
79
+ if (address == ~0)
80
+ {
81
+ if (board_model == MODEL_B_2)
82
+ return 0xc0000000;
83
+ else
84
+ return 0x40000000;
85
+ }
86
+ else
87
+ {
88
+ return address;
89
+ }
90
+ }
91
+
92
+ int board_info_init(void)
93
+ {
94
+ char buf[128], revstr[128], modelstr[128];
95
+ char *ptr, *end, *res;
96
+ FILE *fp;
97
+
98
+ if (board_info_initialised)
99
+ return 0;
100
+
101
+ revstr[0] = modelstr[0] = '\0';
102
+
103
+ fp = fopen("/proc/cpuinfo", "r");
104
+
105
+ if (!fp)
106
+ fatal("Unable to open /proc/cpuinfo: %m\n");
107
+
108
+ while ((res = fgets(buf, 128, fp))) {
109
+ if (!strncasecmp("model name", buf, 8))
110
+ memcpy(modelstr, buf, 128);
111
+ else if (!strncasecmp(buf, "revision", 8))
112
+ memcpy(revstr, buf, 128);
113
+ }
114
+ fclose(fp);
115
+
116
+ if (modelstr[0] == '\0')
117
+ fatal("No 'Model name' record in /proc/cpuinfo\n");
118
+ if (revstr[0] == '\0')
119
+ fatal("No 'Revision' record in /proc/cpuinfo\n");
120
+
121
+ if (strstr(modelstr, "ARMv6"))
122
+ board_model = MODEL_B;
123
+ else if (strstr(modelstr, "ARMv7"))
124
+ board_model = MODEL_B_2;
125
+ else
126
+ fatal("Cannot parse the model name string\n");
127
+
128
+ ptr = revstr + strlen(revstr) - 3;
129
+ board_revision = strtol(ptr, &end, 16);
130
+ if (end != ptr + 2)
131
+ fatal("Failed to parse Revision string\n");
132
+ if (board_revision < 1)
133
+ fatal("Invalid board Revision\n");
134
+ else if (board_revision < 4)
135
+ board_revision = 1;
136
+ else
137
+ board_revision = 2;
138
+
139
+ board_info_initialised = 1;
140
+
141
+ return 0;
142
+ }
143
+