diff --git a/inky_run.py b/inky_run.py index 50cf8fa..e2fff6a 100644 --- a/inky_run.py +++ b/inky_run.py @@ -1,7 +1,7 @@ -#!python3 -from inkycal import Inkycal # Import Inkycal +import asyncio +from inkycal import Inkycal inky = Inkycal(render=True) # Initialise Inkycal # If your settings.json file is not in /boot, use the full path: inky = Inkycal('path/to/settings.json', render=True) inky.test() # test if Inkycal can be run correctly, running this will show a bit of info for each module -inky.run() # If there were no issues, you can run Inkycal nonstop +asyncio.run(inky.run()) # If there were no issues, you can run Inkycal nonstop diff --git a/inkycal/display/display.py b/inkycal/display/display.py index e24b355..51652a2 100644 --- a/inkycal/display/display.py +++ b/inkycal/display/display.py @@ -4,6 +4,7 @@ Copyright by aceisace """ import os import logging +import traceback from importlib import import_module from PIL import Image @@ -43,7 +44,7 @@ class Display: except FileNotFoundError: raise Exception('SPI could not be found. Please check if SPI is enabled') - def render(self, im_black: Image.Image, im_colour=Image.Image or None) -> None: + def render(self, im_black: Image, im_colour=Image or None) -> None: """Renders an image on the selected E-Paper display. Initlializes the E-Paper display, sends image data and executes command @@ -66,7 +67,6 @@ class Display: Rendering black-white on coloured E-Paper displays: - >>> sample_image = Image.open('path/to/file.png') >>> display = Display('my_coloured_display') >>> display.render(sample_image, sample_image) @@ -82,14 +82,7 @@ class Display: epaper = self._epaper - if not self.supports_colour: - print('Initialising..', end='') - epaper.init() - print('Updating display......', end='') - epaper.display(epaper.getbuffer(im_black)) - print('Done') - - elif self.supports_colour: + if self.supports_colour: if not im_colour: raise Exception('im_colour is required for coloured epaper displays') print('Initialising..', end='') @@ -97,6 +90,12 @@ class Display: print('Updating display......', end='') epaper.display(epaper.getbuffer(im_black), epaper.getbuffer(im_colour)) print('Done') + else: + print('Initialising..', end='') + epaper.init() + print('Updating display......', end='') + epaper.display(epaper.getbuffer(im_black)) + print('Done') print('Sending E-Paper to deep sleep...', end='') epaper.sleep() @@ -173,9 +172,10 @@ class Display: try: driver = import_driver(model_name) return driver.EPD_WIDTH, driver.EPD_HEIGHT - except Exception as e: + except: logging.error(f'Failed to load driver for ${model_name}. Check spelling?') - raise e; + print(traceback.format_exc()) + raise AssertionError("Could not import driver") @classmethod def get_display_names(cls) -> list: diff --git a/inkycal/main.py b/inkycal/main.py index 08ed6c5..2b83dc8 100644 --- a/inkycal/main.py +++ b/inkycal/main.py @@ -1,6 +1,3 @@ -#!python3 -# -*- coding: utf-8 -*- - """ Main class for inkycal Project Copyright by aceinnolab @@ -9,11 +6,12 @@ Copyright by aceinnolab import glob import hashlib import json -import traceback from logging.handlers import RotatingFileHandler import arrow import numpy +import asyncio + from inkycal.custom import * from inkycal.display import Display @@ -27,7 +25,6 @@ stream_handler = logging.StreamHandler() stream_handler.setLevel(logging.ERROR) - if not os.path.exists(f'{top_level}/logs'): os.mkdir(f'{top_level}/logs') @@ -37,9 +34,7 @@ logging.basicConfig( 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 @@ -71,15 +66,18 @@ class Inkycal: to improve rendering on E-Papers. Set this to False for 9.7" E-Paper. """ - def __init__(self, settings_path=None, render=True): + def __init__(self, settings_path:str or None=None, render:bool=True): """Initialise Inkycal""" - self._release = '2.0.3' + # Get the release version from setup.py + with open(f'{top_level}/setup.py') as setup_file: + for line in setup_file: + if line.startswith('VERSION'): + self._release = line.split('=')[1].strip().replace("'", "") + break - # 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 + self.info = None # load settings file - throw an error if file could not be found if settings_path: @@ -89,7 +87,7 @@ class Inkycal: self.settings = settings except FileNotFoundError: - raise SettingsFileNotFoundError + raise FileNotFoundError(f"No settings.json file could be found in the specified location: {settings_path}") else: try: @@ -121,7 +119,7 @@ class Inkycal: # init calibration state self._calibration_state = False - # Load and intialize modules specified in the settings file + # Load and initialise modules specified in the settings file self._module_number = 1 for module in settings['modules']: module_name = module['name'] @@ -168,10 +166,10 @@ class Inkycal: update_timings = [(60 - int(interval_mins) * updates) for updates in range(60 // int(interval_mins))][::-1] - # Calculate time in mins until next update + # Calculate time in minutes until next update minutes = [_ for _ in update_timings if _ >= now.minute][0] - now.minute - # Print the remaining time in mins until next update + # Print the remaining time in minutes until next update print(f'{minutes} minutes left until next refresh') # Calculate time in seconds until next update @@ -259,12 +257,12 @@ class Inkycal: return res - def run(self): + async def run(self): """Runs main program in nonstop mode. Uses an infinity loop to run Inkycal nonstop. Inkycal generates the image from all modules, assembles them in one image, refreshed the E-Paper and - then sleeps until the next sheduled update. + then sleeps until the next scheduled update. """ # Get the time of initial run @@ -327,7 +325,7 @@ class Inkycal: self._calibration_check() if self._calibration_state: - # after calibration we have to forcefully rewrite the screen + # after calibration, we have to forcefully rewrite the screen self._remove_hashes(self.image_folder) if self.supports_colour: @@ -365,7 +363,7 @@ class Inkycal: f'program started {runtime.humanize()}') sleep_time = self.countdown() - time.sleep(sleep_time) + await asyncio.sleep(sleep_time) @staticmethod def _merge_bands(): @@ -536,7 +534,7 @@ class Inkycal: self.Display.calibrate() def _calibration_check(self): - """Calibration sheduler + """Calibration scheduler uses calibration hours from settings file to check if calibration is due""" now = arrow.now() # print('hour:', now.hour, 'hours:', self._calibration_hours) @@ -547,187 +545,6 @@ class Inkycal: else: self._calibration_state = False - @classmethod - def add_module(cls, filepath): - """registers a third party module for inkycal. - - 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. - - Args: - - filepath: The full filepath of the third party module. Modules should be - in Inkycal/inkycal/modules. - - Usage: - - download a third-party module. The exact link is provided by the - developer of that module and starts with - `https://raw.githubusercontent.com/...` - - enter the following in bash to download a module:: - - $ cd Inkycal/inkycal/modules #navigate to modules folder in inkycal - $ wget https://raw.githubusercontent.com/... #download the module - - then register it with this function:: - - >>> from inkycal import Inkycal - >>> Inkycal.add_module('/full/path/to/the/module/in/inkycal/modules.py') - """ - - module_folder = top_level + '/inkycal/modules' - - if module_folder in filepath: - filename = filepath.split('.py')[0].split('/')[-1] - - # 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() - - for line in module_content: - if '(inkycal_module):' in line: - classname = line.split(' ')[-1].split('(')[0] - break - - if not classname: - raise TypeError("your module doesn't seem to be a correct inkycal module.." - "Please check your module again.") - - # 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() - - 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 - - # Check if module is inside the modules folder - raise Exception(f"Your module should be in {module_folder} " - f"but is currently in {filepath}") - - @classmethod - def remove_module(cls, filename, remove_file=True): - """unregisters an inkycal module. - - Looks for given filename.py in /modules folder, removes entries of that - module in init files inside /inkycal and /inkycal/modules - - 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 - - - Usage: - - Look for the module in Inkycal/inkycal/modules which should be removed. - Only the filename (with .py) is required, not the full path. - - Use this function to unregister the module from inkycal:: - - >>> from inkycal import Inkycal - >>> Inkycal.remove_module('mymodule.py') - """ - - module_folder = top_level + '/inkycal/modules' - - # 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() - - for line in module_content: - if '(inkycal_module):' in line: - classname = line.split(' ')[-1].split('(')[0] - break - - if not classname: - print('The module you are trying to remove is not an inkycal module.. ' - 'Not removing it.') - return - - except FileNotFoundError: - print(f"No module named {filename} found in {module_folder}") - return - - filename = filename.split('.py')[0] - - # Create a memory backup of /modules init file - with open(module_folder + '/__init__.py', mode='r') as file: - module_init = file.read().splitlines() - - 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') - - # 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() - - 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: - if filename in line: - print('found, removing') - else: - file.write(line + '\n') - - # remove the file of the third party module if it exists and remove_file - # was set to True (default) - if os.path.exists(f"{module_folder}/{filename}.py") and remove_file is True: - print('deleting module file') - os.remove(f"{module_folder}/{filename}.py") - - print(f"Your module '{filename}' with class '{classname}' was removed.") - if __name__ == '__main__': print(f'running inkycal main in standalone/debug mode')