-
Notifications
You must be signed in to change notification settings - Fork 17
/
blinkytape.py
175 lines (139 loc) · 5.48 KB
/
blinkytape.py
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""BlinkyTape Python communication library.
This code assumes stock serialLoop() in the firmware.
Commands are issued in 3-byte blocks, with pixel data
encoded in RGB triplets in range 0-254, sent sequentially
and a triplet ending with a 255 causes the accumulated pixel
data to display (a show command).
Note that with the stock firmware changing the maximum brightness
over serial communication is impossible.
"""
import serial
# For Python3 support- always run strings through a bytes converter
import sys
if sys.version_info < (3,):
def encode(x):
return x
else:
import codecs
def encode(x):
return codecs.latin_1_encode(x)[0]
class BlinkyTape(object):
def __init__(self, port, ledCount=60, buffered=True):
"""Creates a BlinkyTape object and opens the port.
Parameters:
port
Required, port name as accepted by PySerial library:
http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial
It is the same port name that is used in Arduino IDE.
Ex.: COM5 (Windows), /dev/ttyACM0 (Linux).
ledCount
Optional, total number of LEDs to work with,
defaults to 60 LEDs. The limit is enforced and an
attempt to send more pixel data will throw an exception.
buffered
Optional, enabled by default. If enabled, will buffer
pixel data until a show command is issued. If disabled,
the data will be sent in byte triplets as expected by firmware,
with immediate flush of the serial buffers (slower).
"""
self.port = port
self.ledCount = ledCount
self.position = 0
self.buffered = buffered
self.buf = ""
self.serial = serial.Serial(port, 115200, write_timeout=1)
self.show() # Flush any incomplete data
def send_list(self, colors):
if len(colors) > self.ledCount:
raise RuntimeError("Attempting to set pixel outside range!")
for r, g, b in colors:
self.sendPixel(r, g, b)
self.show()
def send_list(self, colors):
data = ""
for r, g, b in colors:
data += chr(r) + chr(g) + chr(b)
data = data.replace(chr(255), chr(254))
self.serial.write(encode(data))
self.show()
def sendData(self, data):
data = data.replace(chr(255), chr(254))
self.serial.write(encode(data))
self.show()
def sendPixel(self, r, g, b):
"""Sends the next pixel data triplet in RGB format.
Values are clamped to 0-254 automatically.
Throws a RuntimeException if [ledCount] pixels are already set.
"""
data = ""
data = chr(r) + chr(g) + chr(b)
data = data.replace(chr(255), chr(254))
if self.position < self.ledCount:
if self.buffered:
self.buf += data
else:
self.serial.write(encode(data))
self.serial.flush()
self.position += 1
else:
raise RuntimeError("Attempting to set pixel outside range!")
def show(self):
"""Sends the command(s) to display all accumulated pixel data.
Resets the next pixel position to 0, flushes the serial buffer,
and discards any accumulated responses from BlinkyTape.
"""
control = chr(255)
if self.buffered:
# Fix an OS X specific bug where sending more than 383 bytes of data at once
# hangs the BlinkyTape controller. Why this is???
# TODO: Test me on other platforms
CHUNK_SIZE = 300
self.buf += control
for i in range(0, len(self.buf), CHUNK_SIZE):
self.serial.write(encode(self.buf[i:i+CHUNK_SIZE]))
self.serial.flush()
self.buf = ""
else:
self.serial.write(encode(control))
self.serial.flush()
self.serial.flushInput() # Clear responses from BlinkyTape, if any
self.position = 0
def displayColor(self, r, g, b):
"""Fills [ledCount] pixels with RGB color and shows it."""
for i in range(0, self.ledCount):
self.sendPixel(r, g, b)
self.show()
def resetToBootloader(self):
"""Initiates a reset on BlinkyTape.
Note that it will be disconnected.
"""
self.serial.setBaudrate(1200)
self.close()
def close(self):
"""Safely closes the serial port."""
self.serial.close()
# Example code
if __name__ == "__main__":
import glob
import optparse
parser = optparse.OptionParser()
parser.add_option("-p", "--port", dest="portname",
help="serial port (ex: /dev/ttyUSB0)", default=None)
parser.add_option("-c", "--ledcount", dest="ledcount",
help="number of LEDs attached", type="int", default=60)
parser.add_option("-b", action="store_true",
default="True", dest="buffered")
parser.add_option("-u", action="store_false", dest="buffered")
(options, args) = parser.parse_args()
if options.portname is not None:
port = options.portname
else:
serialPorts = glob.glob("/dev/cu.usbmodem*")
port = serialPorts[0]
bt = BlinkyTape(port, options.ledcount, options.buffered)
while True:
bt.displayColor(255, 0, 0)
bt.displayColor(0, 255, 0)
bt.displayColor(0, 0, 255)
bt.displayColor(255, 255, 255)
bt.displayColor(0, 0, 0)