Module OLED1306
[frames] | no frames]

Source Code for Module OLED1306

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