| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | """
 | 
					
						
							|  |  |  | Main class for inkycal Project | 
					
						
							| 
									
										
										
										
											2023-06-03 16:16:07 +02:00
										 |  |  | Copyright by aceinnolab | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | """
 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 09:09:36 +00:00
										 |  |  | import glob | 
					
						
							|  |  |  | import hashlib | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2020-12-05 00:24:26 +01:00
										 |  |  | from logging.handlers import RotatingFileHandler | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  | import arrow | 
					
						
							| 
									
										
										
										
											2023-01-10 22:58:01 +01:00
										 |  |  | import numpy | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  | import asyncio | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-05 00:24:26 +01:00
										 |  |  | from inkycal.custom import * | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  | from inkycal.display import Display | 
					
						
							| 
									
										
										
										
											2020-12-05 00:24:26 +01:00
										 |  |  | from inkycal.modules.inky_image import Inkyimage as Images | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-10 22:58:01 +01:00
										 |  |  | from PIL import Image | 
					
						
							| 
									
										
										
										
											2020-11-29 14:56:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | # On the console, set a logger to show only important logs | 
					
						
							|  |  |  | # (level ERROR or higher) | 
					
						
							|  |  |  | stream_handler = logging.StreamHandler() | 
					
						
							|  |  |  | stream_handler.setLevel(logging.ERROR) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-03 21:56:01 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | if not os.path.exists(f'{top_level}/logs'): | 
					
						
							|  |  |  |     os.mkdir(f'{top_level}/logs') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Save all logs to a file, which contains more detailed output | 
					
						
							|  |  |  | logging.basicConfig( | 
					
						
							|  |  |  |     level=logging.INFO, | 
					
						
							|  |  |  |     format='%(asctime)s | %(name)s |  %(levelname)s: %(message)s', | 
					
						
							|  |  |  |     datefmt='%d-%m-%Y %H:%M:%S', | 
					
						
							|  |  |  |     handlers=[ | 
					
						
							|  |  |  |         stream_handler,  # add stream handler from above | 
					
						
							|  |  |  |         RotatingFileHandler(  # log to a file too | 
					
						
							|  |  |  |             f'{top_level}/logs/inkycal.log',  # file to log | 
					
						
							|  |  |  |             maxBytes=2097152,  # 2MB max filesize | 
					
						
							|  |  |  |             backupCount=5  # create max 5 log files | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-30 12:08:29 +01:00
										 |  |  | # Show less logging for PIL module | 
					
						
							|  |  |  | logging.getLogger("PIL").setLevel(logging.WARNING) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 02:56:04 +02:00
										 |  |  | logger = logging.getLogger(__name__) | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | # TODO: autostart -> supervisor? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  | class Inkycal: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     """Inkycal main class
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     Main class of Inkycal, test and run the main Inkycal program. | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     Args: | 
					
						
							|  |  |  |       - settings_path = str -> the full path to your settings.json file | 
					
						
							|  |  |  |         if no path is given, tries looking for settings file in /boot folder. | 
					
						
							|  |  |  |       - render = bool (True/False) -> show the image on the epaper display? | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     Attributes: | 
					
						
							|  |  |  |       - optimize = True/False. Reduce number of colours on the generated image | 
					
						
							|  |  |  |         to improve rendering on E-Papers. Set this to False for 9.7" E-Paper. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |     def __init__(self, settings_path:str or None=None, render:bool=True): | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """Initialise Inkycal""" | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         # Get the release version from setup.py | 
					
						
							|  |  |  |         with open(f'{top_level}/setup.py') as setup_file: | 
					
						
							|  |  |  |             for line in setup_file: | 
					
						
							| 
									
										
										
										
											2023-11-22 16:34:28 +01:00
										 |  |  |                 if line.startswith('__version__'): | 
					
						
							|  |  |  |                     self._release = line.split("=")[-1].replace("'", "").replace('"', "").replace(" ", "") | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |                     break | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         self.render = render | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         self.info = None | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # load settings file - throw an error if file could not be found | 
					
						
							|  |  |  |         if settings_path: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 with open(settings_path) as settings_file: | 
					
						
							|  |  |  |                     settings = json.load(settings_file) | 
					
						
							|  |  |  |                     self.settings = settings | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             except FileNotFoundError: | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |                 raise FileNotFoundError(f"No settings.json file could be found in the specified location: {settings_path}") | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         else: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 with open('/boot/settings.json') as settings_file: | 
					
						
							|  |  |  |                     settings = json.load(settings_file) | 
					
						
							|  |  |  |                     self.settings = settings | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             except FileNotFoundError: | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |                 raise SettingsFileNotFoundError | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-14 22:19:30 +01:00
										 |  |  |         self.disable_calibration = self.settings.get('disable_calibration', False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 06:29:11 +02:00
										 |  |  |         if not os.path.exists(image_folder): | 
					
						
							|  |  |  |             os.mkdir(image_folder) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Option to use epaper image optimisation, reduces colours | 
					
						
							| 
									
										
										
										
											2024-01-20 21:15:37 +01:00
										 |  |  |         self.optimize = True | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 02:37:27 +01:00
										 |  |  |         self.show_border = self.settings.get('border_around_modules', False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Load drivers if image should be rendered | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         if self.render: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             # Init Display class with model in settings file | 
					
						
							|  |  |  |             # from inkycal.display import Display | 
					
						
							|  |  |  |             self.Display = Display(settings["model"]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # check if colours can be rendered | 
					
						
							|  |  |  |             self.supports_colour = True if 'colour' in settings['model'] else False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # get calibration hours | 
					
						
							|  |  |  |             self._calibration_hours = self.settings['calibration_hours'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # init calibration state | 
					
						
							|  |  |  |             self._calibration_state = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         # Load and initialise modules specified in the settings file | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         self._module_number = 1 | 
					
						
							|  |  |  |         for module in settings['modules']: | 
					
						
							|  |  |  |             module_name = module['name'] | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 loader = f'from inkycal.modules import {module_name}' | 
					
						
							|  |  |  |                 # print(loader) | 
					
						
							|  |  |  |                 exec(loader) | 
					
						
							|  |  |  |                 setup = f'self.module_{self._module_number} = {module_name}({module})' | 
					
						
							|  |  |  |                 # print(setup) | 
					
						
							|  |  |  |                 exec(setup) | 
					
						
							|  |  |  |                 logger.info(('name : {name} size : {width}x{height} px'.format( | 
					
						
							|  |  |  |                     name=module_name, | 
					
						
							|  |  |  |                     width=module['config']['size'][0], | 
					
						
							|  |  |  |                     height=module['config']['size'][1]))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 self._module_number += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If a module was not found, print an error message | 
					
						
							|  |  |  |             except ImportError: | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |                 logger.exception(f'Could not find module: "{module}". Please try to import manually') | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # If something unexpected happened, show the error message | 
					
						
							|  |  |  |             except Exception as e: | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |                 logger.exception(f"Exception: {traceback.format_exc()}.") | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Path to store images | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  |         self.image_folder = image_folder | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 09:09:36 +00:00
										 |  |  |         # Remove old hashes | 
					
						
							|  |  |  |         self._remove_hashes(self.image_folder) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Give an OK message | 
					
						
							|  |  |  |         print('loaded inkycal') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def countdown(self, interval_mins=None): | 
					
						
							|  |  |  |         """Returns the remaining time in seconds until next display update""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Check if empty, if empty, use value from settings file | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         if interval_mins is None: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             interval_mins = self.settings["update_interval"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Find out at which minutes the update should happen | 
					
						
							|  |  |  |         now = arrow.now() | 
					
						
							|  |  |  |         update_timings = [(60 - int(interval_mins) * updates) for updates in | 
					
						
							|  |  |  |                           range(60 // int(interval_mins))][::-1] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         # Calculate time in minutes until next update | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         minutes = [_ for _ in update_timings if _ >= now.minute][0] - now.minute | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         # Print the remaining time in minutes until next update | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         print(f'{minutes} minutes left until next refresh') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Calculate time in seconds until next update | 
					
						
							|  |  |  |         remaining_time = minutes * 60 + (60 - now.second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Return seconds until next update | 
					
						
							|  |  |  |         return remaining_time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test(self): | 
					
						
							|  |  |  |         """Tests if Inkycal can run without issues.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Attempts to import module names from settings file. Loads the config | 
					
						
							|  |  |  |         for each module and initializes the module. Tries to run the module and | 
					
						
							|  |  |  |         checks if the images could be generated correctly. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Generated images can be found in the /images folder of Inkycal. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |         logger.info(f"Inkycal version: v{self._release}") | 
					
						
							|  |  |  |         logger.info(f'Selected E-paper display: {self.settings["model"]}') | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # store module numbers in here | 
					
						
							|  |  |  |         errors = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # short info for info-section | 
					
						
							|  |  |  |         self.info = f"{arrow.now().format('D MMM @ HH:mm')}  " | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for number in range(1, self._module_number): | 
					
						
							|  |  |  |             name = eval(f"self.module_{number}.name") | 
					
						
							|  |  |  |             module = eval(f'self.module_{number}') | 
					
						
							|  |  |  |             print(f'generating image(s) for {name}...', end="") | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 black, colour = module.generate_image() | 
					
						
							| 
									
										
										
										
											2023-11-24 02:37:27 +01:00
										 |  |  |                 if self.show_border: | 
					
						
							|  |  |  |                     draw_border_2(im=black, xy=(1, 1), size=(black.width - 2, black.height - 2), radius=5) | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  |                 black.save(f"{self.image_folder}module{number}_black.png", "PNG") | 
					
						
							|  |  |  |                 colour.save(f"{self.image_folder}module{number}_colour.png", "PNG") | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |                 print("OK!") | 
					
						
							|  |  |  |             except Exception: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 errors.append(number) | 
					
						
							|  |  |  |                 self.info += f"module {number}: Error!  " | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |                 logger.exception("Error!") | 
					
						
							|  |  |  |                 logger.exception(f"Exception: {traceback.format_exc()}.") | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         if errors: | 
					
						
							| 
									
										
										
										
											2024-01-17 21:29:24 +00:00
										 |  |  |             logger.error('Error/s in modules:', *errors) | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         del errors | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         self._assemble() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 09:09:36 +00:00
										 |  |  |     def _image_hash(self, _in): | 
					
						
							|  |  |  |         """Create a md5sum of a path or a bytes stream.""" | 
					
						
							|  |  |  |         if not isinstance(_in, str): | 
					
						
							|  |  |  |             image_bytes = _in.tobytes() | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 with open(_in) as i: | 
					
						
							|  |  |  |                     return i.read() | 
					
						
							|  |  |  |             except FileNotFoundError: | 
					
						
							|  |  |  |                 image_bytes = None | 
					
						
							|  |  |  |         return hashlib.md5(image_bytes).hexdigest() if image_bytes else "" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _remove_hashes(self, basepath): | 
					
						
							|  |  |  |         for _file in glob.glob(f"{basepath}/*.hash"): | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 os.remove(_file) | 
					
						
							|  |  |  |             except: | 
					
						
							|  |  |  |                 pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _write_image_hash(self, path, _in): | 
					
						
							|  |  |  |         """Write hash to a file.""" | 
					
						
							|  |  |  |         with open(path, "w") as o: | 
					
						
							|  |  |  |             o.write(self._image_hash(_in)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _needs_image_update(self, _list): | 
					
						
							|  |  |  |         """Check if any image has been updated or not.
 | 
					
						
							|  |  |  |         Input a list of tuples(str, image)."""
 | 
					
						
							|  |  |  |         res = False | 
					
						
							|  |  |  |         for item in _list: | 
					
						
							|  |  |  |             _a = self._image_hash(item[0]) | 
					
						
							|  |  |  |             _b = self._image_hash(item[1]) | 
					
						
							|  |  |  |             print("{f1}:{h1} -> {h2}".format(f1=item[0], h1=_a, h2=_b)) | 
					
						
							|  |  |  |             if _a != _b: | 
					
						
							|  |  |  |                 res = True | 
					
						
							|  |  |  |                 self._write_image_hash(item[0], item[1]) | 
					
						
							|  |  |  |             print("Refresh needed: {a}".format(a=res)) | 
					
						
							|  |  |  |         return res | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |     async def run(self): | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """Runs main program in nonstop mode.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         Uses an infinity loop to run Inkycal nonstop. Inkycal generates the image | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         from all modules, assembles them in one image, refreshed the E-Paper and | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         then sleeps until the next scheduled update. | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get the time of initial run | 
					
						
							|  |  |  |         runtime = arrow.now() | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Function to flip images upside down | 
					
						
							|  |  |  |         upside_down = lambda image: image.rotate(180, expand=True) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Count the number of times without any errors | 
					
						
							|  |  |  |         counter = 0 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         print(f'Inkycal version: v{self._release}') | 
					
						
							|  |  |  |         print(f'Selected E-paper display: {self.settings["model"]}') | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         while True: | 
					
						
							|  |  |  |             current_time = arrow.now(tz=get_system_tz()) | 
					
						
							|  |  |  |             print(f"Date: {current_time.format('D MMM YY')} | " | 
					
						
							|  |  |  |                   f"Time: {current_time.format('HH:mm')}") | 
					
						
							|  |  |  |             print('Generating images for all modules...', end='') | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             errors = []  # store module numbers in here | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             # short info for info-section | 
					
						
							| 
									
										
										
										
											2022-08-23 09:09:36 +00:00
										 |  |  |             if not self.settings.get('image_hash', False): | 
					
						
							|  |  |  |                 self.info = f"{current_time.format('D MMM @ HH:mm')}  " | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.info = "" | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             for number in range(1, self._module_number): | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |                 # name = eval(f"self.module_{number}.name") | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 module = eval(f'self.module_{number}') | 
					
						
							| 
									
										
										
										
											2020-11-25 14:24:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 try: | 
					
						
							|  |  |  |                     black, colour = module.generate_image() | 
					
						
							| 
									
										
										
										
											2023-11-24 02:37:27 +01:00
										 |  |  |                     if self.show_border: | 
					
						
							|  |  |  |                         draw_border_2(im=black, xy=(1, 1), size=(black.width - 2, black.height - 2), radius=5) | 
					
						
							| 
									
										
										
										
											2022-04-14 06:12:06 +02:00
										 |  |  |                     black.save(f"{self.image_folder}module{number}_black.png", "PNG") | 
					
						
							|  |  |  |                     colour.save(f"{self.image_folder}module{number}_colour.png", "PNG") | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                     self.info += f"module {number}: OK  " | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |                 except Exception as e: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                     errors.append(number) | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |                     self.info += f"module {number}: Error!  " | 
					
						
							|  |  |  |                     logger.exception("Error!") | 
					
						
							|  |  |  |                     logger.exception(f"Exception: {traceback.format_exc()}.") | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             if errors: | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |                 logger.error("Error/s in modules:", *errors) | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 counter = 0 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 counter += 1 | 
					
						
							| 
									
										
										
										
											2024-01-24 21:52:24 +01:00
										 |  |  |                 logger.info("successful") | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             del errors | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             # Assemble image from each module - add info section if specified | 
					
						
							|  |  |  |             self._assemble() | 
					
						
							| 
									
										
										
										
											2020-11-25 14:24:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             # Check if image should be rendered | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |             if self.render: | 
					
						
							|  |  |  |                 display = self.Display | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 self._calibration_check() | 
					
						
							| 
									
										
										
										
											2022-08-23 09:09:36 +00:00
										 |  |  |                 if self._calibration_state: | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |                     # after calibration, we have to forcefully rewrite the screen | 
					
						
							| 
									
										
										
										
											2022-08-23 09:09:36 +00:00
										 |  |  |                     self._remove_hashes(self.image_folder) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |                 if self.supports_colour: | 
					
						
							| 
									
										
										
										
											2022-04-14 06:12:06 +02:00
										 |  |  |                     im_black = Image.open(f"{self.image_folder}canvas.png") | 
					
						
							|  |  |  |                     im_colour = Image.open(f"{self.image_folder}canvas_colour.png") | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                     # Flip the image by 180° if required | 
					
						
							|  |  |  |                     if self.settings['orientation'] == 180: | 
					
						
							|  |  |  |                         im_black = upside_down(im_black) | 
					
						
							|  |  |  |                         im_colour = upside_down(im_colour) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                     # render the image on the display | 
					
						
							| 
									
										
										
										
											2022-08-23 09:09:36 +00:00
										 |  |  |                     if not self.settings.get('image_hash', False) or self._needs_image_update([ | 
					
						
							|  |  |  |                       (f"{self.image_folder}/canvas.png.hash", im_black), | 
					
						
							|  |  |  |                       (f"{self.image_folder}/canvas_colour.png.hash", im_colour) | 
					
						
							|  |  |  |                     ]): | 
					
						
							|  |  |  |                         # render the image on the display | 
					
						
							|  |  |  |                         display.render(im_black, im_colour) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Part for black-white ePapers | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |                 elif not self.supports_colour: | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                     im_black = self._merge_bands() | 
					
						
							| 
									
										
										
										
											2020-11-30 12:08:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                     # Flip the image by 180° if required | 
					
						
							|  |  |  |                     if self.settings['orientation'] == 180: | 
					
						
							|  |  |  |                         im_black = upside_down(im_black) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 09:09:36 +00:00
										 |  |  |                     if not self.settings.get('image_hash', False) or self._needs_image_update([ | 
					
						
							|  |  |  |                       (f"{self.image_folder}/canvas.png.hash", im_black), | 
					
						
							|  |  |  |                     ]): | 
					
						
							|  |  |  |                         display.render(im_black) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             print(f'\nNo errors since {counter} display updates \n' | 
					
						
							|  |  |  |                   f'program started {runtime.humanize()}') | 
					
						
							| 
									
										
										
										
											2020-11-23 22:36:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             sleep_time = self.countdown() | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |             await asyncio.sleep(sleep_time) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |     @staticmethod | 
					
						
							|  |  |  |     def _merge_bands(): | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """Merges black and coloured bands for black-white ePapers
 | 
					
						
							|  |  |  |         returns the merged image | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  |         im1_path, im2_path = image_folder + 'canvas.png', image_folder + 'canvas_colour.png' | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # If there is an image for black and colour, merge them | 
					
						
							|  |  |  |         if os.path.exists(im1_path) and os.path.exists(im2_path): | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             im1 = Image.open(im1_path).convert('RGBA') | 
					
						
							|  |  |  |             im2 = Image.open(im2_path).convert('RGBA') | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             im1 = Images.merge(im1, im2) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # If there is no image for the coloured-band, return the bw-image | 
					
						
							|  |  |  |         elif os.path.exists(im1_path) and not os.path.exists(im2_path): | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |             im1 = Image.open(im1_path).convert('RGBA') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise FileNotFoundError("Inkycal cannot find images to merge") | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         return im1 | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     def _assemble(self): | 
					
						
							|  |  |  |         """Assembles all sub-images to a single image""" | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Create 2 blank images with the same resolution as the display | 
					
						
							|  |  |  |         width, height = Display.get_display_size(self.settings["model"]) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Since Inkycal runs in vertical mode, switch the height and width | 
					
						
							|  |  |  |         width, height = height, width | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         im_black = Image.new('RGB', (width, height), color='white') | 
					
						
							|  |  |  |         im_colour = Image.new('RGB', (width, height), color='white') | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Set cursor for y-axis | 
					
						
							|  |  |  |         im1_cursor = 0 | 
					
						
							|  |  |  |         im2_cursor = 0 | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         for number in range(1, self._module_number): | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             # get the path of the current module's generated images | 
					
						
							| 
									
										
										
										
											2022-04-14 06:12:06 +02:00
										 |  |  |             im1_path = f"{self.image_folder}module{number}_black.png" | 
					
						
							|  |  |  |             im2_path = f"{self.image_folder}module{number}_colour.png" | 
					
						
							| 
									
										
										
										
											2020-06-22 15:52:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             # Check if there is an image for the black band | 
					
						
							|  |  |  |             if os.path.exists(im1_path): | 
					
						
							| 
									
										
										
										
											2020-06-22 15:52:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Get actual size of image | 
					
						
							|  |  |  |                 im1 = Image.open(im1_path).convert('RGBA') | 
					
						
							|  |  |  |                 im1_size = im1.size | 
					
						
							| 
									
										
										
										
											2020-06-22 15:52:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Get the size of the section | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |                 section_size = [i for i in self.settings['modules'] if i['position'] == number][0]['config']['size'] | 
					
						
							| 
									
										
										
										
											2020-06-22 15:52:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Calculate coordinates to center the image | 
					
						
							|  |  |  |                 x = int((section_size[0] - im1_size[0]) / 2) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # If this is the first module, use the y-offset | 
					
						
							|  |  |  |                 if im1_cursor == 0: | 
					
						
							|  |  |  |                     y = int((section_size[1] - im1_size[1]) / 2) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     y = im1_cursor + int((section_size[1] - im1_size[1]) / 2) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # center the image in the section space | 
					
						
							|  |  |  |                 im_black.paste(im1, (x, y), im1) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Shift the y-axis cursor at the beginning of next section | 
					
						
							|  |  |  |                 im1_cursor += section_size[1] | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             # Check if there is an image for the coloured band | 
					
						
							|  |  |  |             if os.path.exists(im2_path): | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Get actual size of image | 
					
						
							|  |  |  |                 im2 = Image.open(im2_path).convert('RGBA') | 
					
						
							|  |  |  |                 im2_size = im2.size | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Get the size of the section | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |                 section_size = [i for i in self.settings['modules'] if i['position'] == number][0]['config']['size'] | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Calculate coordinates to center the image | 
					
						
							|  |  |  |                 x = int((section_size[0] - im2_size[0]) / 2) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # If this is the first module, use the y-offset | 
					
						
							|  |  |  |                 if im2_cursor == 0: | 
					
						
							|  |  |  |                     y = int((section_size[1] - im2_size[1]) / 2) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     y = im2_cursor + int((section_size[1] - im2_size[1]) / 2) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # center the image in the section space | 
					
						
							|  |  |  |                 im_colour.paste(im2, (x, y), im2) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 # Shift the y-axis cursor at the beginning of next section | 
					
						
							|  |  |  |                 im2_cursor += section_size[1] | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Add info-section if specified -- | 
					
						
							| 
									
										
										
										
											2020-06-22 15:52:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Calculate the max. fontsize for info-section | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         if self.settings['info_section']: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             info_height = self.settings["info_section_height"] | 
					
						
							|  |  |  |             info_width = width | 
					
						
							|  |  |  |             font = self.font = ImageFont.truetype( | 
					
						
							|  |  |  |                 fonts['NotoSansUI-Regular'], size=14) | 
					
						
							| 
									
										
										
										
											2020-06-22 15:52:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             info_x = im_black.size[1] - info_height | 
					
						
							|  |  |  |             write(im_black, (0, info_x), (info_width, info_height), | 
					
						
							|  |  |  |                   self.info, font=font) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # optimize the image by mapping colours to pure black and white | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         if self.optimize: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             im_black = self._optimize_im(im_black) | 
					
						
							|  |  |  |             im_colour = self._optimize_im(im_colour) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 06:12:06 +02:00
										 |  |  |         im_black.save(self.image_folder + 'canvas.png', 'PNG') | 
					
						
							|  |  |  |         im_colour.save(self.image_folder + 'canvas_colour.png', 'PNG') | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  |         # Additionally, combine the two images with color | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         def clear_white(img): | 
					
						
							|  |  |  |             """Replace all white pixels from image with transparent pixels
 | 
					
						
							|  |  |  |             """
 | 
					
						
							|  |  |  |             x = numpy.asarray(img.convert('RGBA')).copy() | 
					
						
							|  |  |  |             x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(numpy.uint8) | 
					
						
							|  |  |  |             return Image.fromarray(x) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  |         # Additionally, combine the two images with color | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         def black_to_colour(img): | 
					
						
							|  |  |  |             """Replace all black pixels from image with red pixels
 | 
					
						
							|  |  |  |             """
 | 
					
						
							|  |  |  |             buffer = numpy.array(img.convert('RGB')) | 
					
						
							|  |  |  |             red, green = buffer[:, :, 0], buffer[:, :, 1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             threshold = 220 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # non-white -> red | 
					
						
							|  |  |  |             buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [255, 0, 0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return Image.fromarray(buffer) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Save full-screen images as well | 
					
						
							|  |  |  |         im_black = clear_white(im_black) | 
					
						
							|  |  |  |         im_colour = black_to_colour(im_colour) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         im_colour.paste(im_black, (0, 0), im_black) | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  |         im_colour.save(image_folder + 'full-screen.png', 'PNG') | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @staticmethod | 
					
						
							|  |  |  |     def _optimize_im(image, threshold=220): | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """Optimize the image for rendering on ePaper displays""" | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 21:10:50 +01:00
										 |  |  |         buffer = numpy.array(image.convert('RGB')) | 
					
						
							|  |  |  |         red, green = buffer[:, :, 0], buffer[:, :, 1] | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # grey->black | 
					
						
							| 
									
										
										
										
											2024-01-15 21:10:50 +01:00
										 |  |  |         buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0, 0, 0] | 
					
						
							|  |  |  |         image = Image.fromarray(buffer) | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         return image | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-14 00:01:41 +01:00
										 |  |  |     def calibrate(self, cycles=3): | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         """Calibrate the E-Paper display
 | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Uses the Display class to calibrate the display with the default of 3 | 
					
						
							|  |  |  |         cycles. After a refresh cycle, a new image is generated and shown. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-14 00:01:41 +01:00
										 |  |  |         self.Display.calibrate(cycles=cycles) | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     def _calibration_check(self): | 
					
						
							| 
									
										
										
										
											2023-11-22 12:45:07 +01:00
										 |  |  |         """Calibration scheduler
 | 
					
						
							| 
									
										
										
										
											2023-12-14 22:19:30 +01:00
										 |  |  |         uses calibration hours from settings file to check if calibration is due. | 
					
						
							|  |  |  |         If no calibration hours are set, calibration is skipped."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Check if calibration hours are not set or the list is empty | 
					
						
							|  |  |  |         if not self._calibration_hours: | 
					
						
							|  |  |  |             print("No calibration hours set. Skipping calibration.") | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         now = arrow.now() | 
					
						
							| 
									
										
										
										
											2023-12-14 22:19:30 +01:00
										 |  |  |         if now.hour in self._calibration_hours and not self._calibration_state: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             self.calibrate() | 
					
						
							|  |  |  |             self._calibration_state = True | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             self._calibration_state = False | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     print(f'running inkycal main in standalone/debug mode') |