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