-
Notifications
You must be signed in to change notification settings - Fork 0
/
strip.go
116 lines (102 loc) · 2.64 KB
/
strip.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package ledclient
import (
"fmt"
"time"
"github.com/ambientsound/wirelight/blinken/lib"
"github.com/ambientsound/wirelight/blinken/pb"
"github.com/golang/protobuf/proto"
"github.com/pebbe/zmq4"
)
// The serial is increased by one every time Blinken sends a LED update.
var serial uint64
// Strip represents a strip of LEDs.
type Strip struct {
sock *zmq4.Socket
refreshRate uint64
width int
height int
shutdown chan int
}
// NewStrip returns Strip.
func NewStrip(sock *zmq4.Socket, width, height int, refreshRate uint64) *Strip {
return &Strip{
sock: sock,
refreshRate: refreshRate, // render all LEDs every 15th update
width: width,
height: height,
shutdown: make(chan int, 1),
}
}
// rpcLED transfers one LED value to the remote server.
func (s *Strip) rpcLED(led *pb.LED) error {
serial++
led.Serial = serial
led.Render = (serial%s.refreshRate == 0)
payload, err := proto.Marshal(led)
if err != nil {
return fmt.Errorf("while generating protobuf payload: %s", err)
}
_, err = s.sock.SendBytes(payload, 0)
if err != nil {
return fmt.Errorf("while sending data using ZeroMQ: %s", err)
}
return nil
}
func cycleTime(freq int) time.Duration {
return (1 * time.Second) / time.Duration(freq)
}
// Index returns the physical position of a single LED.
//
// The LEDs are configured in a zig-zag pattern, as drawn below. The LED server
// only knows a single strand, so we must perform the positional conversion here.
//
// START.........................
// |
// ..............................
// |
// ...........................END
//
func (s *Strip) Index(x, y int) uint32 {
if y%2 == 0 {
return uint32(y*s.width + x)
}
return uint32(y*s.width + (s.width - x - 1))
}
// BitBlit transfers image data from an object implementing the Image interface
// to a remote LED server.
func (s *Strip) BitBlit(img *Canvas) error {
led := &pb.LED{}
for y := 0; y < s.height; y++ {
for x := 0; x < s.width; x++ {
led.Index = s.Index(x, y)
c := img.At(x, y)
led.Rgb = lib.RGBA(c.Clamped())
err := s.rpcLED(led)
if err != nil {
return err
}
}
}
return nil
}
// Loop renders the LEDs periodically. This function never returns until
// Close() is called, so be sure to call it in a goroutine.
func (s *Strip) Loop(img *Canvas, freq int) {
c := cycleTime(freq)
for {
select {
case <-s.shutdown:
return
default:
err := s.BitBlit(img)
if err != nil {
fmt.Printf("BitBlit: %s\n", err)
}
time.Sleep(c)
}
}
}
// Close turns all LEDs black and shuts down the rendering function.
func (s *Strip) Close() {
s.shutdown <- 0
}