1
2
3
4
5
6 import SSD1306
7 import RPi.GPIO as GPIO
8 import smbus
9
10
11 import os, time
12 from threading import Thread
13 from PIL import Image
14 from PIL import ImageFont
15 from PIL import ImageDraw
17 '''
18 Creates a display instances with given OLED display type (128x32 or 128x64, black & white).
19 with standard font from font file /usr/share/fonts/truetype/freefont/FreeSans.ttf.
20 @param imagePath: the path to the PPM image file used as background (must have size 128x32 or 128x64 resp.)
21 @param type: 32 or 64 defining 128x32 or 128x64 bit resolution (default: 64)
22 @param inverse: if True, the background is white and the text is black;
23 otherwise the background is black and the text is white (default)
24 '''
25 - def __init__(self, bkImagePath = None, type = 64, inverse = False):
26 bus = smbus.SMBus(1)
27 i2c_address = 0x3C
28 try:
29 bus.read_word_data(i2c_address, 0)
30 except:
31 self._isAvailable = False
32 return
33 self._isAvailable = True
34 if type == 32:
35
36 self.disp = SSD1306.SSD1306_128_32(rst = None, gpio = GPIO)
37 elif type == 64:
38
39 self.disp = SSD1306.SSD1306_128_64(rst = None, gpio = GPIO)
40 else:
41 print "Device type", type, "not supported"
42 self.inverse = inverse
43 self.bkImagePath = bkImagePath
44 self.type = type
45 self.blinkerThread = None
46
47
48 self.disp.begin()
49
50
51 self.width = self.disp.width
52 self.height = self.disp.height
53
54
55 self.disp.clear()
56 self.disp.display()
57
58
59 self.image = Image.new('1', (self.width, self.height))
60
61
62
63 self.fontSize = 10
64
65 self.ttfFileStandard = "/home/pi/Fonts/OpenSans-Semibold.ttf"
66 self.ttfFile = self.ttfFileStandard
67 self.font = ImageFont.truetype(self.ttfFile, 10)
68
69
70 self.draw = ImageDraw.Draw(self.image)
71
72
73 self.disp.set_contrast(255)
74
75
76 self.textBuf = {}
77 self.cursor = [0, 0]
78 self.scroll = False
79
80 - def dim(self, enable):
81 '''
82 Enables/disables dimming the display.
83 @param enable: if True, the display is slightly dimmed;
84 otherwise it is set to full contrast
85 '''
86 if enable:
87 self.disp.set_contrast(0)
88 else:
89 self.disp.set_contrast(255)
90
92 '''
93 Defines a background image to be displayed with next setText() or show().
94 '''
95 self.bkImagePath = bkImagePath
96
97 - def setFont(self, ttfFile, fontSize = 10):
98 '''
99 Sets a new font defined by the given TTF font file.
100 @ttfFile: the path to the font file (only TTF fonts supported)
101 @fontSize: the font size (default: 10)
102 '''
103 self.ttfFile = ttfFile
104 self.ttfFileStandard = ttfFile
105 self.fontSize = fontSize
106 self.font = ImageFont.truetype(ttfFile, fontSize)
107
109 '''
110 Sets a new font size of current font.
111 @fontSize: the new font size
112 '''
113 self.font = ImageFont.truetype(self.ttfFile, fontSize)
114 self.fontSize = fontSize
115
117 '''
118 Erases the display and clears the text buffer.
119 '''
120 if self.inverse:
121 self.draw.rectangle((0, 0, self.width, self.height), outline = 0, fill = 255)
122 else:
123 self.draw.rectangle((0, 0, self.width, self.height), outline = 0, fill = 0)
124 self.disp.image(self.image)
125 self.disp.display()
126 self.textBuf = {}
127 self.cursor = [0, 0]
128 self.scroll = False
129
131 '''
132 Erases the display without clearing the text buffer.
133 '''
134 if self.inverse:
135 self.draw.rectangle((0, 0, self.width, self.height), outline = 0, fill = 255)
136 else:
137 self.draw.rectangle((0, 0, self.width, self.height), outline = 0, fill = 0)
138 self.disp.image(self.image)
139 self.disp.display()
140
141 - def setText(self, text, lineNum = 0, fontSize = None, indent = 0):
142 '''
143 Displays text at given line left adjusted.
144 The old text of this line is erased, other text is not modified
145 The line distance is defined by the font size (text height + 1).
146 If no text is attributed to a line, the line is considered to consist of a single space
147 character with the font size of the preceeding line.
148 The position of the text cursor is not modified.
149 Text separated by \n is considered as a multiline text. In this case lineNum is the line number of the
150 first line.
151 @param text: the text to display. If emtpy, text with a single space character is assumed.
152 @param lineNum: the line number where to display the text (default: 0)
153 @param fontSize: the size of the font (default: None, set to current font size)
154 @indent: the line indent in pixels (default: 0)
155 '''
156 if "\n" not in text:
157 self._setLine(text, lineNum, fontSize, indent)
158 else:
159 lines = text.split("\n")
160 nb = lineNum
161 for line in lines:
162 self._setLine(line, nb, fontSize, indent)
163 nb += 1
164
165 - def _setLine(self, text, lineNum, fontSize, indent):
166 if text == "":
167 text = " "
168 if fontSize == None:
169 fontSize = self.fontSize
170 self.textBuf[lineNum] = (text, fontSize, indent)
171 self.repaint()
172
174 '''
175 Returns the current font size.
176 @return: the font size
177 '''
178 return self.fontSize
179
181 '''
182 Returns the height of one line.
183 @return: line spacing in pixels
184 '''
185 charWidthDummy, charHeight = self.draw.textsize('I', font = self.font)
186 return charHeight
187
189 '''
190 Repaints the screen (background image and text buffer).
191 '''
192 if len(self.textBuf) == 0:
193 maxNum = 0
194 else:
195 maxNum = max(self.textBuf.keys())
196
197 if self.inverse:
198 self.draw.rectangle((0, 0, self.width, self.height), outline = 0, fill = 255)
199 else:
200 self.draw.rectangle((0, 0, self.width, self.height), outline = 0, fill = 0)
201
202 if self.bkImagePath != None:
203 picture = Image.open(self.bkImagePath).convert('1')
204 self.image.paste(picture)
205 y = 0
206 lastFontSize = 0
207 for lineNb in range(maxNum + 1):
208 try:
209 text = self.textBuf[lineNb][0]
210 fontSize = self.textBuf[lineNb][1]
211 x = self.textBuf[lineNb][2]
212 except:
213 text = " "
214 fontSize = 10
215 x = 0
216 self.font = ImageFont.truetype(self.ttfFile, fontSize)
217 charWidthDummy, charHeight = self.draw.textsize('A', font = self.font)
218 for c in text:
219 charWidth, charHeightDummy = self.draw.textsize(c, font = self.font)
220 if self.inverse:
221 self.draw.text((x, y), c, font = self.font, fill = 0)
222 else:
223 self.draw.text((x, y), c, font = self.font, fill = 255)
224 x += charWidth
225 y += charHeight
226
227
228 self.disp.image(self.image)
229 self.disp.display()
230
232 '''
233 Shows the image (1 pixel monochrome) with given filename (must have size 128x32 for display type 32
234 or 128x64 for display type 64).
235 @param imagePath: the path to the PPM image file
236 '''
237 picture = Image.open(imagePath).convert('1')
238 self.disp.image(picture)
239 self.disp.display()
240
242 '''
243 Appends text at current cursor position and scrolls, if necessary.
244 Sets the cursor at the beginning of next line.
245 @param text: the text to display
246 '''
247 charWidthDummy, charHeight = self.draw.textsize('I', font = self.font)
248 nbLines = int(self.type / (charHeight))
249
250 if not self.scroll:
251 self.setText(text, self.cursor[1])
252 self.cursor[1] += 1
253 if self.cursor[1] == nbLines:
254 self.scroll = True
255 else:
256 for i in range(nbLines - 1):
257 self.textBuf[i] = self.textBuf[i + 1]
258 self.setText(text, nbLines - 1)
259
261 '''
262 Sets the current font size to a maximum to show the given number of lines.
263 @param: the number of lines to display
264 '''
265 fontSize = int(self.type / nbLines) + 1
266 self.setFontSize(fontSize)
267
269 '''
270 @param inverse: if True, the background is white and the text is black;
271 otherwise the background is black and the text is white (default)
272 '''
273 self.inverse = inverse
274 self.repaint()
275
276 - def startBlinker(self, count = 3, offTime = 1000, onTime = 1000, blocking = False):
277 '''
278 Blicks the entire screen for given number of times (off-on periods).
279 @param count: the number of blinking (default: 3)
280 @param offTime: the time the display is erased (in ms, default: 1000)
281 @param onTime: the time the display is shown (in ms, default: 1000)
282 @param blocking: if True, the function blocks until the blinking is finished; otherwise
283 it returns immediately
284 '''
285 if self.blinkerThread != None:
286 self.stopBlinker()
287 self.blinkerThread = BlinkerThread(self, count, offTime, onTime)
288 if blocking:
289 while self.isBlinkerAlive():
290 continue
291
293 '''
294 Stops a running blinker.
295 The method blocks until the blinker thread is finished and isBlinkerAlive() returns False.
296 '''
297 if self.blinkerThread != None:
298 self.blinkerThread.stop()
299 self.blinkerThread = None
300
302 '''
303 @return: True, if the blinker is displaying; otherwise False
304 '''
305 time.sleep(0.001)
306 if self.blinkerThread == None:
307 return False
308 return self.blinkerThread.isAlive
309
311 '''
312 Returns True, if the device is detected on the I2C bus;
313 otherwise returns False
314 '''
315 return self._isAvailable
316
317
319 - def __init__(self, display, count, offTime, onTime):
320 Thread.__init__(self)
321 self.display = display
322 self.offTime = offTime
323 self.onTime = onTime
324 self.count = count
325 self.isRunning = False
326 self.isAlive = True
327 self.start()
328 while not self.isRunning:
329 continue
330
332 nb = 0
333 time.sleep(1)
334 self.isRunning = True
335 while self.isRunning:
336 self.display.erase()
337 startTime = time.time()
338 while time.time() - startTime < self.offTime / 1000 and self.isRunning:
339 time.sleep(0.001)
340 if not self.isRunning:
341 break
342 nb += 1
343 self.display.repaint()
344 startTime = time.time()
345 while time.time() - startTime < self.onTime / 1000 and self.isRunning:
346 time.sleep(0.001)
347 if not self.isRunning:
348 break
349 if nb == self.count:
350 self.isRunning = False
351 self.isAlive = False
352
354 self.isRunning = False
355 while self.isAlive:
356 continue
357