| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | """
 | 
					
						
							| 
									
										
										
										
											2022-10-02 00:49:27 +02:00
										 |  |  | Inkycal ePaper driving functions | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  | Copyright by aceisace | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | """
 | 
					
						
							| 
									
										
										
										
											2023-09-11 00:59:39 +08:00
										 |  |  | import logging | 
					
						
							| 
									
										
										
										
											2023-12-14 01:51:10 +01:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  | import traceback | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | from importlib import import_module | 
					
						
							| 
									
										
										
										
											2023-12-14 01:51:10 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | import PIL | 
					
						
							| 
									
										
										
										
											2023-05-20 03:09:15 +02:00
										 |  |  | from PIL import Image | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  | from inkycal.custom import top_level | 
					
						
							| 
									
										
										
										
											2023-12-14 01:51:10 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-11 00:59:39 +08:00
										 |  |  | def import_driver(model): | 
					
						
							|  |  |  |     return import_module(f'inkycal.display.drivers.{model}') | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-14 01:51:10 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | class Display: | 
					
						
							|  |  |  |     """Display class for inkycal
 | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     Creates an instance of the driver for the selected E-Paper model and allows | 
					
						
							|  |  |  |     rendering images and calibrating the E-Paper display | 
					
						
							| 
									
										
										
										
											2020-12-05 00:18:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 03:09:15 +02:00
										 |  |  |     Args: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |       - epaper_model: The name of your E-Paper model. | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-12-05 00:18:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     def __init__(self, epaper_model): | 
					
						
							|  |  |  |         """Load the drivers for this epaper model""" | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |         if 'colour' in epaper_model: | 
					
						
							|  |  |  |             self.supports_colour = True | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.supports_colour = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2023-09-11 00:59:39 +08:00
										 |  |  |             driver = import_driver(epaper_model) | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             self._epaper = driver.EPD() | 
					
						
							|  |  |  |             self.model_name = epaper_model | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         except ImportError: | 
					
						
							|  |  |  |             raise Exception('This module is not supported. Check your spellings?') | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         except FileNotFoundError: | 
					
						
							|  |  |  |             raise Exception('SPI could not be found. Please check if SPI is enabled') | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-14 01:51:10 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test(self) -> None: | 
					
						
							|  |  |  |         """Test the display by showing a test image""" | 
					
						
							|  |  |  |         # TODO implement test image | 
					
						
							|  |  |  |         raise NotImplementedError("Devs were too lazy again, sorry, please try again later") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-19 21:46:21 +01:00
										 |  |  |     def render(self, im_black: PIL.Image, im_colour: PIL.Image or None=None) -> None: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """Renders an image on the selected E-Paper display.
 | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Initlializes the E-Paper display, sends image data and executes command | 
					
						
							|  |  |  |         to update the display. | 
					
						
							| 
									
										
										
										
											2020-11-29 23:43:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Args: | 
					
						
							|  |  |  |           - im_black: The image for the black-pixels. Anything in this image that is | 
					
						
							|  |  |  |             black is rendered as black on the display. This is required and ideally | 
					
						
							|  |  |  |             should be a black-white image. | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |           - im_colour: For E-Paper displays supporting colour, a separate image, | 
					
						
							|  |  |  |             ideally black-white is required for the coloured pixels. Anything that is | 
					
						
							|  |  |  |             black in this image will show up as either red/yellow. | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Rendering an image for black-white E-Paper displays: | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 03:09:15 +02:00
										 |  |  |         >>> sample_image = Image.open('path/to/file.png') | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         >>> display = Display('my_black_white_display') | 
					
						
							|  |  |  |         >>> display.render(sample_image) | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Rendering black-white on coloured E-Paper displays: | 
					
						
							| 
									
										
										
										
											2023-05-20 03:09:15 +02:00
										 |  |  |         >>> sample_image = Image.open('path/to/file.png') | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         >>> display = Display('my_coloured_display') | 
					
						
							|  |  |  |         >>> display.render(sample_image, sample_image) | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Rendering coloured image where 2 images are available: | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 03:09:15 +02:00
										 |  |  |         >>> black_image = Image.open('path/to/file.png') # black pixels | 
					
						
							|  |  |  |         >>> colour_image = Image.open('path/to/file.png') # coloured pixels | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         >>> display = Display('my_coloured_display') | 
					
						
							|  |  |  |         >>> display.render(black_image, colour_image) | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         epaper = self._epaper | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         if self.supports_colour: | 
					
						
							|  |  |  |             if not im_colour: | 
					
						
							|  |  |  |                 raise Exception('im_colour is required for coloured epaper displays') | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |             print('Initialising..', end='') | 
					
						
							|  |  |  |             epaper.init() | 
					
						
							|  |  |  |             print('Updating display......', end='') | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |             epaper.display(epaper.getbuffer(im_black), epaper.getbuffer(im_colour)) | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |             print('Done') | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |             print('Initialising..', end='') | 
					
						
							|  |  |  |             epaper.init() | 
					
						
							|  |  |  |             print('Updating display......', end='') | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |             epaper.display(epaper.getbuffer(im_black)) | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |             print('Done') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         print('Sending E-Paper to deep sleep...', end='') | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         epaper.sleep() | 
					
						
							|  |  |  |         print('Done') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def calibrate(self, cycles=3): | 
					
						
							|  |  |  |         """Calibrates the display to retain crisp colours
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Flushes the selected display several times with it's supported colours, | 
					
						
							|  |  |  |         removing any previous effects of ghosting. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Args: | 
					
						
							|  |  |  |           - cycles: -> int. The number of times to flush the display with it's | 
					
						
							|  |  |  |             supported colours. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         It's recommended to calibrate the display after every 6 display updates | 
					
						
							|  |  |  |         for best results. For black-white only displays, calibration is less | 
					
						
							|  |  |  |         critical, but not calibrating regularly results in grey-ish text. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Please note that calibration takes a while to complete. 3 cycles may | 
					
						
							|  |  |  |         take 10 minutes on black-white E-Papers while it takes 20 minutes on coloured | 
					
						
							|  |  |  |         E-Paper displays. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         epaper = self._epaper | 
					
						
							|  |  |  |         epaper.init() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |         display_size = self.get_display_size(self.model_name) | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         white = Image.new('1', display_size, 'white') | 
					
						
							|  |  |  |         black = Image.new('1', display_size, 'black') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         print('----------Started calibration of ePaper display----------') | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |         if self.supports_colour: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             for _ in range(cycles): | 
					
						
							|  |  |  |                 print('Calibrating...', end=' ') | 
					
						
							|  |  |  |                 print('black...', end=' ') | 
					
						
							|  |  |  |                 epaper.display(epaper.getbuffer(black), epaper.getbuffer(white)) | 
					
						
							|  |  |  |                 print('colour...', end=' ') | 
					
						
							|  |  |  |                 epaper.display(epaper.getbuffer(white), epaper.getbuffer(black)) | 
					
						
							|  |  |  |                 print('white...') | 
					
						
							|  |  |  |                 epaper.display(epaper.getbuffer(white), epaper.getbuffer(white)) | 
					
						
							|  |  |  |                 print(f'Cycle {_ + 1} of {cycles} complete') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |         if not self.supports_colour: | 
					
						
							|  |  |  |             for _ in range(cycles): | 
					
						
							|  |  |  |                 print('Calibrating...', end=' ') | 
					
						
							|  |  |  |                 print('black...', end=' ') | 
					
						
							|  |  |  |                 epaper.display(epaper.getbuffer(black)) | 
					
						
							|  |  |  |                 print('white...') | 
					
						
							|  |  |  |                 epaper.display(epaper.getbuffer(white)), | 
					
						
							|  |  |  |                 print(f'Cycle {_ + 1} of {cycles} complete') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             print('-----------Calibration complete----------') | 
					
						
							|  |  |  |             epaper.sleep() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2023-12-07 18:40:24 +01:00
										 |  |  |     def get_display_size(cls, model_name) -> (int, int): | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """Returns the size of the display as a tuple -> (width, height)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |         Looks inside "drivers" folder for the given model name, then returns it's | 
					
						
							|  |  |  |         size. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Args: | 
					
						
							|  |  |  |           - model_name: str -> The name of the E-Paper display to get it's size. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Returns: | 
					
						
							|  |  |  |           (width, height) ->tuple, showing the size of the display | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         You can use this function directly without creating the Display class: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         >>> Display.get_display_size('model_name') | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2023-09-11 00:59:39 +08:00
										 |  |  |         try: | 
					
						
							|  |  |  |             driver = import_driver(model_name) | 
					
						
							|  |  |  |             return driver.EPD_WIDTH, driver.EPD_HEIGHT | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         except: | 
					
						
							| 
									
										
										
										
											2023-09-11 00:59:39 +08:00
										 |  |  |             logging.error(f'Failed to load driver for ${model_name}. Check spelling?') | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |             print(traceback.format_exc()) | 
					
						
							|  |  |  |             raise AssertionError("Could not import driver") | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2023-05-20 03:09:15 +02:00
										 |  |  |     def get_display_names(cls) -> list: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """Prints all supported E-Paper models.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Fetches all filenames in driver folder and prints them on the console. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Returns: | 
					
						
							|  |  |  |           Printed version of all supported Displays. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Use one of the models to intilialize the Display class in order to gain | 
					
						
							|  |  |  |         access to the E-Paper. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         You can use this function directly without creating the Display class: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         >>> Display.get_display_names() | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2023-06-07 12:32:06 +02:00
										 |  |  |         driver_files = top_level + '/inkycal/display/drivers/' | 
					
						
							|  |  |  |         drivers = [i for i in os.listdir(driver_files) if i.endswith(".py") and not i.startswith("__") and "_" in i] | 
					
						
							| 
									
										
										
										
											2023-05-17 10:12:52 +02:00
										 |  |  |         return drivers | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-05 00:18:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | if __name__ == '__main__': | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     print("Running Display class in standalone mode") |