Improved documentation

This commit is contained in:
Ace 2020-11-24 00:40:49 +01:00
parent 0acd5c5c17
commit 418422fa52
4 changed files with 260 additions and 40 deletions

View File

@ -40,13 +40,41 @@ for path,dirs,files in os.walk(fonts_location):
available_fonts = [key for key,values in fonts.items()]
def get_fonts():
"""Print all available fonts by name"""
"""Print all available fonts by name.
Searches the /font folder in Inykcal and displays all fonts found in
there.
Returns:
printed output of all available fonts. To access a fontfile, use the
fonts dictionary to access it.
>>> fonts['fontname']
To use a font, use the following sytax, where fontname is one of the
printed fonts of this function:
>>> ImageFont.truetype(fonts['fontname'], size = 10)
"""
for fonts in available_fonts:
print(fonts)
def get_system_tz():
"""Get the timezone set by the system"""
"""Gets the system-timezone
Gets the timezone set by the system.
Returns:
- A timezone if a system timezone was found.
- None if no timezone was found.
The extracted timezone can be used to show the local time instead of UTC. e.g.
>>> import arrow
>>> print(arrow.now()) # returns non-timezone-aware time
>>> print(arrow.now(tz=get_system_tz()) # prints timezone aware time.
"""
try:
local_tz = time.tzname[1]
except:
@ -57,8 +85,21 @@ def get_system_tz():
def auto_fontsize(font, max_height):
"""Adjust the fontsize to fit 80% of max_height
returns the font object with modified size"""
"""Scales a given font to 80% of max_height.
Gets the height of a font and scales it until 80% of the max_height
is filled.
Args:
- font: A PIL Font object.
- max_height: An integer representing the height to adjust the font to
which the given font should be scaled to.
Returns:
A PIL font object with modified height.
"""
fontsize = font.getsize('hg')[1]
while font.getsize('hg')[1] <= (max_height * 0.80):
fontsize += 1
@ -67,12 +108,29 @@ def auto_fontsize(font, max_height):
def write(image, xy, box_size, text, font=None, **kwargs):
"""Write text on specified image
image = on which image should the text be added?
xy = (x,y) coordinates as tuple -> (x,y)
box_size = size of text-box -> (width,height)
text = string (what to write)
font = which font to use
"""Writes text on a image.
Writes given text at given position on the specified image.
Args:
- image: The image to draw this text on, usually im_black or im_colour.
- xy: tuple-> (x,y) representing the x and y co-ordinate.
- box_size: tuple -> (width, height) representing the size of the text box.
- text: string, the actual text to add on the image.
- font: A PIL Font object e.g.
ImageFont.truetype(fonts['fontname'], size = 10).
Args: (optional)
- alignment: alignment of the text, use 'center', 'left', 'right'.
- autofit: bool (True/False). Automatically increases fontsize to fill in
as much of the box-height as possible.
- colour: black by default, do not change as it causes issues with rendering
on e-Paper.
- rotation: Rotate the text with the text-box by a given angle anti-clockwise.
- fill_width: Decimal representing a percentage e.g. 0.9 # 90%. Fill a
maximum of 90% of the size of the full width of text-box.
- fill_height: Decimal representing a percentage e.g. 0.9 # 90%. Fill a
maximum of 90% of the size of the full height of the text-box.
"""
allowed_kwargs = ['alignment', 'autofit', 'colour', 'rotation',
'fill_width', 'fill_height']
@ -142,7 +200,19 @@ def write(image, xy, box_size, text, font=None, **kwargs):
def text_wrap(text, font=None, max_width = None):
"""Split long text (text-wrapping). Returns a list"""
"""Splits a very long text into smaller parts
Splits a long text to smaller lines which can fit in a line with max_width.
Uses a Font object for more accurate calculations.
Args:
- font: A PIL font object which is used to calculate the size.
- max_width: int-> a width in pixels defining the maximum width before
splitting the text into the next chunk.
Returns:
A list containing chunked strings of the full text.
"""
lines = []
if font.getsize(text)[0] < max_width:
lines.append(text)
@ -162,7 +232,20 @@ def text_wrap(text, font=None, max_width = None):
def internet_available():
"""check if the internet is available"""
"""checks if the internet is available.
Attempts to connect to google.com with a timeout of 5 seconds to check
if the network can be reached.
Returns:
- True if connection could be established.
- False if the internet could not be reached.
Returned output can be used to add a check for internet availability:
>>> if internet_available() == True:
>>> #...do something that requires internet connectivity
"""
try:
urlopen('https://google.com',timeout=5)
@ -172,11 +255,25 @@ def internet_available():
def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1,0.1)):
"""Draws a border with round corners at (x,y)
xy = position e.g: (5,10)
size = size of border (width, height), radius: corner radius
thickness = border thickness
shrinkage = shrink and center border by given percentage:(width_%, height_%)
"""Draws a border at given coordinates.
Args:
- image: The image on which the border should be drawn (usually im_black or
im_colour.
- xy: Tuple representing the top-left corner of the border e.g. (32, 100)
where 32 is the x co-ordinate and 100 is the y-coordinate.
- size: Size of the border as a tuple -> (width, height).
- radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners.
- thickness: Thickness of the border in pixels.
- shrinkage: A tuple containing decimals presenting a percentage of shrinking
-> (width_shrink_percentage, height_shrink_percentage).
e.g. (0.1, 0.2) ~ shrinks the width of border by 10%, shrinks height of
border by 20%
"""
colour='black'

