Add support for longer update intervals
Load display-sizes from supported_models Cleanup old images on new run of Inkycal
This commit is contained in:
parent
1b94162ac4
commit
ae86daf6b8
@ -21,7 +21,7 @@ logs = logging.getLogger(__name__)
|
|||||||
logs.setLevel(level=logging.INFO)
|
logs.setLevel(level=logging.INFO)
|
||||||
|
|
||||||
# Get the path to the Inkycal folder
|
# Get the path to the Inkycal folder
|
||||||
top_level = os.path.dirname(os.path.abspath(os.path.dirname(__file__))).split("/inkycal")[0]
|
top_level = "/".join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))).split("/")[:-1])
|
||||||
|
|
||||||
# Get path of 'fonts' and 'images' folders within Inkycal folder
|
# Get path of 'fonts' and 'images' folders within Inkycal folder
|
||||||
fonts_location = os.path.join(top_level, "fonts/")
|
fonts_location = os.path.join(top_level, "fonts/")
|
||||||
|
@ -2,15 +2,14 @@
|
|||||||
Inkycal ePaper driving functions
|
Inkycal ePaper driving functions
|
||||||
Copyright by aceisace
|
Copyright by aceisace
|
||||||
"""
|
"""
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import traceback
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
import PIL
|
import PIL
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from inkycal.custom import top_level
|
from inkycal.custom import top_level
|
||||||
|
from inkycal.display.supported_models import supported_models
|
||||||
|
|
||||||
|
|
||||||
def import_driver(model):
|
def import_driver(model):
|
||||||
@ -47,14 +46,12 @@ 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 test(self) -> None:
|
def test(self) -> None:
|
||||||
"""Test the display by showing a test image"""
|
"""Test the display by showing a test image"""
|
||||||
# TODO implement test image
|
# TODO implement test image
|
||||||
raise NotImplementedError("Devs were too lazy again, sorry, please try again later")
|
raise NotImplementedError("Devs were too lazy again, sorry, please try again later")
|
||||||
|
|
||||||
|
def render(self, im_black: PIL.Image, im_colour: PIL.Image or None = None) -> None:
|
||||||
def render(self, im_black: PIL.Image, im_colour: PIL.Image or None=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
|
||||||
@ -166,26 +163,25 @@ class Display:
|
|||||||
def get_display_size(cls, model_name) -> (int, int):
|
def get_display_size(cls, model_name) -> (int, int):
|
||||||
"""Returns the size of the display as a tuple -> (width, height)
|
"""Returns the size of the display as a tuple -> (width, height)
|
||||||
|
|
||||||
Looks inside "drivers" folder for the given model name, then returns it's
|
Looks inside supported_models file for the given model name, then returns it's
|
||||||
size.
|
size.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- model_name: str -> The name of the E-Paper display to get it's size.
|
model_name: str -> The name of the E-Paper display to get it's size.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(width, height) ->tuple, showing the size of the display
|
(width, height) representing the size of the display
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: If the display name was not found in the supported models.
|
||||||
|
|
||||||
You can use this function directly without creating the Display class:
|
You can use this function directly without creating the Display class:
|
||||||
|
|
||||||
>>> Display.get_display_size('model_name')
|
>>> Display.get_display_size('model_name')
|
||||||
"""
|
"""
|
||||||
try:
|
if model_name in supported_models:
|
||||||
driver = import_driver(model_name)
|
return supported_models[model_name]
|
||||||
return driver.EPD_WIDTH, driver.EPD_HEIGHT
|
raise AssertionError(f'{model_name} not found in supported models')
|
||||||
except:
|
|
||||||
logging.error(f'Failed to load driver for ${model_name}. Check spelling?')
|
|
||||||
print(traceback.format_exc())
|
|
||||||
raise AssertionError("Could not import driver")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_display_names(cls) -> list:
|
def get_display_names(cls) -> list:
|
||||||
|
19
inkycal/display/supported_models.py
Normal file
19
inkycal/display/supported_models.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
supported_models = {
|
||||||
|
'epd_12_in_48': (1304, 984),
|
||||||
|
'epd_7_in_5_colour': (640, 384),
|
||||||
|
'9_in_7': (1200, 825),
|
||||||
|
'epd_5_in_83_colour': (600, 448),
|
||||||
|
'epd_12_in_48_colour': (1304, 984),
|
||||||
|
'epd_4_in_2_colour': (400, 300),
|
||||||
|
'epd_7_in_5_v2': (800, 480),
|
||||||
|
'epd_12_in_48_colour_V2': (1304, 984),
|
||||||
|
'epd_7_in_5': (640, 384),
|
||||||
|
'epd5in83b_V2': (648, 480),
|
||||||
|
'epd_7_in_5_v3': (880, 528),
|
||||||
|
'10_in_3': (1872, 1404),
|
||||||
|
'epd_7_in_5_v2_colour': (800, 480),
|
||||||
|
'epd_4_in_2': (400, 300),
|
||||||
|
'7_in_8': (1872, 1404),
|
||||||
|
'epd_7_in_5_v3_colour': (880, 528),
|
||||||
|
'epd_5_in_83': (600, 448)
|
||||||
|
}
|
@ -3,28 +3,22 @@ Main class for inkycal Project
|
|||||||
Copyright by aceinnolab
|
Copyright by aceinnolab
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import glob
|
import glob
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
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
|
||||||
from inkycal.modules.inky_image import Inkyimage as Images
|
from inkycal.modules.inky_image import Inkyimage as Images
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
# On the console, set a logger to show only important logs
|
# On the console, set a logger to show only important logs
|
||||||
# (level ERROR or higher)
|
# (level ERROR or higher)
|
||||||
stream_handler = logging.StreamHandler()
|
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')
|
||||||
|
|
||||||
@ -66,7 +60,7 @@ 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:str or None=None, render:bool=True):
|
def __init__(self, settings_path: str or None = None, render: bool = True):
|
||||||
"""Initialise Inkycal"""
|
"""Initialise Inkycal"""
|
||||||
|
|
||||||
# Get the release version from setup.py
|
# Get the release version from setup.py
|
||||||
@ -87,7 +81,8 @@ class Inkycal:
|
|||||||
self.settings = settings
|
self.settings = settings
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise FileNotFoundError(f"No settings.json file could be found in the specified location: {settings_path}")
|
raise FileNotFoundError(
|
||||||
|
f"No settings.json file could be found in the specified location: {settings_path}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@ -108,6 +103,8 @@ class Inkycal:
|
|||||||
|
|
||||||
self.show_border = self.settings.get('border_around_modules', False)
|
self.show_border = self.settings.get('border_around_modules', False)
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
# Load drivers if image should be rendered
|
# Load drivers if image should be rendered
|
||||||
if self.render:
|
if self.render:
|
||||||
# Init Display class with model in settings file
|
# Init Display class with model in settings file
|
||||||
@ -146,7 +143,7 @@ class Inkycal:
|
|||||||
logger.exception(f'Could not find module: "{module}". Please try to import manually')
|
logger.exception(f'Could not find module: "{module}". Please try to import manually')
|
||||||
|
|
||||||
# If something unexpected happened, show the error message
|
# If something unexpected happened, show the error message
|
||||||
except Exception as e:
|
except:
|
||||||
logger.exception(f"Exception: {traceback.format_exc()}.")
|
logger.exception(f"Exception: {traceback.format_exc()}.")
|
||||||
|
|
||||||
# Path to store images
|
# Path to store images
|
||||||
@ -158,8 +155,16 @@ class Inkycal:
|
|||||||
# Give an OK message
|
# Give an OK message
|
||||||
print('loaded inkycal')
|
print('loaded inkycal')
|
||||||
|
|
||||||
def countdown(self, interval_mins=None):
|
def countdown(self, interval_mins: int or None = None) -> int:
|
||||||
"""Returns the remaining time in seconds until next display update"""
|
"""Returns the remaining time in seconds until next display update.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- interval_mins = int -> the interval in minutes for the update
|
||||||
|
if no interval is given, the value from the settings file is used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- int -> the remaining time in seconds until next update
|
||||||
|
"""
|
||||||
|
|
||||||
# Check if empty, if empty, use value from settings file
|
# Check if empty, if empty, use value from settings file
|
||||||
if interval_mins is None:
|
if interval_mins is None:
|
||||||
@ -167,20 +172,30 @@ class Inkycal:
|
|||||||
|
|
||||||
# Find out at which minutes the update should happen
|
# Find out at which minutes the update should happen
|
||||||
now = arrow.now()
|
now = arrow.now()
|
||||||
update_timings = [(60 - int(interval_mins) * updates) for updates in
|
if interval_mins <= 60:
|
||||||
range(60 // int(interval_mins))][::-1]
|
update_timings = [(60 - interval_mins * updates) for updates in range(60 // interval_mins)][::-1]
|
||||||
|
|
||||||
# Calculate time in minutes 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 minutes 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
|
||||||
remaining_time = minutes * 60 + (60 - now.second)
|
remaining_time = minutes * 60 + (60 - now.second)
|
||||||
|
|
||||||
# Return seconds until next update
|
# Return seconds until next update
|
||||||
return remaining_time
|
return remaining_time
|
||||||
|
else:
|
||||||
|
# Calculate time in minutes until next update using the range of 24 hours in steps of every full hour
|
||||||
|
update_timings = [(60 * 24 - interval_mins * updates) for updates in range(60 * 24 // interval_mins)][::-1]
|
||||||
|
minutes = [_ for _ in update_timings if _ >= now.minute][0] - now.minute
|
||||||
|
remaining_time = minutes * 60 + (60 - now.second)
|
||||||
|
|
||||||
|
print(f'{round(minutes / 60, 1)} hours left until next refresh')
|
||||||
|
|
||||||
|
# Return seconds until next update
|
||||||
|
return remaining_time
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
"""Tests if Inkycal can run without issues.
|
"""Tests if Inkycal can run without issues.
|
||||||
@ -262,7 +277,6 @@ class Inkycal:
|
|||||||
print("Refresh needed: {a}".format(a=res))
|
print("Refresh needed: {a}".format(a=res))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""Runs main program in nonstop mode.
|
"""Runs main program in nonstop mode.
|
||||||
|
|
||||||
@ -346,8 +360,8 @@ class Inkycal:
|
|||||||
|
|
||||||
# render the image on the display
|
# render the image on the display
|
||||||
if not self.settings.get('image_hash', False) or self._needs_image_update([
|
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.png.hash", im_black),
|
||||||
(f"{self.image_folder}/canvas_colour.png.hash", im_colour)
|
(f"{self.image_folder}/canvas_colour.png.hash", im_colour)
|
||||||
]):
|
]):
|
||||||
# render the image on the display
|
# render the image on the display
|
||||||
display.render(im_black, im_colour)
|
display.render(im_black, im_colour)
|
||||||
@ -362,7 +376,7 @@ class Inkycal:
|
|||||||
im_black = upside_down(im_black)
|
im_black = upside_down(im_black)
|
||||||
|
|
||||||
if not self.settings.get('image_hash', False) or self._needs_image_update([
|
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.png.hash", im_black),
|
||||||
]):
|
]):
|
||||||
display.render(im_black)
|
display.render(im_black)
|
||||||
|
|
||||||
@ -557,6 +571,16 @@ class Inkycal:
|
|||||||
else:
|
else:
|
||||||
self._calibration_state = False
|
self._calibration_state = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def cleanup():
|
||||||
|
# clean up old images in image_folder
|
||||||
|
for _file in glob.glob(f"{image_folder}*.png"):
|
||||||
|
try:
|
||||||
|
os.remove(_file)
|
||||||
|
except:
|
||||||
|
logger.error(f"could not remove file: {_file}")
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print(f'running inkycal main in standalone/debug mode')
|
print(f'running inkycal main in standalone/debug mode')
|
||||||
|
@ -17,12 +17,35 @@ class TestMain(unittest.TestCase):
|
|||||||
assert inkycal.settings["model"] == "image_file"
|
assert inkycal.settings["model"] == "image_file"
|
||||||
assert inkycal.settings["update_interval"] == 5
|
assert inkycal.settings["update_interval"] == 5
|
||||||
assert inkycal.settings["orientation"] == 0
|
assert inkycal.settings["orientation"] == 0
|
||||||
assert inkycal.settings["info_section"] == True
|
assert inkycal.settings["info_section"] is True
|
||||||
assert inkycal.settings["info_section_height"] == 70
|
assert inkycal.settings["info_section_height"] == 70
|
||||||
assert inkycal.settings["border_around_modules"] == True
|
assert inkycal.settings["border_around_modules"] is True
|
||||||
|
|
||||||
def test_run(self):
|
def test_run(self):
|
||||||
inkycal = Inkycal(self.settings_path, render=False)
|
inkycal = Inkycal(self.settings_path, render=False)
|
||||||
inkycal.test()
|
inkycal.test()
|
||||||
|
|
||||||
|
def test_countdown(self):
|
||||||
|
inkycal = Inkycal(self.settings_path, render=False)
|
||||||
|
|
||||||
|
remaining_time = inkycal.countdown(5)
|
||||||
|
assert 1 <= remaining_time <= 5 * 60
|
||||||
|
remaining_time = inkycal.countdown(10)
|
||||||
|
assert 1 <= remaining_time <= 10 * 60
|
||||||
|
remaining_time = inkycal.countdown(15)
|
||||||
|
assert 1 <= remaining_time <= 15 * 60
|
||||||
|
remaining_time = inkycal.countdown(20)
|
||||||
|
assert 1 <= remaining_time <= 20 * 60
|
||||||
|
remaining_time = inkycal.countdown(30)
|
||||||
|
assert 1 <= remaining_time <= 30 * 60
|
||||||
|
remaining_time = inkycal.countdown(60)
|
||||||
|
assert 1 <= remaining_time <= 60 * 60
|
||||||
|
|
||||||
|
remaining_time = inkycal.countdown(120)
|
||||||
|
assert 1 <= remaining_time <= 120 * 2 * 60
|
||||||
|
remaining_time = inkycal.countdown(240)
|
||||||
|
assert 1 <= remaining_time <= 240 * 2 * 60
|
||||||
|
remaining_time = inkycal.countdown(600)
|
||||||
|
assert 1 <= remaining_time <= 600 * 2 * 60
|
||||||
|
remaining_time = inkycal.countdown(1200)
|
||||||
|
assert 1 <= remaining_time <= 1200 * 2 * 60
|
||||||
|
Loading…
Reference in New Issue
Block a user