Improved documentation
This commit is contained in:
parent
0acd5c5c17
commit
418422fa52
@ -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'
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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"""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user