View File

@ -12,7 +12,15 @@ import glob
class Display:
"""Display class for inkycal
Handles rendering on display"""
Creates an instance of the driver for the selected E-Paper model and allows
rendering images and calibrating the E-Paper display
args:
- epaper_model: The name of your E-Paper model.
"""
def __init__(self, epaper_model):
"""Load the drivers for this epaper model"""
@ -35,8 +43,41 @@ class Display:
raise Exception('SPI could not be found. Please check if SPI is enabled')
def render(self, im_black, im_colour = None):
"""Render an image on the epaper
im_colour is required for three-colour epapers"""
"""Renders an image on the selected E-Paper display.
Initlializes the E-Paper display, sends image data and executes command
to update the display.
Args:
- im_black: The image for the black-pixels. Anything in this image that is
black is rendered as black on the display. This is required and ideally
should be a black-white image.
- im_colour: For E-Paper displays supporting colour, a separate image,
ideally black-white is required for the coloured pixels. Anything that is
black in this image will show up as either red/yellow.
Rendering an image for black-white E-Paper displays:
>>> sample_image = PIL.Image.open('path/to/file.png')
>>> display = Display('my_black_white_display')
>>> display.render(sample_image)
Rendering black-white on coloured E-Paper displays:
>>> sample_image = PIL.Image.open('path/to/file.png')
>>> display = Display('my_coloured_display')
>>> display.render(sample_image, sample_image)
Rendering coloured image where 2 images are available:
>>> black_image = PIL.Image.open('path/to/file.png') # black pixels
>>> colour_image = PIL.Image.open('path/to/file.png') # coloured pixels
>>> display = Display('my_coloured_display')
>>> display.render(black_image, colour_image)
"""
epaper = self._epaper
@ -65,9 +106,23 @@ class Display:
print('Done')
def calibrate(self, cycles=3):
"""Flush display with single colour to prevent burn-ins (ghosting)
cycles -> int. How many times should each colour be flushed?
recommended cycles = 3"""
"""Calibrates the display to retain crisp colours
Flushes the selected display several times with it's supported colours,
removing any previous effects of ghosting.
Args:
- cycles: -> int. The number of times to flush the display with it's
supported colours.
It's recommended to calibrate the display after every 6 display updates
for best results. For black-white only displays, calibration is less
critical, but not calibrating regularly results in grey-ish text.
Please note that calibration takes a while to complete. 3 cycles may
take 10 mins on black-white E-Papers while it takes 20 minutes on coloured
E-Paper displays.
"""
epaper = self._epaper
epaper.init()
@ -102,7 +157,21 @@ class Display:
@classmethod
def get_display_size(cls, model_name):
"returns (width, height) of given display"
"""Returns the size of the display as a tuple -> (width, height)
Looks inside drivers folder 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.
Returns:
(width, height) ->tuple, showing the size of the display
You can use this function directly without creating the Display class:
>>> Display.get_display_size('model_name')
"""
if not isinstance(model_name, str):
print('model_name should be a string')
return
@ -110,6 +179,8 @@ class Display:
driver_files = top_level+'/inkycal/display/drivers/*.py'
drivers = glob.glob(driver_files)
drivers = [i.split('/')[-1].split('.')[0] for i in drivers]
drivers.remove('__init__')
drivers.remove('epdconfig')
if model_name not in drivers:
print('This model name was not found. Please double check your spellings')
return
@ -122,6 +193,28 @@ class Display:
height = int(line.rstrip().replace(" ", "").split('=')[-1])
return width, height
@classmethod
def get_display_names(cls):
"""Prints all supported E-Paper models.
Fetches all filenames in driver folder and prints them on the console.
Returns:
Printed version of all supported Displays.
Use one of the models to intilialize the Display class in order to gain
access to the E-Paper.
You can use this function directly without creating the Display class:
>>> Display.get_display_names()
"""
driver_files = top_level+'/inkycal/display/drivers/*.py'
drivers = glob.glob(driver_files)
drivers = [i.split('/')[-1].split('.')[0] for i in drivers]
drivers.remove('__init__')
drivers.remove('epdconfig')
print(*drivers, sep='\n')
if __name__ == '__main__':
print("Running Display class in standalone mode")

View File

@ -24,8 +24,9 @@ except ImportError:
try:
import numpy
except ImportError:
print('numpy is not installed! Please install with:')
print('pip3 install numpy')
print('numpy is not installed!. \nIf you are on Windows '
'run: pip3 install numpy \nIf you are on Raspberry Pi '
'remove numpy: pip3 uninstall numpy \nThen try again.')
logging.basicConfig(
level = logging.INFO, #DEBUG > #INFO > #ERROR > #WARNING > #CRITICAL
@ -34,16 +35,31 @@ logging.basicConfig(
logger = logging.getLogger('inykcal main')
# TODO: fix issue with non-render mode requiring SPI
# TODO: fix info section not updating after a calibration
# TODO: add function to add/remove third party modules
# TODO: autostart -> supervisor?
# TODO: logging to files
class Inkycal:
"""Inkycal main class"""
"""Inkycal main class
Main class of Inkycal, test and run the main Inkycal program.
Args:
- settings_path = str -> the full path to your settings.json file
if no path is given, tries looking for settings file in /boot folder.
- render = bool (True/False) -> show the image on the epaper display?
Attributes:
- optimize = True/False. Reduce number of colours on the generated image
to improve rendering on E-Papers. Set this to False for 9.7" E-Paper.
"""
def __init__(self, settings_path=None, render=True):
"""Initialise Inkycal
settings_path = str -> the full path to your settings.json file
if no path is given, try looking for settings file in /boot folder
"""Initialise Inkycal"""
render = bool (True/False) -> show the image on the epaper display?
"""
self._release = '2.0.0'
# Check if render was set correctly
@ -87,7 +103,6 @@ class Inkycal:
# check if colours can be rendered
self.supports_colour = True if 'colour' in settings['model'] else False
# get calibration hours
self._calibration_hours = self.settings['calibration_hours']
@ -152,9 +167,14 @@ class Inkycal:
def test(self):
"""Inkycal test run
Generates images for each module, one by one and prints OK if no
problems were found."""
"""Tests if Inkycal can run without issues.
Attempts to import module names from settings file. Loads the config
for each module and initializes the module. Tries to run the module and
checks if the images could be generated correctly.
Generated images can be found in the /images folder of Inkycal.
"""
print(f'Inkycal version: v{self._release}')
print(f'Selected E-paper display: {self.settings["model"]}')
@ -183,8 +203,12 @@ class Inkycal:
del errors
def run(self):
"""Runs the main inykcal program nonstop (cannot be stopped anymore!)
Will show something on the display if render was set to True"""
"""Runs main programm in nonstop mode.
Uses a 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.
"""
# Get the time of initial run
runtime = arrow.now()
@ -411,8 +435,11 @@ class Inkycal:
return image
def calibrate(self):
"""Calibrate the ePaper display to prevent burn-ins (ghosting)
use this command to manually calibrate the display"""
"""Calibrate the E-Paper display
Uses the Display class to calibrate the display with the default of 3
cycles. After a refresh cycle, a new image is generated and shown.
"""
self.Display.calibrate()

View File

@ -47,6 +47,9 @@ class Inkyimage(inkycal_module):
}
# TODO: thorough testing and code cleanup
# TODO: presentation mode (cycle through images in folder)
def __init__(self, config):
"""Initialize inkycal_rss module"""