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()
Exemplo n.º 2
0
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()
Exemplo n.º 3
0
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)