class eInkDisplay(object): """ An object to manage the Waveshare e-ink display """ def __init__(self, vcom: float): self.display = AutoEPDDisplay(vcom=vcom) self.clear_display() self.dims = (self.display.width, self.display.height) def clear_display(self): """ Clears display by removing any image """ self.display.clear() def paste_image(self, img): """ Pastes a PIL image to the display """ self.display.frame_buf.paste( 0xFF, box=(0, 0, self.display.width, self.display.height) ) paste_coords = [self.dims[i] - img.size[i] for i in (0, 1)] self.display.frame_buf.paste(img, paste_coords) self.display.draw_full(constants.DisplayModes.GC16)
def main(): from sys import path path += ['../../'] from IT8951.display import AutoEPDDisplay display = AutoEPDDisplay(vcom=-2.06) print('VCOM set to', display.epd.get_vcom()) # 1. Clear Display print('Clearing display...') display.clear() # 2. Draw demo image = Image.new('1', (display.width, display.height), 1) draw = ImageDraw.Draw(image) font = ImageFont.truetype( '/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf', 24) draw.rectangle((0, 6, 1200, 30), fill=0) draw.text((200, 10), 'e-Paper demo', font=font, fill=255) draw.rectangle((200, 80, 600, 280), fill=0) draw.arc((240, 120, 580, 220), 0, 360, fill=255) draw.rectangle((0, 80, 160, 280), fill=255) draw.arc((40, 80, 180, 220), 0, 360, fill=0) color = 0x10 display.frame_buf.paste(color, box=image) display.draw_full(constants.DisplayModes.GC16) sleep(1) # 3. Clear Display print('Clearing display...') display.clear() # 4. Draw Image print('Draw Image...') image = Image.open('monocolor.bmp') dims = (display.width, display.height) image.thumbnail(dims) paste_coords = [dims[i] - image.size[i] for i in (0, 1)] # align image with bottom of display display.frame_buf.paste(image, paste_coords) display.draw_full(constants.DisplayModes.GC16)
class DrawingAdapterEPD(DrawingAdapter): def __init__(self, vcom: float): if has_it8951: print('Setting up the display using VCOM=' + str(vcom)) self.display = AutoEPDDisplay(vcom=vcom, spi_hz=24000000) self.display.epd.wait_display_ready() self.display.clear() else: raise Exception("IT8951 driver not present") self.lock = threading.RLock() super().__init__(self.display.frame_buf) def draw(self): with self.lock: self.display.epd.run() self.display.draw_full(constants.DisplayModes.GC16) self.display.epd.wait_display_ready() self.display.epd.sleep()
def main(): print('Initializing...') display = AutoEPDDisplay(vcom=-2.06) display.clear() print('Writing initial image...') place_text(display.frame_buf, 'partial', x_offset=-200) display.draw_full(constants.DisplayModes.GC16) # so that we're not timing the previous operations display.epd.wait_display_ready() print('Doing partial update...') place_text(display.frame_buf, 'update', x_offset=+200) profile_func( display.draw_partial, constants.DisplayModes.DU # should see what best mode is here )
def draw_thread(q): print('Initializing EPD...') logging.info('Initializing EPD...') # here, spi_hz controls the rate of data transfer to the device, so a higher # value means faster display refreshes. the documentation for the IT8951 device # says the max is 24 MHz (24000000), but my device seems to still work as high as # 80 MHz (80000000) rotate = None display = AutoEPDDisplay(vcom=-1.32, rotate=rotate, spi_hz=24000000) partial_update_draw_count = 0 last_drawn_graphics = None last_clear = datetime.now() last_total_reset = datetime.now() while True: try: graphics = q.pop() logging.error("drawing updated graphics") last_drawn_graphics = graphics partial_update_draw_count += 1 if partial_update_draw_count > 10: partial_update_draw_count = 0 display.frame_buf.paste(0xFF, box=(0, 0, display.width, display.height)) draw(display, graphics) display.draw_full(constants.DisplayModes.GC16) else: # clearing image to white display.frame_buf.paste(0xFF, box=(0, 0, display.width, display.height)) draw(display, graphics) display.draw_partial(constants.DisplayModes.DU) except IndexError: time.sleep(0.01) if (datetime.now() - last_total_reset).total_seconds() > 60*60*6: # 6 hours logging.error("REINIT EINK DISPLAY") display = AutoEPDDisplay(vcom=-1.32, rotate=rotate, spi_hz=24000000) last_total_reset = datetime.now() if (datetime.now() - last_clear).total_seconds() > 60: display.clear() if last_drawn_graphics is not None: draw(display, last_drawn_graphics) display.draw_full(constants.DisplayModes.GC16) last_clear = datetime.now()
def main(): print('Initializing...') display = AutoEPDDisplay(vcom=-2.06, spi_hz=24000000) display.clear() # so that we're not timing the previous operations display.epd.wait_display_ready() print('Doing update...') # draw all black display.frame_buf.paste(0x00, (0, 0, display.width, display.height)) p = Profiler() p.profile_func( display.draw_partial, constants.DisplayModes.DU # should see what best mode is here ) p.profile_func(display.epd.spi.wait_ready) p.print_results()
def main(): print('Initializing...') display = AutoEPDDisplay(vcom=-2.06) display.clear() # so that we're not timing the previous operations display.epd.wait_display_ready() print('Doing partial update...') char_height = 20 char_width = 12 rows = display.height // char_height cols = display.width // char_width p = Profiler() text = 'partialupdate' start = default_timer() for n, c in enumerate(cycle(text)): row = n // cols col = n % cols place_text(display.frame_buf, c, x=col * char_width, y=row * char_height) p.profile_func( display.draw_partial, constants.DisplayModes.DU # should see what best mode is here ) # run for 10 seconds then stop if default_timer() - start > 10: print('total iterations: {}'.format(n + 1)) break p.print_results()
class TtyInk(): def __init__( self, display_mode=DisplayModes.GL16, image_filter=Image.NEAREST, debug=False ): global VCOM start = time.time() self.display_mode = display_mode self.image_filter = image_filter self.debug = debug self.epd = EPD() self.display = AutoEPDDisplay(epd=self.epd, vcom=VCOM) self.dims = (self.display.width, self.display.height) self.prev = None if self.debug: print(f"Time to initialize: {round(time.time() - start, 3)}s") start = time.time() self.display.clear() if self.debug: print(f"Time to clear: {round(time.time() - start, 3)}s") def __enter__(self): return self def __exit__(self, _type, _value, _traceback): pass def refresh(self, full=False, display_mode=None): start = time.time() self.display_to_screen(self.get_screen(), full=full, display_mode=display_mode) if self.debug: print(f"Time to refresh: {round(time.time() - start, 3)}s") def display_to_screen(self, image, full=False, display_mode=None): start = time.time() self.display.frame_buf.paste(image, [0, 0]) if full: self.display.draw_full(self.display_mode if display_mode is None else display_mode) else: self.display.draw_partial(self.display_mode if display_mode is None else display_mode) if self.debug: print(f"Time to display: {round(time.time() - start, 3)}s") def get_screen(self): global TTY_DEVICE start = time.time() output = subprocess.run( f"{SCRIPT_DIRECTORY}/tty.sh {TTY_DEVICE}".split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL ) if self.debug: print(f"Time to capture image: {round(time.time() - start, 3)}s") start = time.time() image = Image.open(io.BytesIO(output.stdout)) image = image.resize(self.dims, self.image_filter) if self.debug: print(f"Time to transform image: {round(time.time() - start, 3)}s") return image def wait(self): self.epd.wait_display_ready()
from IT8951.display import AutoEPDDisplay display = AutoEPDDisplay(vcom=-2.25, rotate=None, spi_hz=24000000) display.clear()
class Runner: def __init__(self, profile=False, ttyn=1, frame_rate=10, flip=False): self.ttyn = ttyn self.inv_frame_rate = 1 / frame_rate self.profile = profile if self.profile: self.pr = cProfile.Profile() # keep track of two displays: one for the terminal, the other for when # processes want to take it over epd = EPD(vcom=-2.06) self.term_display = AutoEPDDisplay(epd, flip=flip) self.controller_display = Controller(epd, flip=flip) print('Initializing...') self.term_display.clear() self.term = Terminal( (self.term_display.width, self.term_display.height), frame_buf=self.term_display.frame_buf) auto_resize_tty(self.ttyn, self.term.char_dims, (self.term_display.width, self.term_display.height)) # handle both of these the same way signal.signal(signal.SIGTERM, self.sigterm_handler) signal.signal(signal.SIGINT, self.sigterm_handler) def sigterm_handler(self, sig=None, frame=None): self.running = False def on_exit(self): ''' Some things to do when we exit cleanly. We don't want to do them when we are not exiting cleanly, so they don't go in __del__ ''' print('Exiting...') if self.profile: s = io.StringIO() ps = pstats.Stats(self.pr, stream=s).sort_stats('cumulative') ps.print_stats() print(s.getvalue()) self.display_penguin() def update_callback(self, need_gray): ''' This function gets called whenever a character gets updated by term, in order to get quick updates on the screen ''' if need_gray: self.term_display.draw_partial(constants.DisplayModes.GL16) else: self.term_display.draw_partial(constants.DisplayModes.DU) def update(self): ''' Update the contents of the display ''' # if another process has decided to take the display, do that if self.controller_display.check_active(): self.controller_display.run() self.term_display.draw_full( constants.DisplayModes.GC16) # get our terminal back # currently just want to profile the updates done here if self.profile: self.pr.enable() cursor_pos, data = read_vcsa(self.ttyn) changed = self.term.update(cursor_pos, data, callback=self.update_callback) if self.profile: self.pr.disable() if changed: self.last_change = perf_counter() self.need_update = True elif self.need_update and perf_counter() - self.last_change > 10: # if it's been long time, clear out the ghosting self.term_display.draw_full(constants.DisplayModes.GC16) self.need_update = False def run(self): print('Running...') self.running = True self.need_update = False # TODO: it would be cool to trigger events off of changes # rather than just polling this file all the time. not sure # if there's a good way to do that while self.running: loop_start = perf_counter() self.update() # sleep for less time if the update took a while sleep_time = self.inv_frame_rate - (perf_counter() - loop_start) if sleep_time > 0: sleep(sleep_time) self.on_exit() def display_penguin(self): ''' Display a cute sleeping Tux to remain on the screen when we shut down the terminal. ''' img_path = join(dirname(__file__), 'images/sleeping_penguin.png') # clear image to white img_bounds = (0, 0, self.term_display.width, self.term_display.height) self.term_display.frame_buf.paste(0xFF, box=img_bounds) img = Image.open(img_path) dims = self.term_display.frame_buf.size # half of the display size img.thumbnail([x // 2 for x in dims]) paste_coords = ( # put it at the bottom of the display, centered (dims[0] - img.size[0]) // 2, dims[1] - img.size[1], ) self.term_display.frame_buf.paste(img, paste_coords) self.term_display.draw_full(constants.DisplayModes.GC16)