test async

This commit is contained in:
Ace 2023-11-22 12:45:07 +01:00
parent dae4ce31b2
commit f4d08c64a2
3 changed files with 34 additions and 217 deletions

View File

@ -1,7 +1,7 @@
#!python3 import asyncio
from inkycal import Inkycal # Import Inkycal from inkycal import Inkycal
inky = Inkycal(render=True) # Initialise 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) # 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.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

View File

@ -4,6 +4,7 @@ Copyright by aceisace
""" """
import os import os
import logging import logging
import traceback
from importlib import import_module from importlib import import_module
from PIL import Image from PIL import Image
@ -43,7 +44,7 @@ class Display:
except FileNotFoundError: except FileNotFoundError:
raise Exception('SPI could not be found. Please check if SPI is enabled') 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. """Renders an image on the selected E-Paper display.
Initlializes the E-Paper display, sends image data and executes command 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: Rendering black-white on coloured E-Paper displays:
>>> sample_image = Image.open('path/to/file.png') >>> sample_image = Image.open('path/to/file.png')
>>> display = Display('my_coloured_display') >>> display = Display('my_coloured_display')
>>> display.render(sample_image, sample_image) >>> display.render(sample_image, sample_image)
@ -82,14 +82,7 @@ class Display:
epaper = self._epaper epaper = self._epaper
if not self.supports_colour: if 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 not im_colour: if not im_colour:
raise Exception('im_colour is required for coloured epaper displays') raise Exception('im_colour is required for coloured epaper displays')
print('Initialising..', end='') print('Initialising..', end='')
@ -97,6 +90,12 @@ class Display:
print('Updating display......', end='') print('Updating display......', end='')
epaper.display(epaper.getbuffer(im_black), epaper.getbuffer(im_colour)) epaper.display(epaper.getbuffer(im_black), epaper.getbuffer(im_colour))
print('Done') 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='') print('Sending E-Paper to deep sleep...', end='')
epaper.sleep() epaper.sleep()
@ -173,9 +172,10 @@ class Display:
try: try:
driver = import_driver(model_name) driver = import_driver(model_name)
return driver.EPD_WIDTH, driver.EPD_HEIGHT return driver.EPD_WIDTH, driver.EPD_HEIGHT
except Exception as e: except:
logging.error(f'Failed to load driver for ${model_name}. Check spelling?') 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 @classmethod
def get_display_names(cls) -> list: def get_display_names(cls) -> list:

View File

@ -1,6 +1,3 @@
#!python3
# -*- coding: utf-8 -*-
""" """
Main class for inkycal Project Main class for inkycal Project
Copyright by aceinnolab Copyright by aceinnolab
@ -9,11 +6,12 @@ Copyright by aceinnolab
import glob import glob
import hashlib import hashlib
import json import json
import traceback
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import arrow import arrow
import numpy import numpy
import asyncio
from inkycal.custom import * from inkycal.custom import *
from inkycal.display import Display from inkycal.display import Display
@ -27,7 +25,6 @@ stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.ERROR) stream_handler.setLevel(logging.ERROR)
if not os.path.exists(f'{top_level}/logs'): if not os.path.exists(f'{top_level}/logs'):
os.mkdir(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', format='%(asctime)s | %(name)s | %(levelname)s: %(message)s',
datefmt='%d-%m-%Y %H:%M:%S', datefmt='%d-%m-%Y %H:%M:%S',
handlers=[ handlers=[
stream_handler, # add stream handler from above stream_handler, # add stream handler from above
RotatingFileHandler( # log to a file too RotatingFileHandler( # log to a file too
f'{top_level}/logs/inkycal.log', # file to log f'{top_level}/logs/inkycal.log', # file to log
maxBytes=2097152, # 2MB max filesize 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. 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""" """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.render = render
self.info = None
# load settings file - throw an error if file could not be found # load settings file - throw an error if file could not be found
if settings_path: if settings_path:
@ -89,7 +87,7 @@ class Inkycal:
self.settings = settings self.settings = settings
except FileNotFoundError: except FileNotFoundError:
raise SettingsFileNotFoundError raise FileNotFoundError(f"No settings.json file could be found in the specified location: {settings_path}")
else: else:
try: try:
@ -121,7 +119,7 @@ class Inkycal:
# init calibration state # init calibration state
self._calibration_state = False 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 self._module_number = 1
for module in settings['modules']: for module in settings['modules']:
module_name = module['name'] module_name = module['name']
@ -168,10 +166,10 @@ class Inkycal:
update_timings = [(60 - int(interval_mins) * updates) for updates in update_timings = [(60 - int(interval_mins) * updates) for updates in
range(60 // int(interval_mins))][::-1] 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 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') print(f'{minutes} minutes left until next refresh')
# Calculate time in seconds until next update # Calculate time in seconds until next update
@ -259,12 +257,12 @@ class Inkycal:
return res return res
def run(self): async def run(self):
"""Runs main program in nonstop mode. """Runs main program in nonstop mode.
Uses an infinity loop to run Inkycal nonstop. Inkycal generates the image 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 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 # Get the time of initial run
@ -327,7 +325,7 @@ class Inkycal:
self._calibration_check() self._calibration_check()
if self._calibration_state: 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) self._remove_hashes(self.image_folder)
if self.supports_colour: if self.supports_colour:
@ -365,7 +363,7 @@ class Inkycal:
f'program started {runtime.humanize()}') f'program started {runtime.humanize()}')
sleep_time = self.countdown() sleep_time = self.countdown()
time.sleep(sleep_time) await asyncio.sleep(sleep_time)
@staticmethod @staticmethod
def _merge_bands(): def _merge_bands():
@ -536,7 +534,7 @@ class Inkycal:
self.Display.calibrate() self.Display.calibrate()
def _calibration_check(self): def _calibration_check(self):
"""Calibration sheduler """Calibration scheduler
uses calibration hours from settings file to check if calibration is due""" uses calibration hours from settings file to check if calibration is due"""
now = arrow.now() now = arrow.now()
# print('hour:', now.hour, 'hours:', self._calibration_hours) # print('hour:', now.hour, 'hours:', self._calibration_hours)
@ -547,187 +545,6 @@ class Inkycal:
else: else:
self._calibration_state = False 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__': if __name__ == '__main__':
print(f'running inkycal main in standalone/debug mode') print(f'running inkycal main in standalone/debug mode')