]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_epaperdisplay.py
Code finished, but not working
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _epaperdisplay.py
1 # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
2 #
3 # SPDX-License-Identifier: MIT
4
5 """
6 `displayio.epaperdisplay`
7 ================================================================================
8
9 displayio for Blinka
10
11 **Software and Dependencies:**
12
13 * Adafruit Blinka:
14   https://github.com/adafruit/Adafruit_Blinka/releases
15
16 * Author(s): Melissa LeBlanc-Williams
17
18 """
19
20 import time
21 from typing import Optional, Union
22 import microcontroller
23 from digitalio import DigitalInOut
24 from circuitpython_typing import ReadableBuffer
25 from ._displaycore import _DisplayCore
26 from ._group import Group, circuitpython_splash
27 from ._colorconverter import ColorConverter
28 from ._displaybus import _DisplayBus
29 from ._area import Area
30 from ._constants import (
31     CHIP_SELECT_TOGGLE_EVERY_BYTE,
32     CHIP_SELECT_UNTOUCHED,
33     DISPLAY_COMMAND,
34     DISPLAY_DATA,
35     NO_COMMAND,
36     DELAY,
37 )
38
39 __version__ = "0.0.0+auto.0"
40 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
41
42
43 class EPaperDisplay:
44     # pylint: disable=too-many-instance-attributes, too-many-statements
45     """Manage updating an epaper display over a display bus
46
47     This initializes an epaper display and connects it into CircuitPython. Unlike other
48     objects in CircuitPython, EPaperDisplay objects live until
49     displayio.release_displays() is called. This is done so that CircuitPython can use
50     the display itself.
51
52     Most people should not use this class directly. Use a specific display driver instead
53     that will contain the startup and shutdown sequences at minimum.
54     """
55
56     def __init__(
57         self,
58         display_bus: _DisplayBus,
59         start_sequence: ReadableBuffer,
60         stop_sequence: ReadableBuffer,
61         *,
62         width: int,
63         height: int,
64         ram_width: int,
65         ram_height: int,
66         colstart: int = 0,
67         rowstart: int = 0,
68         rotation: int = 0,
69         set_column_window_command: Optional[int] = None,
70         set_row_window_command: Optional[int] = None,
71         set_current_column_command: Optional[int] = None,
72         set_current_row_command: Optional[int] = None,
73         write_black_ram_command: int,
74         black_bits_inverted: bool = False,
75         write_color_ram_command: Optional[int] = None,
76         color_bits_inverted: bool = False,
77         highlight_color: int = 0x000000,
78         refresh_display_command: Union[int, ReadableBuffer],
79         refresh_time: float = 40.0,
80         busy_pin: Optional[microcontroller.Pin] = None,
81         busy_state: bool = True,
82         seconds_per_frame: float = 180,
83         always_toggle_chip_select: bool = False,
84         grayscale: bool = False,
85         advanced_color_epaper: bool = False,
86         two_byte_sequence_length: bool = False,
87         start_up_time: float = 0,
88         address_little_endian: bool = False,
89     ) -> None:
90         # pylint: disable=too-many-locals
91         """Create a EPaperDisplay object on the given display bus (`displayio.FourWire` or
92         `paralleldisplay.ParallelBus`).
93
94         The ``start_sequence`` and ``stop_sequence`` are bitpacked to minimize the ram impact. Every
95         command begins with a command byte followed by a byte to determine the parameter count and
96         delay. When the top bit of the second byte is 1 (0x80), a delay will occur after the command
97         parameters are sent. The remaining 7 bits are the parameter count excluding any delay
98         byte. The bytes following are the parameters. When the delay bit is set, a single byte after
99         the parameters specifies the delay duration in milliseconds. The value 0xff will lead to an
100         extra long 500 ms delay instead of 255 ms. The next byte will begin a new command
101         definition.
102
103         :param display_bus: The bus that the display is connected to
104         :type _DisplayBus: displayio.FourWire or paralleldisplay.ParallelBus
105         :param ~circuitpython_typing.ReadableBuffer start_sequence: Byte-packed command sequence.
106         :param ~circuitpython_typing.ReadableBuffer stop_sequence: Byte-packed command sequence.
107         :param int width: Width in pixels
108         :param int height: Height in pixels
109         :param int ram_width: RAM width in pixels
110         :param int ram_height: RAM height in pixels
111         :param int colstart: The index if the first visible column
112         :param int rowstart: The index if the first visible row
113         :param int rotation: The rotation of the display in degrees clockwise. Must be in
114             90 degree increments (0, 90, 180, 270)
115         :param int set_column_window_command: Command used to set the start and end columns
116             to update
117         :param int set_row_window_command: Command used so set the start and end rows to update
118         :param int set_current_column_command: Command used to set the current column location
119         :param int set_current_row_command: Command used to set the current row location
120         :param int write_black_ram_command: Command used to write pixels values into the update
121             region
122         :param bool black_bits_inverted: True if 0 bits are used to show black pixels. Otherwise,
123             1 means to show black.
124         :param int write_color_ram_command: Command used to write pixels values into the update
125             region
126         :param bool color_bits_inverted: True if 0 bits are used to show the color. Otherwise, 1
127             means to show color.
128         :param int highlight_color: RGB888 of source color to highlight with third ePaper color.
129         :param int refresh_display_command: Command used to start a display refresh. Single int
130             or byte-packed command sequence
131         :param float refresh_time: Time it takes to refresh the display before the stop_sequence
132             should be sent. Ignored when busy_pin is provided.
133         :param microcontroller.Pin busy_pin: Pin used to signify the display is busy
134         :param bool busy_state: State of the busy pin when the display is busy
135         :param float seconds_per_frame: Minimum number of seconds between screen refreshes
136         :param bool always_toggle_chip_select: When True, chip select is toggled every byte
137         :param bool grayscale: When true, the color ram is the low bit of 2-bit grayscale
138         :param bool advanced_color_epaper: When true, the display is a 7-color advanced color
139             epaper (ACeP)
140         :param bool two_byte_sequence_length: When true, use two bytes to define sequence length
141         :param float start_up_time: Time to wait after reset before sending commands
142         :param bool address_little_endian: Send the least significant byte (not bit) of
143             multi-byte addresses first. Ignored when ram is addressed with one byte
144         """
145
146         if isinstance(refresh_display_command, int):
147             refresh_sequence = bytearray([refresh_display_command, 0])
148             if two_byte_sequence_length:
149                 refresh_sequence += bytes([0])
150         elif isinstance(refresh_display_command, ReadableBuffer):
151             refresh_sequence = bytearray(refresh_display_command)
152         else:
153             raise ValueError("Invalid refresh_display_command")
154
155         if write_color_ram_command is None:
156             write_color_ram_command = NO_COMMAND
157
158         if rotation % 90 != 0:
159             raise ValueError("Display rotation must be in 90 degree increments")
160
161         color_depth = 1
162         core_grayscale = True
163
164         if advanced_color_epaper:
165             color_depth = 4
166             grayscale = False
167             core_grayscale = False
168
169         self._core = _DisplayCore(
170             bus=display_bus,
171             width=width,
172             height=height,
173             ram_width=ram_width,
174             ram_height=ram_height,
175             colstart=colstart,
176             rowstart=rowstart,
177             rotation=rotation,
178             color_depth=color_depth,
179             grayscale=core_grayscale,
180             pixels_in_byte_share_row=True,
181             bytes_per_cell=1,
182             reverse_pixels_in_byte=True,
183             reverse_bytes_in_word=True,
184             column_command=set_column_window_command,
185             row_command=set_row_window_command,
186             set_current_column_command=set_current_column_command,
187             set_current_row_command=set_current_row_command,
188             data_as_commands=False,
189             always_toggle_chip_select=always_toggle_chip_select,
190             sh1107_addressing=False,
191             address_little_endian=address_little_endian,
192         )
193
194         if highlight_color != 0x000000:
195             self._core.colorspace.tricolor = True
196             self._core.colorspace.tricolor_hue = ColorConverter._compute_hue(
197                 highlight_color
198             )
199             self._core.colorspace.tricolor_luma = ColorConverter._compute_luma(
200                 highlight_color
201             )
202         else:
203             self._core.colorspace.tricolor = False
204
205         self._acep = advanced_color_epaper
206         self._core.colorspace.sevencolor = advanced_color_epaper
207         self._write_black_ram_command = write_black_ram_command
208         self._black_bits_inverted = black_bits_inverted
209         self._write_color_ram_command = write_color_ram_command
210         self._color_bits_inverted = color_bits_inverted
211         self._refresh_time_ms = refresh_time * 1000
212         self._busy_state = busy_state
213         self._refreshing = False
214         self._milliseconds_per_frame = seconds_per_frame * 1000
215         self._chip_select = (
216             CHIP_SELECT_TOGGLE_EVERY_BYTE
217             if always_toggle_chip_select
218             else CHIP_SELECT_UNTOUCHED
219         )
220         self._grayscale = grayscale
221
222         self._start_sequence = start_sequence
223         self._start_up_time = start_up_time
224         self._stop_sequence = stop_sequence
225         self._refesh_sequence = refresh_sequence
226         self._busy = None
227         self._two_byte_sequence_length = two_byte_sequence_length
228         if busy_pin is not None:
229             self._busy = DigitalInOut(busy_pin)
230             self._busy.switch_to_input()
231
232         # Clear the color memory if it isn't in use
233         if highlight_color == 0x00 and write_color_ram_command != NO_COMMAND:
234             """TODO: Clear"""
235
236         self.show(circuitpython_splash)
237
238     def __new__(cls, *args, **kwargs):
239         from . import (  # pylint: disable=import-outside-toplevel, cyclic-import
240             allocate_display,
241         )
242
243         display_instance = super().__new__(cls)
244         allocate_display(display_instance)
245         return display_instance
246
247     def show(self, group: Group) -> None:
248         # pylint: disable=unnecessary-pass
249         """Switches to displaying the given group of layers. When group is None, the default
250         CircuitPython terminal will be shown (eventually).
251         """
252         if group is None:
253             group = circuitpython_splash
254         self._core.set_root_group(group)
255
256     def update_refresh_mode(
257         self, start_sequence: ReadableBuffer, seconds_per_frame: float
258     ) -> None:
259         """Updates the ``start_sequence`` and ``seconds_per_frame`` parameters to enable
260         varying the refresh mode of the display."""
261         self._start_sequence = bytearray(start_sequence)
262         self._milliseconds_per_frame = seconds_per_frame * 1000
263
264     def refresh(self) -> None:
265         """Refreshes the display immediately or raises an exception if too soon. Use
266         ``time.sleep(display.time_to_refresh)`` to sleep until a refresh can occur.
267         """
268         if not self._refresh():
269             raise RuntimeError("Refresh too soon")
270
271     def _refresh(self) -> bool:
272         if self._refreshing and self._busy is not None:
273             if self._busy.value != self._busy_state:
274                 self._refreshing = False
275                 self._send_command_sequence(False, self._stop_sequence)
276             else:
277                 return False
278         if self._core.current_group is None:
279             return True
280         # Refresh at seconds per frame rate
281         if self.time_to_refresh > 0:
282             return False
283
284         if not self._core.bus_free():
285             # Can't acquire display bus; skip updating this display. Try next display
286             return False
287
288         areas_to_refresh = self._get_refresh_areas()
289         if not areas_to_refresh:
290             return True
291
292         if self._acep:
293             self._start_refresh()
294             self._clean_area()
295             self._finish_refresh()
296             while self._refreshing:
297                 # TODO: Add something here that can change self._refreshing
298                 pass
299
300         self._start_refresh()
301         for area in areas_to_refresh:
302             self._refresh_area(area)
303         self._core.finish_refresh()
304
305         return True
306
307     def _release(self) -> None:
308         """Release the display and free its resources"""
309         if self._refreshing:
310             self._wait_for_busy()
311             self._refreshing = False
312             # Run stop sequence but don't wait for busy because busy is set when sleeping
313             self._send_command_sequence(False, self._stop_sequence)
314         self._core.release_display_core()
315         if self._busy is not None:
316             self._busy.deinit()
317
318     def _background(self) -> None:
319         """Run background refresh tasks."""
320         if self._refreshing:
321             refresh_done = False
322             if self._busy is not None:
323                 busy = self._busy.value
324                 refresh_done = busy == self._busy_state
325             else:
326                 refresh_done = (
327                     time.monotonic() * 1000 - self._core.last_refresh
328                     > self._refresh_time
329                 )
330             if refresh_done:
331                 self._refreshing = False
332                 self._finish_refresh()
333                 # Run stop sequence but don't wait for busy because busy is set when sleeping
334                 self._send_command_sequence(False, self._stop_sequence)
335
336     def _get_refresh_areas(self) -> list[Area]:
337         """Get a list of areas to be refreshed"""
338         areas = []
339         if self._core.full_refresh:
340             areas.append(self._core.area)
341             return areas
342         first_area = None
343         if self._core.current_group is not None:
344             self._core.current_group._get_refresh_areas(  # pylint: disable=protected-access
345                 areas
346             )
347             first_area = areas[0]
348         if first_area is not None and self._core.row_command == NO_COMMAND:
349             # Do a full refresh if the display doesn't support partial updates
350             areas = [self._core.area]
351         return areas
352
353     def _refresh_area(self, area: Area) -> bool:
354         """Redraw the area."""
355         # pylint: disable=too-many-locals, too-many-branches
356
357         clipped = Area()
358         # Clip the area to the display by overlapping the areas.
359         # If there is no overlap then we're done.
360         if not self._core.clip_area(area, clipped):
361             return True
362
363         subrectangles = 1
364         rows_per_buffer = clipped.height()
365         pixels_per_word = 32 // self._core.colorspace.depth
366         pixels_per_buffer = clipped.size()
367
368         # We should have lots of memory
369         buffer_size = clipped.size() // pixels_per_word
370
371         if clipped.size() > buffer_size * pixels_per_word:
372             rows_per_buffer = buffer_size * pixels_per_word // clipped.width()
373             if rows_per_buffer == 0:
374                 rows_per_buffer = 1
375             subrectangles = clipped.height() // rows_per_buffer
376             if clipped.height() % rows_per_buffer != 0:
377                 subrectangles += 1
378             pixels_per_buffer = rows_per_buffer * clipped.width()
379             buffer_size = pixels_per_buffer // pixels_per_word
380             if pixels_per_buffer % pixels_per_word:
381                 buffer_size += 1
382
383         mask_length = (pixels_per_buffer // 8) + 1  # 1 bit per pixel + 1
384
385         passes = 1
386         if self._write_color_ram_command != NO_COMMAND:
387             passes = 2
388         for pass_index in range(passes):
389             remaining_rows = clipped.height()
390             if self._core.row_command != NO_COMMAND:
391                 self._core.set_region_to_update(clipped)
392
393             write_command = self._write_black_ram_command
394             if pass_index == 1:
395                 write_command = self._write_color_ram_command
396
397             self._core.begin_transaction()
398             self._core.send(DISPLAY_COMMAND, self._chip_select, bytes([write_command]))
399             self._core.end_transaction()
400
401             for subrect_index in range(subrectangles):
402                 subrectangle = Area(
403                     clipped.x1,
404                     clipped.y1 + rows_per_buffer * subrect_index,
405                     clipped.x2,
406                     clipped.y1 + rows_per_buffer * (subrect_index + 1),
407                 )
408                 if remaining_rows < rows_per_buffer:
409                     subrectangle.y2 = subrectangle.y1 + remaining_rows
410                 remaining_rows -= rows_per_buffer
411
412                 subrectangle_size_bytes = subrectangle.size() // (
413                     8 // self._core.colorspace.depth
414                 )
415
416                 buffer = memoryview(bytearray([0] * (buffer_size * 4)))
417                 mask = memoryview(bytearray([0] * mask_length))
418
419                 if not self._acep:
420                     self._core.colorspace.grayscale = True
421                     self._core.colorspace.grayscale_bit = 7
422                 if pass_index == 1:
423                     if self._grayscale:  # 4-color grayscale
424                         self._core.colorspace.grayscale_bit = 6
425                         self._core.fill_area(subrectangle, mask, buffer)
426                     elif self._core.colorspace.tricolor:
427                         self._core.colorspace.grayscale = False
428                         self._core.fill_area(subrectangle, mask, buffer)
429                     elif self._core.colorspace.sevencolor:
430                         self._core.fill_area(subrectangle, mask, buffer)
431                 else:
432                     self._core.fill_area(subrectangle, mask, buffer)
433
434                 # Invert it all
435                 if (pass_index == 1 and self._color_bits_inverted) or (
436                     pass_index == 0 and self._black_bits_inverted
437                 ):
438                     for i, _ in enumerate(buffer):
439                         buffer[i] = ~buffer[i]
440
441                 if not self._core.begin_transaction():
442                     # Can't acquire display bus; skip the rest of the data. Try next display.
443                     return False
444                 self._core.send(
445                     DISPLAY_DATA, self._chip_select, buffer[:subrectangle_size_bytes]
446                 )
447                 self._core.end_transaction()
448         return True
449
450     def _send_command_sequence(
451         self, should_wait_for_busy: bool, sequence: ReadableBuffer
452     ) -> None:
453         i = 0
454         while i < len(sequence):
455             command = sequence[i]
456             data_size = sequence[i + 1]
457             delay = (data_size & DELAY) != 0
458             data_size &= ~DELAY
459             data = sequence[i + 2 : i + 2 + data_size]
460             if self._two_byte_sequence_length:
461                 data_size = ((data_size & ~DELAY) << 8) + sequence[i + 2]
462                 data = sequence[i + 3 : i + 3 + data_size]
463
464             self._core.begin_transaction()
465             self._core.send(
466                 DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command])
467             )
468             self._core.send(
469                 DISPLAY_DATA,
470                 CHIP_SELECT_UNTOUCHED,
471                 data,
472             )
473             self._core.end_transaction()
474             delay_time_ms = 0
475             if delay:
476                 data_size += 1
477                 delay_time_ms = sequence[i + 1 + data_size]
478                 if delay_time_ms == 255:
479                     delay_time_ms = 500
480             time.sleep(delay_time_ms / 1000)
481             if should_wait_for_busy:
482                 self._wait_for_busy()
483             i += 2 + data_size
484             if self._two_byte_sequence_length:
485                 i += 1
486
487     def _start_refresh(self) -> None:
488         # Run start sequence
489         self._core._bus_reset()  # pylint: disable=protected-access
490         time.sleep(self._start_up_time)
491         self._send_command_sequence(True, self._start_sequence)
492         self._core.start_refresh()
493
494     def _finish_refresh(self) -> None:
495         # Actually refresh the display now that all pixel RAM has been updated
496         self._send_command_sequence(False, self._refesh_sequence)
497         self._refreshing = True
498         self._core.finish_refresh()
499
500     def _wait_for_busy(self) -> None:
501         if self._busy is not None:
502             while self._busy.value != self._busy_state:
503                 time.sleep(0.001)
504
505     def _clean_area(self) -> bool:
506         width = self._core.width
507         height = self._core.height
508
509         buffer = bytearray([0x77] * (width // 2))
510         self._core.begin_transaction()
511         self._core.send(
512             DISPLAY_COMMAND, self._chip_select, bytes([self._write_black_ram_command])
513         )
514         self._core.end_transaction()
515         for _ in range(height):
516             if not self._core.begin_transaction():
517                 return False
518             self._core.send(DISPLAY_DATA, self._chip_select, buffer)
519             self._core.end_transaction()
520         return True
521
522     @property
523     def rotation(self) -> int:
524         """The rotation of the display as an int in degrees"""
525         return self._core.rotation
526
527     @rotation.setter
528     def rotation(self, value: int) -> None:
529         if value % 90 != 0:
530             raise ValueError("Display rotation must be in 90 degree increments")
531         transposed = self._core.rotation in (90, 270)
532         will_transposed = value in (90, 270)
533         if transposed != will_transposed:
534             self._core.width, self._core.height = self._core.height, self._core.width
535         self._core.set_rotation(value)
536
537     @property
538     def time_to_refresh(self) -> float:
539         """Time, in fractional seconds, until the ePaper display can be refreshed."""
540         if self._core.last_refresh == 0:
541             return 0
542
543         # Refresh at seconds per frame rate
544         elapsed_time = time.monotonic() * 1000 - self._core.last_refresh
545         if elapsed_time > self._milliseconds_per_frame:
546             return 0
547         return self._milliseconds_per_frame - elapsed_time
548
549     @property
550     def busy(self) -> bool:
551         """True when the display is refreshing. This uses the ``busy_pin`` when available or the
552         ``refresh_time`` otherwise."""
553         return self._refreshing
554
555     @property
556     def width(self) -> int:
557         """Display Width"""
558         return self._core.width
559
560     @property
561     def height(self) -> int:
562         """Display Height"""
563         return self._core.height
564
565     @property
566     def bus(self) -> _DisplayBus:
567         """Current Display Bus"""
568         return self._core.get_bus()
569
570     @property
571     def root_group(self) -> Group:
572         """The root group on the epaper display.
573         If the root group is set to ``None``, no output will be shown.
574         """
575         return self._core.root_group
576
577     @root_group.setter
578     def root_group(self, new_group: Group) -> None:
579         self._core.root_group = new_group