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()]
|
available_fonts = [key for key,values in fonts.items()]
|
||||||
|
|
||||||
def get_fonts():
|
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:
|
for fonts in available_fonts:
|
||||||
print(fonts)
|
print(fonts)
|
||||||
|
|
||||||
|
|
||||||
def get_system_tz():
|
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:
|
try:
|
||||||
local_tz = time.tzname[1]
|
local_tz = time.tzname[1]
|
||||||
except:
|
except:
|
||||||
@ -57,8 +85,21 @@ def get_system_tz():
|
|||||||
|
|
||||||
|
|
||||||
def auto_fontsize(font, max_height):
|
def auto_fontsize(font, max_height):
|
||||||
"""Adjust the fontsize to fit 80% of max_height
|
"""Scales a given font to 80% of max_height.
|
||||||
returns the font object with modified size"""
|
|
||||||
|
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]
|
fontsize = font.getsize('hg')[1]
|
||||||
while font.getsize('hg')[1] <= (max_height * 0.80):
|
while font.getsize('hg')[1] <= (max_height * 0.80):
|
||||||
fontsize += 1
|
fontsize += 1
|
||||||
@ -67,12 +108,29 @@ def auto_fontsize(font, max_height):
|
|||||||
|
|
||||||
|
|
||||||
def write(image, xy, box_size, text, font=None, **kwargs):
|
def write(image, xy, box_size, text, font=None, **kwargs):
|
||||||
"""Write text on specified image
|
"""Writes text on a image.
|
||||||
image = on which image should the text be added?
|
|
||||||
xy = (x,y) coordinates as tuple -> (x,y)
|
Writes given text at given position on the specified image.
|
||||||
box_size = size of text-box -> (width,height)
|
|
||||||
text = string (what to write)
|
Args:
|
||||||
font = which font to use
|
- 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',
|
allowed_kwargs = ['alignment', 'autofit', 'colour', 'rotation',
|
||||||
'fill_width', 'fill_height']
|
'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):
|
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 = []
|
lines = []
|
||||||
if font.getsize(text)[0] < max_width:
|
if font.getsize(text)[0] < max_width:
|
||||||
lines.append(text)
|
lines.append(text)
|
||||||
@ -162,7 +232,20 @@ def text_wrap(text, font=None, max_width = None):
|
|||||||
|
|
||||||
|
|
||||||
def internet_available():
|
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:
|
try:
|
||||||
urlopen('https://google.com',timeout=5)
|
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)):
|
def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1,0.1)):
|
||||||
"""Draws a border with round corners at (x,y)
|
"""Draws a border at given coordinates.
|
||||||
xy = position e.g: (5,10)
|
|
||||||
size = size of border (width, height), radius: corner radius
|
Args:
|
||||||
thickness = border thickness
|
- image: The image on which the border should be drawn (usually im_black or
|
||||||
shrinkage = shrink and center border by given percentage:(width_%, height_%)
|
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'
|
colour='black'
|
||||||
|
@ -12,7 +12,15 @@ import glob
|
|||||||
|
|
||||||
class Display:
|
class Display:
|
||||||
"""Display class for inkycal
|
"""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):
|
def __init__(self, epaper_model):
|
||||||
"""Load the drivers for this 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')
|
raise Exception('SPI could not be found. Please check if SPI is enabled')
|
||||||
|
|
||||||
def render(self, im_black, im_colour = None):
|
def render(self, im_black, im_colour = None):
|
||||||
"""Render an image on the epaper
|
"""Renders an image on the selected E-Paper display.
|
||||||
im_colour is required for three-colour epapers"""
|
|
||||||
|
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
|
epaper = self._epaper
|
||||||
|
|
||||||
@ -65,9 +106,23 @@ class Display:
|
|||||||
print('Done')
|
print('Done')
|
||||||
|
|
||||||
def calibrate(self, cycles=3):
|
def calibrate(self, cycles=3):
|
||||||
"""Flush display with single colour to prevent burn-ins (ghosting)
|
"""Calibrates the display to retain crisp colours
|
||||||
cycles -> int. How many times should each colour be flushed?
|
|
||||||
recommended cycles = 3"""
|
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 = self._epaper
|
||||||
epaper.init()
|
epaper.init()
|
||||||
@ -102,7 +157,21 @@ class Display:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_display_size(cls, model_name):
|
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):
|
if not isinstance(model_name, str):
|
||||||
print('model_name should be a string')
|
print('model_name should be a string')
|
||||||
return
|
return
|
||||||
@ -110,6 +179,8 @@ class Display:
|
|||||||
driver_files = top_level+'/inkycal/display/drivers/*.py'
|
driver_files = top_level+'/inkycal/display/drivers/*.py'
|
||||||
drivers = glob.glob(driver_files)
|
drivers = glob.glob(driver_files)
|
||||||
drivers = [i.split('/')[-1].split('.')[0] for i in drivers]
|
drivers = [i.split('/')[-1].split('.')[0] for i in drivers]
|
||||||
|
drivers.remove('__init__')
|
||||||
|
drivers.remove('epdconfig')
|
||||||
if model_name not in drivers:
|
if model_name not in drivers:
|
||||||
print('This model name was not found. Please double check your spellings')
|
print('This model name was not found. Please double check your spellings')
|
||||||
return
|
return
|
||||||
@ -122,6 +193,28 @@ class Display:
|
|||||||
height = int(line.rstrip().replace(" ", "").split('=')[-1])
|
height = int(line.rstrip().replace(" ", "").split('=')[-1])
|
||||||
return width, height
|
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__':
|
if __name__ == '__main__':
|
||||||
print("Running Display class in standalone mode")
|
print("Running Display class in standalone mode")
|
||||||
|
@ -24,8 +24,9 @@ except ImportError:
|
|||||||
try:
|
try:
|
||||||
import numpy
|
import numpy
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print('numpy is not installed! Please install with:')
|
print('numpy is not installed!. \nIf you are on Windows '
|
||||||
print('pip3 install numpy')
|
'run: pip3 install numpy \nIf you are on Raspberry Pi '
|
||||||
|
'remove numpy: pip3 uninstall numpy \nThen try again.')
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level = logging.INFO, #DEBUG > #INFO > #ERROR > #WARNING > #CRITICAL
|
level = logging.INFO, #DEBUG > #INFO > #ERROR > #WARNING > #CRITICAL
|
||||||
@ -34,16 +35,31 @@ logging.basicConfig(
|
|||||||
|
|
||||||
logger = logging.getLogger('inykcal main')
|
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:
|
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):
|
def __init__(self, settings_path=None, render=True):
|
||||||
"""Initialise Inkycal
|
"""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
|
|
||||||
|
|
||||||
render = bool (True/False) -> show the image on the epaper display?
|
|
||||||
"""
|
|
||||||
self._release = '2.0.0'
|
self._release = '2.0.0'
|
||||||
|
|
||||||
# Check if render was set correctly
|
# Check if render was set correctly
|
||||||
@ -87,7 +103,6 @@ class Inkycal:
|
|||||||
# check if colours can be rendered
|
# check if colours can be rendered
|
||||||
self.supports_colour = True if 'colour' in settings['model'] else False
|
self.supports_colour = True if 'colour' in settings['model'] else False
|
||||||
|
|
||||||
|
|
||||||
# get calibration hours
|
# get calibration hours
|
||||||
self._calibration_hours = self.settings['calibration_hours']
|
self._calibration_hours = self.settings['calibration_hours']
|
||||||
|
|
||||||
@ -152,9 +167,14 @@ class Inkycal:
|
|||||||
|
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
"""Inkycal test run
|
"""Tests if Inkycal can run without issues.
|
||||||
Generates images for each module, one by one and prints OK if no
|
|
||||||
problems were found."""
|
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'Inkycal version: v{self._release}')
|
||||||
print(f'Selected E-paper display: {self.settings["model"]}')
|
print(f'Selected E-paper display: {self.settings["model"]}')
|
||||||
@ -183,8 +203,12 @@ class Inkycal:
|
|||||||
del errors
|
del errors
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Runs the main inykcal program nonstop (cannot be stopped anymore!)
|
"""Runs main programm in nonstop mode.
|
||||||
Will show something on the display if render was set to True"""
|
|
||||||
|
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
|
# Get the time of initial run
|
||||||
runtime = arrow.now()
|
runtime = arrow.now()
|
||||||
@ -411,8 +435,11 @@ class Inkycal:
|
|||||||
return image
|
return image
|
||||||
|
|
||||||
def calibrate(self):
|
def calibrate(self):
|
||||||
"""Calibrate the ePaper display to prevent burn-ins (ghosting)
|
"""Calibrate the E-Paper display
|
||||||
use this command to manually calibrate the 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()
|
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):
|
def __init__(self, config):
|
||||||
"""Initialize inkycal_rss module"""
|
"""Initialize inkycal_rss module"""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user