Skip to content

Commit

Permalink
Merge pull request #324 from aceinnolab/feature/#322
Browse files Browse the repository at this point in the history
Add support for longer update intervals
  • Loading branch information
aceisace authored Mar 10, 2024
2 parents 1b94162 + ae86daf commit 4243c93
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 44 deletions.
2 changes: 1 addition & 1 deletion inkycal/custom/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
logs.setLevel(level=logging.INFO)

# 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
fonts_location = os.path.join(top_level, "fonts/")
Expand Down
26 changes: 11 additions & 15 deletions inkycal/display/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
Inkycal ePaper driving functions
Copyright by aceisace
"""
import logging
import os
import traceback
from importlib import import_module

import PIL
from PIL import Image

from inkycal.custom import top_level
from inkycal.display.supported_models import supported_models


def import_driver(model):
Expand Down Expand Up @@ -47,14 +46,12 @@ def __init__(self, epaper_model):
except FileNotFoundError:
raise Exception('SPI could not be found. Please check if SPI is enabled')


def test(self) -> None:
"""Test the display by showing a test image"""
# TODO implement test image
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.
Initlializes the E-Paper display, sends image data and executes command
Expand Down Expand Up @@ -166,26 +163,25 @@ def calibrate(self, cycles=3):
def get_display_size(cls, model_name) -> (int, int):
"""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.
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:
(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:
>>> Display.get_display_size('model_name')
"""
try:
driver = import_driver(model_name)
return driver.EPD_WIDTH, driver.EPD_HEIGHT
except:
logging.error(f'Failed to load driver for ${model_name}. Check spelling?')
print(traceback.format_exc())
raise AssertionError("Could not import driver")
if model_name in supported_models:
return supported_models[model_name]
raise AssertionError(f'{model_name} not found in supported models')

@classmethod
def get_display_names(cls) -> list:
Expand Down
19 changes: 19 additions & 0 deletions inkycal/display/supported_models.py
Original file line number Diff line number Diff line change
@@ -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)
}
76 changes: 50 additions & 26 deletions inkycal/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,22 @@
Copyright by aceinnolab
"""

import asyncio
import glob
import hashlib
import json
from logging.handlers import RotatingFileHandler

import arrow
import numpy
import asyncio


from inkycal.custom import *
from inkycal.display import Display
from inkycal.modules.inky_image import Inkyimage as Images

from PIL import Image

# On the console, set a logger to show only important logs
# (level ERROR or higher)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.ERROR)


if not os.path.exists(f'{top_level}/logs'):
os.mkdir(f'{top_level}/logs')

Expand Down Expand Up @@ -66,7 +60,7 @@ class Inkycal:
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"""

# Get the release version from setup.py
Expand All @@ -87,7 +81,8 @@ def __init__(self, settings_path:str or None=None, render:bool=True):
self.settings = settings

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:
try:
Expand All @@ -108,6 +103,8 @@ def __init__(self, settings_path:str or None=None, render:bool=True):

self.show_border = self.settings.get('border_around_modules', False)

self.cleanup()

# Load drivers if image should be rendered
if self.render:
# Init Display class with model in settings file
Expand Down Expand Up @@ -146,7 +143,7 @@ def __init__(self, settings_path:str or None=None, render:bool=True):
logger.exception(f'Could not find module: "{module}". Please try to import manually')

# If something unexpected happened, show the error message
except Exception as e:
except:
logger.exception(f"Exception: {traceback.format_exc()}.")

# Path to store images
Expand All @@ -158,29 +155,47 @@ def __init__(self, settings_path:str or None=None, render:bool=True):
# Give an OK message
print('loaded inkycal')

def countdown(self, interval_mins=None):
"""Returns the remaining time in seconds until next display update"""
def countdown(self, interval_mins: int or None = None) -> int:
"""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
if interval_mins is None:
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]
if interval_mins <= 60:
update_timings = [(60 - interval_mins * updates) for updates in range(60 // interval_mins)][::-1]

# Calculate time in minutes until next update
minutes = [_ for _ in update_timings if _ >= now.minute][0] - now.minute
# Calculate time in minutes until next update
minutes = [_ for _ in update_timings if _ >= now.minute][0] - now.minute

# Print the remaining time in minutes until next update
print(f'{minutes} minutes left until next refresh')
# Print the remaining time in minutes 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)
# Calculate time in seconds until next update
remaining_time = minutes * 60 + (60 - now.second)

# Return seconds until next update
return remaining_time
# Return seconds until next update
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):
"""Tests if Inkycal can run without issues.
Expand Down Expand Up @@ -262,7 +277,6 @@ def _needs_image_update(self, _list):
print("Refresh needed: {a}".format(a=res))
return res


async def run(self):
"""Runs main program in nonstop mode.
Expand Down Expand Up @@ -346,8 +360,8 @@ async def run(self):

# render the image on the display
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)
(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)
Expand All @@ -362,7 +376,7 @@ async def run(self):
im_black = upside_down(im_black)

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)

Expand Down Expand Up @@ -557,6 +571,16 @@ def _calibration_check(self):
else:
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__':
print(f'running inkycal main in standalone/debug mode')
27 changes: 25 additions & 2 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,35 @@ def test_init(self):
assert inkycal.settings["model"] == "image_file"
assert inkycal.settings["update_interval"] == 5
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["border_around_modules"] == True
assert inkycal.settings["border_around_modules"] is True

def test_run(self):
inkycal = Inkycal(self.settings_path, render=False)
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

0 comments on commit 4243c93

Please sign in to comment.