| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  | from inkycal import Settings, Layout | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | from inkycal.custom import * | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  | from os.path import exists | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | import traceback | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | import arrow | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | try: | 
					
						
							|  |  |  |   from PIL import Image | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |   print('Pillow is not installed! Please install with:') | 
					
						
							|  |  |  |   print('pip3 install Pillow') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | try: | 
					
						
							|  |  |  |   import numpy | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |   print('numpy is not installed! Please install with:') | 
					
						
							|  |  |  |   print('pip3 install numpy') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | logger = logging.getLogger('inkycal') | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  | logger.setLevel(level=logging.ERROR) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  | class Inkycal: | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |   """Main class""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |   def __init__(self, settings_path, render=True): | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |     """initialise class
 | 
					
						
							|  |  |  |     settings_path = str -> location/folder of settings file | 
					
						
							|  |  |  |     render = bool -> show something on the ePaper? | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     self._release = '2.0.0beta' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |     # Check if render is boolean | 
					
						
							|  |  |  |     if not isinstance(render, bool): | 
					
						
							|  |  |  |       raise Exception('render must be True or False, not "{}"'.format(render)) | 
					
						
							|  |  |  |     self.render = render | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     # Init settings class | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |     self.Settings = Settings(settings_path) | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Check if display support colour | 
					
						
							|  |  |  |     self.supports_colour = self.Settings.Layout.supports_colour | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Option to flip image upside down | 
					
						
							|  |  |  |     self.upside_down = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Option to use epaper image optimisation | 
					
						
							|  |  |  |     self.optimize = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Load drivers if image should be rendered | 
					
						
							|  |  |  |     if self.render == True: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Get model and check if colour can be rendered | 
					
						
							|  |  |  |       model= self.Settings.model | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Init Display class | 
					
						
							|  |  |  |       from inkycal.display import Display | 
					
						
							|  |  |  |       self.Display = Display(model) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # load+validate settings file. Import and setup specified modules | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |     self.active_modules = self.Settings.active_modules() | 
					
						
							|  |  |  |     for module in self.active_modules: | 
					
						
							|  |  |  |       try: | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |         loader = 'from inkycal.modules import {0}'.format(module) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |         module_data = self.Settings.get_config(module) | 
					
						
							|  |  |  |         size, conf = module_data['size'], module_data['config'] | 
					
						
							|  |  |  |         setup = 'self.{} = {}(size, conf)'.format(module, module) | 
					
						
							|  |  |  |         exec(loader) | 
					
						
							|  |  |  |         exec(setup) | 
					
						
							|  |  |  |         logger.debug(('{}: size: {}, config: {}'.format(module, size, conf))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # If a module was not found, print an error message | 
					
						
							|  |  |  |       except ImportError: | 
					
						
							|  |  |  |         print( | 
					
						
							|  |  |  |           'Could not find module: "{}". Please try to import manually.'.format( | 
					
						
							|  |  |  |           module)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Give an OK message | 
					
						
							|  |  |  |     print('loaded inkycal') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def countdown(self, interval_mins=None ): | 
					
						
							|  |  |  |     """Returns the remaining time in seconds until next display update""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Validate update interval | 
					
						
							|  |  |  |     allowed_intervals = [10, 15, 20, 30, 60] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Check if empty, if empty, use value from settings file | 
					
						
							|  |  |  |     if interval_mins == None: | 
					
						
							|  |  |  |       interval_mins = self.Settings.update_interval | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Check if integer | 
					
						
							|  |  |  |     if not isinstance(interval_mins, int): | 
					
						
							|  |  |  |       raise Exception('Update interval must be an integer -> 60') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Check if value is supported | 
					
						
							|  |  |  |     if interval_mins not in allowed_intervals: | 
					
						
							|  |  |  |       raise Exception('Update interval is {}, but should be one of: {}'.format( | 
					
						
							|  |  |  |         interval_mins, allowed_intervals)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # 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('{0} Minutes left until next refresh'.format(minutes)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # 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): | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     """Inkycal test run""" | 
					
						
							|  |  |  |     print('You are running inkycal v{}'.format(self._release)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print('Running inkyal test-run for {} ePaper'.format( | 
					
						
							|  |  |  |       self.Settings.model)) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for module in self.active_modules: | 
					
						
							|  |  |  |       generate_im = 'self.{0}.generate_image()'.format(module) | 
					
						
							|  |  |  |       print('generating image for {} module...'.format(module), end = '') | 
					
						
							|  |  |  |       try: | 
					
						
							|  |  |  |         exec(generate_im) | 
					
						
							|  |  |  |         print('OK!') | 
					
						
							|  |  |  |       except Exception as Error: | 
					
						
							|  |  |  |         print('Error!') | 
					
						
							|  |  |  |         print(traceback.format_exc()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |   def run(self): | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |     """Runs the main inykcal program nonstop (cannot be stopped anymore!)
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     Will show something on the display if render was set to True"""
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |     # TODO: printing traceback on display (or at least a smaller message?) | 
					
						
							|  |  |  |     # Calibration | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     # Get the time of initial run | 
					
						
							|  |  |  |     runtime = arrow.now() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function to flip images upside down | 
					
						
							|  |  |  |     upside_down = lambda image: image.rotate(180, expand=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Count the number of times without any errors | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |     counter = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     while True: | 
					
						
							|  |  |  |       print('Generating images for all modules...') | 
					
						
							|  |  |  |       for module in self.active_modules: | 
					
						
							|  |  |  |         generate_im = 'self.{0}.generate_image()'.format(module) | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |           exec(generate_im) | 
					
						
							|  |  |  |         except Exception as Error: | 
					
						
							|  |  |  |           print('Error!') | 
					
						
							|  |  |  |           message = traceback.format_exc() | 
					
						
							|  |  |  |           print(message) | 
					
						
							|  |  |  |           counter = 0 | 
					
						
							|  |  |  |       print('OK') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |       # Assemble image from each module | 
					
						
							|  |  |  |       self._assemble() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Check if image should be rendered | 
					
						
							|  |  |  |       if self.render == True: | 
					
						
							|  |  |  |         Display = self.Display | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.supports_colour == True: | 
					
						
							|  |  |  |           im_black = Image.open(images+'canvas.png') | 
					
						
							|  |  |  |           im_colour = Image.open(images+'canvas_colour.png') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Flip the image by 180° if required | 
					
						
							|  |  |  |           if self.upside_down == True: | 
					
						
							|  |  |  |             upside_down(im_black) | 
					
						
							|  |  |  |             upside_down(im_colour) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # render the image on the display | 
					
						
							|  |  |  |           Display.render(im_black, im_colour) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Part for black-white ePapers | 
					
						
							|  |  |  |         elif self.supports_colour == False: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           im_black = self._merge_bands() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Flip the image by 180° if required | 
					
						
							|  |  |  |           if self.upside_down == True: | 
					
						
							|  |  |  |             upside_down(im_black) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           Display.render(im_black) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       print('\ninkycal has been running without any errors for', end = ' ') | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |       print('{} display updates'.format(counter)) | 
					
						
							|  |  |  |       print('That was {}'.format(runtime.humanize())) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |       counter += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |       sleep_time = self.countdown() | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  |       time.sleep(sleep_time) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |   def _merge_bands(): | 
					
						
							|  |  |  |     """Merges black and coloured bands for black-white ePapers
 | 
					
						
							|  |  |  |     returns the merged image | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     im_path = images | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     im1_path, im2_path = images+'canvas.png', images+'canvas_colour.png' | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     # If there is an image for black and colour, merge them | 
					
						
							|  |  |  |     if exists(im1_path) and exists(im2_path): | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |       im1 = Image.open(im1_name).convert('RGBA') | 
					
						
							|  |  |  |       im2 = Image.open(im2_name).convert('RGBA') | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +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) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |       im2 = clear_white(im2) | 
					
						
							|  |  |  |       im1.paste(im2, (0,0), im2) | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     # If there is no image for the coloured-band, return the bw-image | 
					
						
							|  |  |  |     elif exists(im1_path) and not exists(im2_path): | 
					
						
							|  |  |  |       im1 = Image.open(im1_name).convert('RGBA') | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |     return im1 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 04:00:39 +02:00
										 |  |  |   def _assemble(self): | 
					
						
							|  |  |  |     """Assmebles all sub-images to a single image""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Create an empty canvas with the size of the display | 
					
						
							|  |  |  |     width, height = self.Settings.Layout.display_size | 
					
						
							|  |  |  |     height, width = width, height | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     im_black = Image.new('RGB', (width, height), color = 'white') | 
					
						
							|  |  |  |     im_colour = Image.new('RGB', (width ,height), color = 'white') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Set cursor for y-axis | 
					
						
							|  |  |  |     im1_cursor = 0 | 
					
						
							|  |  |  |     im2_cursor = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for module in self.active_modules: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       im1_path = images+module+'.png' | 
					
						
							|  |  |  |       im2_path = images+module+'_colour.png' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Check if there is an image for the black band | 
					
						
							|  |  |  |       if exists(im1_path): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get actual size of image | 
					
						
							|  |  |  |         im1 = Image.open(im1_path).convert('RGBA') | 
					
						
							|  |  |  |         im1_size = im1.size | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get the size of the section | 
					
						
							|  |  |  |         section_size = self.Settings.get_config(module)['size'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Calculate coordinates to center the image | 
					
						
							|  |  |  |         x = int( (section_size[0]-im1_size[0]) /2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # center the image in the section space | 
					
						
							|  |  |  |         im_black.paste(im1, (x,y), im1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Shift the y-axis cursor at the beginning of next section | 
					
						
							|  |  |  |         im1_cursor += section_size[1] - y | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Check if there is an image for the coloured band | 
					
						
							|  |  |  |       if exists(im2_path): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get actual size of image | 
					
						
							|  |  |  |         im2 = Image.open(im2_path).convert('RGBA') | 
					
						
							|  |  |  |         im2_size = im2.size | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get the size of the section | 
					
						
							|  |  |  |         section_size = self.Settings.get_config(module)['size'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Calculate coordinates to center the image | 
					
						
							|  |  |  |         x = int( (section_size[0]-im2_size[0]) /2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # center the image in the section space | 
					
						
							|  |  |  |         im_colour.paste(im2, (x,y), im2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Shift the y-axis cursor at the beginning of next section | 
					
						
							|  |  |  |         im2_cursor += section_size[1] - y | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if self.optimize == True: | 
					
						
							|  |  |  |       self._optimize_im(im_black).save(images+'canvas.png', 'PNG') | 
					
						
							|  |  |  |       self._optimize_im(im_colour).save(images+'canvas_colour.png', 'PNG') | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |       im_black.save(images+'canvas.png', 'PNG') | 
					
						
							|  |  |  |       im_colour.save(images+'canvas_colour.png', 'PNG') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _optimize_im(self, image, threshold=220): | 
					
						
							|  |  |  |     """Optimize the image for rendering on ePaper displays""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     buffer = numpy.array(image.convert('RGB')) | 
					
						
							|  |  |  |     red, green = buffer[:, :, 0], buffer[:, :, 1] | 
					
						
							|  |  |  |     buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0,0,0] #grey->black | 
					
						
							|  |  |  |     image = Image.fromarray(buffer) | 
					
						
							|  |  |  |     return image | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def calibrate(self): | 
					
						
							|  |  |  |     """Calibrate the ePaper display to prevent burn-ins (ghosting)
 | 
					
						
							|  |  |  |     Currently has to be run manually"""
 | 
					
						
							|  |  |  |     self.Display.calibrate() | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _check_for_updates(self): | 
					
						
							|  |  |  |     """Check if a new update is available for inkycal""" | 
					
						
							|  |  |  |     raise NotImplementedError('Tha developer were too lazy to implement this..') | 
					
						
							| 
									
										
										
										
											2020-05-26 19:20:18 +02:00
										 |  |  | 
 |