| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | #!python3 | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | # -*- coding: utf-8 -*- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | Main class for inkycal Project | 
					
						
							|  |  |  | Copyright by aceisace | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							| 
									
										
										
										
											2022-04-14 06:07:14 +02:00
										 |  |  | import traceback | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 00:23:42 +01:00
										 |  |  | on_rtd = os.environ.get('READTHEDOCS') == 'True' | 
					
						
							|  |  |  | if on_rtd: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     logging.basicConfig( | 
					
						
							|  |  |  |         level=logging.INFO, | 
					
						
							|  |  |  |         format='%(asctime)s | %(name)s |  %(levelname)s: %(message)s', | 
					
						
							|  |  |  |         datefmt='%d-%m-%Y %H:%M:%S', | 
					
						
							|  |  |  |         handlers=[stream_handler]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 00:23:42 +01:00
										 |  |  | else: | 
					
						
							| 
									
										
										
										
											2023-01-10 22:58:01 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if not os.path.exists(f'{top_level}/logs'): | 
					
						
							|  |  |  |         os.mkdir(f'{top_level}/logs') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     # 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-12-07 00:23:42 +01:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         ] | 
					
						
							| 
									
										
										
										
											2020-12-07 00:23:42 +01:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     def __init__(self, settings_path=None, render=True): | 
					
						
							|  |  |  |         """Initialise Inkycal""" | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 17:47:37 +02:00
										 |  |  |         self._release = '2.0.2' | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Check if render was set correctly | 
					
						
							|  |  |  |         if render not in [True, False]: | 
					
						
							|  |  |  |             raise Exception(f'render must be True or False, not "{render}"') | 
					
						
							|  |  |  |         self.render = render | 
					
						
							| 
									
										
										
										
											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: | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |                 raise SettingsFileNotFoundError | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |         self.optimize = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Load and intialize modules specified in the settings file | 
					
						
							|  |  |  |         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: | 
					
						
							|  |  |  |                 print(f'Could not find module: "{module}". Please try to import manually') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If something unexpected happened, show the error message | 
					
						
							|  |  |  |             except Exception as e: | 
					
						
							|  |  |  |                 print(str(e)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Calculate time in mins until next update | 
					
						
							|  |  |  |         minutes = [_ for _ in update_timings if _ >= now.minute][0] - now.minute | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Print the remaining time in mins until next update | 
					
						
							|  |  |  |         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. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         print(f'Inkycal version: v{self._release}') | 
					
						
							|  |  |  |         print(f'Selected E-paper display: {self.settings["model"]}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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() | 
					
						
							| 
									
										
										
										
											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") | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 print('OK!') | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |             except: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 errors.append(number) | 
					
						
							|  |  |  |                 self.info += f"module {number}: Error!  " | 
					
						
							|  |  |  |                 print('Error!') | 
					
						
							|  |  |  |                 print(traceback.format_exc()) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         if errors: | 
					
						
							|  |  |  |             print('Error/s in modules:', *errors) | 
					
						
							|  |  |  |         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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     def run(self): | 
					
						
							|  |  |  |         """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 | 
					
						
							|  |  |  |         then sleeps until the next sheduled update. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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() | 
					
						
							| 
									
										
										
										
											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  " | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |                 except: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                     errors.append(number) | 
					
						
							|  |  |  |                     print('error!') | 
					
						
							|  |  |  |                     print(traceback.format_exc()) | 
					
						
							|  |  |  |                     self.info += f"module {number}: error!  " | 
					
						
							|  |  |  |                     logger.exception(f'Exception in module {number}') | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             if errors: | 
					
						
							|  |  |  |                 print('error/s in modules:', *errors) | 
					
						
							|  |  |  |                 counter = 0 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 counter += 1 | 
					
						
							|  |  |  |                 print('successful') | 
					
						
							|  |  |  |             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: | 
					
						
							|  |  |  |                     # after calibration we have to forcefully rewrite the screen | 
					
						
							|  |  |  |                     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() | 
					
						
							|  |  |  |             time.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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02: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 | 
					
						
							|  |  |  |         buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0, 0, 0] | 
					
						
							|  |  |  |         image = Image.fromarray(buffer) | 
					
						
							|  |  |  |         return image | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     def calibrate(self): | 
					
						
							|  |  |  |         """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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         self.Display.calibrate() | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     def _calibration_check(self): | 
					
						
							|  |  |  |         """Calibration sheduler
 | 
					
						
							|  |  |  |         uses calibration hours from settings file to check if calibration is due"""
 | 
					
						
							|  |  |  |         now = arrow.now() | 
					
						
							|  |  |  |         # print('hour:', now.hour, 'hours:', self._calibration_hours) | 
					
						
							|  |  |  |         # print('state:', self._calibration_state) | 
					
						
							|  |  |  |         if now.hour in self._calibration_hours and self._calibration_state == False: | 
					
						
							|  |  |  |             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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def add_module(cls, filepath): | 
					
						
							|  |  |  |         """registers a third party module for inkycal.
 | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Uses the full filepath of the third party module to check if it is inside | 
					
						
							|  |  |  |         the correct folder, then checks if it's an inkycal module. Lastly, the | 
					
						
							|  |  |  |         init files in /inkycal and /inkycal/modules are updated to allow using | 
					
						
							|  |  |  |         the new module. | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Args: | 
					
						
							|  |  |  |           - filepath: The full filepath of the third party module. Modules should be | 
					
						
							|  |  |  |             in Inkycal/inkycal/modules. | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Usage: | 
					
						
							|  |  |  |           - download a third-party module. The exact link is provided by the | 
					
						
							|  |  |  |             developer of that module and starts with | 
					
						
							|  |  |  |             `https://raw.githubusercontent.com/...` | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             enter the following in bash to download a module:: | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |               $ cd Inkycal/inkycal/modules #navigate to modules folder in inkycal | 
					
						
							|  |  |  |               $ wget https://raw.githubusercontent.com/...     #download the module | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             then register it with this function:: | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |               >>> from inkycal import Inkycal | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |               >>> Inkycal.add_module('/full/path/to/the/module/in/inkycal/modules.py') | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         module_folder = top_level + '/inkycal/modules' | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         if module_folder in filepath: | 
					
						
							|  |  |  |             filename = filepath.split('.py')[0].split('/')[-1] | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |             # Extract name of class from given module and validate if it's an inkycal | 
					
						
							|  |  |  |             # module | 
					
						
							|  |  |  |             with open(filepath, mode='r') as module: | 
					
						
							|  |  |  |                 module_content = module.read().splitlines() | 
					
						
							| 
									
										
										
										
											2020-11-24 00:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |             for line in module_content: | 
					
						
							|  |  |  |                 if '(inkycal_module):' in line: | 
					
						
							|  |  |  |                     classname = line.split(' ')[-1].split('(')[0] | 
					
						
							|  |  |  |                     break | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |             if not classname: | 
					
						
							|  |  |  |                 raise TypeError("your module doesn't seem to be a correct inkycal module.." | 
					
						
							|  |  |  |                                 "Please check your module again.") | 
					
						
							| 
									
										
										
										
											2020-11-21 16:31:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |             # Check if filename or classname exists in init of module folder | 
					
						
							|  |  |  |             with open(module_folder + '/__init__.py', mode='r') as file: | 
					
						
							|  |  |  |                 module_init = file.read().splitlines() | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |             print('checking module init file..') | 
					
						
							|  |  |  |             for line in module_init: | 
					
						
							|  |  |  |                 if filename in line: | 
					
						
							|  |  |  |                     raise Exception( | 
					
						
							|  |  |  |                         "A module with this filename already exists! \n" | 
					
						
							|  |  |  |                         "Please consider renaming your module and try again." | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 if classname in line: | 
					
						
							|  |  |  |                     raise Exception( | 
					
						
							|  |  |  |                         "A module with this classname already exists! \n" | 
					
						
							|  |  |  |                         "Please consider renaming your class and try again." | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |             print('OK!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Check if filename or classname exists in init of inkycal folder | 
					
						
							|  |  |  |             with open(top_level + '/inkycal/__init__.py', mode='r') as file: | 
					
						
							|  |  |  |                 inkycal_init = file.read().splitlines() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             print('checking inkycal init file..') | 
					
						
							|  |  |  |             for line in inkycal_init: | 
					
						
							|  |  |  |                 if filename in line: | 
					
						
							|  |  |  |                     raise Exception( | 
					
						
							|  |  |  |                         "A module with this filename already exists! \n" | 
					
						
							|  |  |  |                         "Please consider renaming your module and try again." | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 if classname in line: | 
					
						
							|  |  |  |                     raise Exception( | 
					
						
							|  |  |  |                         "A module with this classname already exists! \n" | 
					
						
							|  |  |  |                         "Please consider renaming your class and try again." | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |             print('OK') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If all checks have passed, add the module in the module init file | 
					
						
							|  |  |  |             with open(module_folder + '/__init__.py', mode='a') as file: | 
					
						
							|  |  |  |                 file.write(f'from .{filename} import {classname} # Added by module adder') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If all checks have passed, add the module in the inkycal init file | 
					
						
							|  |  |  |             with open(top_level + '/inkycal/__init__.py', mode='a') as file: | 
					
						
							|  |  |  |                 file.write(f'import inkycal.modules.{filename} # Added by module adder') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             print(f"Your module '{filename}' with class '{classname}' has been added " | 
					
						
							|  |  |  |                   "successfully! Hooray!") | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         # Check if module is inside the modules folder | 
					
						
							|  |  |  |         raise Exception(f"Your module should be in {module_folder} " | 
					
						
							|  |  |  |                         f"but is currently in {filepath}") | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def remove_module(cls, filename, remove_file=True): | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         """unregisters an inkycal module.
 | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Looks for given filename.py in /modules folder, removes entries of that | 
					
						
							|  |  |  |         module in init files inside /inkycal and /inkycal/modules | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Args: | 
					
						
							|  |  |  |           - filename: The filename (with .py ending) of the module which should be | 
					
						
							|  |  |  |             unregistered. e.g. `'mymodule.py'` | 
					
						
							|  |  |  |           - remove_file: ->bool (True/False). If set to True, the module is deleted | 
					
						
							|  |  |  |             after unregistering it, else it remains in the /modules folder | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         Usage: | 
					
						
							|  |  |  |           - Look for the module in Inkycal/inkycal/modules which should be removed. | 
					
						
							|  |  |  |             Only the filename (with .py) is required, not the full path. | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             Use this function to unregister the module from inkycal:: | 
					
						
							| 
									
										
										
										
											2020-11-30 12:08:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |               >>> from inkycal import Inkycal | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |               >>> Inkycal.remove_module('mymodule.py') | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         module_folder = top_level + '/inkycal/modules' | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Check if module is inside the modules folder and extract classname | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             with open(f"{module_folder}/{filename}", mode='r') as file: | 
					
						
							|  |  |  |                 module_content = file.read().splitlines() | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 for line in module_content: | 
					
						
							|  |  |  |                     if '(inkycal_module):' in line: | 
					
						
							|  |  |  |                         classname = line.split(' ')[-1].split('(')[0] | 
					
						
							|  |  |  |                         break | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                 if not classname: | 
					
						
							|  |  |  |                     print('The module you are trying to remove is not an inkycal module.. ' | 
					
						
							|  |  |  |                           'Not removing it.') | 
					
						
							|  |  |  |                     return | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         except FileNotFoundError: | 
					
						
							|  |  |  |             print(f"No module named {filename} found in {module_folder}") | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         filename = filename.split('.py')[0] | 
					
						
							| 
									
										
										
										
											2020-11-30 12:08:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Create a memory backup of /modules init file | 
					
						
							|  |  |  |         with open(module_folder + '/__init__.py', mode='r') as file: | 
					
						
							|  |  |  |             module_init = file.read().splitlines() | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         print('removing line from module_init') | 
					
						
							|  |  |  |         # Remove lines that contain classname | 
					
						
							|  |  |  |         with open(module_folder + '/__init__.py', mode='w') as file: | 
					
						
							|  |  |  |             for line in module_init: | 
					
						
							|  |  |  |                 if not classname in line: | 
					
						
							|  |  |  |                     file.write(line + '\n') | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     print('found, removing') | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # Create a memory backup of inkycal init file | 
					
						
							|  |  |  |         with open(f"{top_level}/inkycal/__init__.py", mode='r') as file: | 
					
						
							|  |  |  |             inkycal_init = file.read().splitlines() | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         print('removing line from inkycal init') | 
					
						
							|  |  |  |         # Remove lines that contain classname | 
					
						
							|  |  |  |         with open(f"{top_level}/inkycal/__init__.py", mode='w') as file: | 
					
						
							|  |  |  |             for line in inkycal_init: | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |                 if filename in line: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |                     print('found, removing') | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |                 else: | 
					
						
							|  |  |  |                     file.write(line + '\n') | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         # remove the file of the third party module if it exists and remove_file | 
					
						
							|  |  |  |         # was set to True (default) | 
					
						
							| 
									
										
										
										
											2022-03-31 19:04:42 +02:00
										 |  |  |         if os.path.exists(f"{module_folder}/{filename}.py") and remove_file is True: | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |             print('deleting module file') | 
					
						
							|  |  |  |             os.remove(f"{module_folder}/{filename}.py") | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 18:36:50 +02:00
										 |  |  |         print(f"Your module '{filename}' with class '{classname}' was removed.") | 
					
						
							| 
									
										
										
										
											2020-11-24 15:32:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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') |