Improved formatting

This commit is contained in:
aceisace
2022-04-02 01:30:17 +02:00
parent 9bf4385b6b
commit 5b032d6231
42 changed files with 3644 additions and 3661 deletions

View File

@@ -1,4 +1,4 @@
# Display class (for dirving E-Paper displays) # Display class (for driving E-Paper displays)
from inkycal.display import Display from inkycal.display import Display
# Default modules # Default modules

View File

@@ -16,7 +16,7 @@ logs.setLevel(level=logging.INFO)
# Get the path to the Inkycal folder # Get the path to the Inkycal folder
top_level = os.path.dirname( top_level = os.path.dirname(
os.path.abspath(os.path.dirname(__file__))).split('/inkycal')[0] os.path.abspath(os.path.dirname(__file__))).split('/inkycal')[0]
# Get path of 'fonts' and 'images' folders within Inkycal folder # Get path of 'fonts' and 'images' folders within Inkycal folder
fonts_location = top_level + '/fonts/' fonts_location = top_level + '/fonts/'
@@ -25,290 +25,290 @@ images = top_level + '/images/'
# Get available fonts within fonts folder # Get available fonts within fonts folder
fonts = {} fonts = {}
for path,dirs,files in os.walk(fonts_location): for path, dirs, files in os.walk(fonts_location):
for filename in files: for filename in files:
if filename.endswith('.otf'): if filename.endswith('.otf'):
name = filename.split('.otf')[0] name = filename.split('.otf')[0]
fonts[name] = os.path.join(path, filename) fonts[name] = os.path.join(path, filename)
if filename.endswith('.ttf'): if filename.endswith('.ttf'):
name = filename.split('.ttf')[0] name = filename.split('.ttf')[0]
fonts[name] = os.path.join(path, filename) fonts[name] = os.path.join(path, filename)
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 Inkycal and displays all fonts found in Searches the /font folder in Inkycal and displays all fonts found in
there. there.
Returns: Returns:
printed output of all available fonts. To access a fontfile, use the printed output of all available fonts. To access a fontfile, use the
fonts dictionary to access it. fonts dictionary to access it.
>>> fonts['fontname'] >>> fonts['fontname']
To use a font, use the following sytax, where fontname is one of the To use a font, use the following sytax, where fontname is one of the
printed fonts of this function: printed fonts of this function:
>>> ImageFont.truetype(fonts['fontname'], size = 10) >>> 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():
"""Gets the system-timezone """Gets the system-timezone
Gets the timezone set by the system. Gets the timezone set by the system.
Returns: Returns:
- A timezone if a system timezone was found. - A timezone if a system timezone was found.
- None if no 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. The extracted timezone can be used to show the local time instead of UTC. e.g.
>>> import arrow >>> import arrow
>>> print(arrow.now()) # returns non-timezone-aware time >>> print(arrow.now()) # returns non-timezone-aware time
>>> print(arrow.now(tz=get_system_tz()) # prints 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:
print('System timezone could not be parsed!') print('System timezone could not be parsed!')
print('Please set timezone manually!. Setting timezone to None...') print('Please set timezone manually!. Setting timezone to None...')
local_tz = None local_tz = None
return local_tz return local_tz
def auto_fontsize(font, max_height): def auto_fontsize(font, max_height):
"""Scales a given font to 80% of max_height. """Scales a given font to 80% of max_height.
Gets the height of a font and scales it until 80% of the max_height Gets the height of a font and scales it until 80% of the max_height
is filled. is filled.
Args: Args:
- font: A PIL Font object. - font: A PIL Font object.
- max_height: An integer representing the height to adjust the font to - max_height: An integer representing the height to adjust the font to
which the given font should be scaled to. which the given font should be scaled to.
Returns: Returns:
A PIL font object with modified height. 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
font = ImageFont.truetype(font.path, fontsize) font = ImageFont.truetype(font.path, fontsize)
return font return font
def write(image, xy, box_size, text, font=None, **kwargs): def write(image, xy, box_size, text, font=None, **kwargs):
"""Writes text on a image. """Writes text on a image.
Writes given text at given position on the specified image. Writes given text at given position on the specified image.
Args: Args:
- image: The image to draw this text on, usually im_black or im_colour. - 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. - xy: tuple-> (x,y) representing the x and y co-ordinate.
- box_size: tuple -> (width, height) representing the size of the text box. - box_size: tuple -> (width, height) representing the size of the text box.
- text: string, the actual text to add on the image. - text: string, the actual text to add on the image.
- font: A PIL Font object e.g. - font: A PIL Font object e.g.
ImageFont.truetype(fonts['fontname'], size = 10). ImageFont.truetype(fonts['fontname'], size = 10).
Args: (optional) Args: (optional)
- alignment: alignment of the text, use 'center', 'left', 'right'. - alignment: alignment of the text, use 'center', 'left', 'right'.
- autofit: bool (True/False). Automatically increases fontsize to fill in - autofit: bool (True/False). Automatically increases fontsize to fill in
as much of the box-height as possible. as much of the box-height as possible.
- colour: black by default, do not change as it causes issues with rendering - colour: black by default, do not change as it causes issues with rendering
on e-Paper. on e-Paper.
- rotation: Rotate the text with the text-box by a given angle anti-clockwise. - 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 - 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. 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 - 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. 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']
# Validate kwargs # Validate kwargs
for key, value in kwargs.items(): for key, value in kwargs.items():
if key not in allowed_kwargs: if key not in allowed_kwargs:
print('{0} does not exist'.format(key)) print('{0} does not exist'.format(key))
# Set kwargs if given, it not, use defaults # Set kwargs if given, it not, use defaults
alignment = kwargs['alignment'] if 'alignment' in kwargs else 'center' alignment = kwargs['alignment'] if 'alignment' in kwargs else 'center'
autofit = kwargs['autofit'] if 'autofit' in kwargs else False autofit = kwargs['autofit'] if 'autofit' in kwargs else False
fill_width = kwargs['fill_width'] if 'fill_width' in kwargs else 1.0 fill_width = kwargs['fill_width'] if 'fill_width' in kwargs else 1.0
fill_height = kwargs['fill_height'] if 'fill_height' in kwargs else 0.8 fill_height = kwargs['fill_height'] if 'fill_height' in kwargs else 0.8
colour = kwargs['colour'] if 'colour' in kwargs else 'black' colour = kwargs['colour'] if 'colour' in kwargs else 'black'
rotation = kwargs['rotation'] if 'rotation' in kwargs else None rotation = kwargs['rotation'] if 'rotation' in kwargs else None
x,y = xy x, y = xy
box_width, box_height = box_size box_width, box_height = box_size
# Increase fontsize to fit specified height and width of text box
if (autofit == True) or (fill_width != 1.0) or (fill_height != 0.8):
size = 8
font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
while (text_width < int(box_width * fill_width) and
text_height < int(box_height * fill_height)):
size += 1
font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
# Increase fontsize to fit specified height and width of text box
if (autofit == True) or (fill_width != 1.0) or (fill_height != 0.8):
size = 8
font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
while (text_width < int(box_width * fill_width) and
text_height < int(box_height * fill_height)):
size += 1
font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] # Truncate text if text is too long so it can fit inside the box
if (text_width, text_height) > (box_width, box_height):
logs.debug(('truncating {}'.format(text)))
while (text_width, text_height) > (box_width, box_height):
text = text[0:-1]
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
logs.debug((text))
# Align text to desired position
if alignment == "center" or None:
x = int((box_width / 2) - (text_width / 2))
elif alignment == 'left':
x = 0
elif alignment == 'right':
x = int(box_width - text_width)
y = int((box_height / 2) - (text_height / 2))
# Draw the text in the text-box
draw = ImageDraw.Draw(image)
space = Image.new('RGBA', (box_width, box_height))
ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font)
# Uncomment following two lines, comment out above two lines to show
# red text-box with white text (debugging purposes)
# space = Image.new('RGBA', (box_width, box_height), color= 'red')
# ImageDraw.Draw(space).text((x, y), text, fill='white', font=font)
if rotation != None:
space.rotate(rotation, expand=True)
# Update only region with text (add text with transparent background)
image.paste(space, xy, space)
# Truncate text if text is too long so it can fit inside the box def text_wrap(text, font=None, max_width=None):
if (text_width, text_height) > (box_width, box_height): """Splits a very long text into smaller parts
logs.debug(('truncating {}'.format(text)))
while (text_width, text_height) > (box_width, box_height):
text=text[0:-1]
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
logs.debug((text))
# Align text to desired position Splits a long text to smaller lines which can fit in a line with max_width.
if alignment == "center" or None: Uses a Font object for more accurate calculations.
x = int((box_width / 2) - (text_width / 2))
elif alignment == 'left':
x = 0
elif alignment == 'right':
x = int(box_width - text_width)
y = int((box_height / 2) - (text_height / 2)) 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.
# Draw the text in the text-box Returns:
draw = ImageDraw.Draw(image) A list containing chunked strings of the full text.
space = Image.new('RGBA', (box_width, box_height)) """
ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font) lines = []
if font.getsize(text)[0] < max_width:
# Uncomment following two lines, comment out above two lines to show lines.append(text)
# red text-box with white text (debugging purposes) else:
words = text.split(' ')
#space = Image.new('RGBA', (box_width, box_height), color= 'red') i = 0
#ImageDraw.Draw(space).text((x, y), text, fill='white', font=font) while i < len(words):
line = ''
if rotation != None: while i < len(words) and font.getsize(line + words[i])[0] <= max_width:
space.rotate(rotation, expand = True) line = line + words[i] + " "
i += 1
# Update only region with text (add text with transparent background) if not line:
image.paste(space, xy, space) line = words[i]
i += 1
lines.append(line)
def text_wrap(text, font=None, max_width = None): return lines
"""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)
else:
words = text.split(' ')
i = 0
while i < len(words):
line = ''
while i < len(words) and font.getsize(line + words[i])[0] <= max_width:
line = line + words[i] + " "
i += 1
if not line:
line = words[i]
i += 1
lines.append(line)
return lines
def internet_available(): def internet_available():
"""checks 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 Attempts to connect to google.com with a timeout of 5 seconds to check
if the network can be reached. if the network can be reached.
Returns: Returns:
- True if connection could be established. - True if connection could be established.
- False if the internet could not be reached. - False if the internet could not be reached.
Returned output can be used to add a check for internet availability: Returned output can be used to add a check for internet availability:
>>> if internet_available() == True: >>> if internet_available() == True:
>>> #...do something that requires internet connectivity >>> #...do something that requires internet connectivity
""" """
try: try:
urlopen('https://google.com',timeout=5) urlopen('https://google.com', timeout=5)
return True return True
except URLError as err: except:
return False return False
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 at given coordinates. """Draws a border at given coordinates.
Args: Args:
- image: The image on which the border should be drawn (usually im_black or - image: The image on which the border should be drawn (usually im_black or
im_colour. im_colour.
- xy: Tuple representing the top-left corner of the border e.g. (32, 100) - 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. where 32 is the x co-ordinate and 100 is the y-coordinate.
- size: Size of the border as a tuple -> (width, height). - size: Size of the border as a tuple -> (width, height).
- radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners. - radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners.
- thickness: Thickness of the border in pixels. - thickness: Thickness of the border in pixels.
- shrinkage: A tuple containing decimals presenting a percentage of shrinking - shrinkage: A tuple containing decimals presenting a percentage of shrinking
-> (width_shrink_percentage, height_shrink_percentage). -> (width_shrink_percentage, height_shrink_percentage).
e.g. (0.1, 0.2) ~ shrinks the width of border by 10%, shrinks height of e.g. (0.1, 0.2) ~ shrinks the width of border by 10%, shrinks height of
border by 20% border by 20%
""" """
colour='black' colour = 'black'
# size from function paramter # size from function paramter
width, height = int(size[0]*(1-shrinkage[0])), int(size[1]*(1-shrinkage[1])) width, height = int(size[0] * (1 - shrinkage[0])), int(size[1] * (1 - shrinkage[1]))
# shift cursor to move rectangle to center # shift cursor to move rectangle to center
offset_x, offset_y = int((size[0] - width)/2), int((size[1]- height)/2) offset_x, offset_y = int((size[0] - width) / 2), int((size[1] - height) / 2)
x, y, diameter = xy[0]+offset_x, xy[1]+offset_y, radius*2 x, y, diameter = xy[0] + offset_x, xy[1] + offset_y, radius * 2
# lenght of rectangle size # lenght of rectangle size
a,b = (width - diameter), (height-diameter) a, b = (width - diameter), (height - diameter)
# Set coordinates for staright lines # Set coordinates for staright lines
p1, p2 = (x+radius, y), (x+radius+a, y) p1, p2 = (x + radius, y), (x + radius + a, y)
p3, p4 = (x+width, y+radius), (x+width, y+radius+b) p3, p4 = (x + width, y + radius), (x + width, y + radius + b)
p5, p6 = (p2[0], y+height), (p1[0], y+height) p5, p6 = (p2[0], y + height), (p1[0], y + height)
p7, p8 = (x, p4[1]), (x,p3[1]) p7, p8 = (x, p4[1]), (x, p3[1])
if radius != 0: if radius != 0:
# Set coordinates for arcs # Set coordinates for arcs
c1, c2 = (x,y), (x+diameter, y+diameter) c1, c2 = (x, y), (x + diameter, y + diameter)
c3, c4 = ((x+width)-diameter, y), (x+width, y+diameter) c3, c4 = ((x + width) - diameter, y), (x + width, y + diameter)
c5, c6 = ((x+width)-diameter, (y+height)-diameter), (x+width, y+height) c5, c6 = ((x + width) - diameter, (y + height) - diameter), (x + width, y + height)
c7, c8 = (x, (y+height)-diameter), (x+diameter, y+height) c7, c8 = (x, (y + height) - diameter), (x + diameter, y + height)
# Draw lines and arcs, creating a square with round corners # Draw lines and arcs, creating a square with round corners
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
draw.line( (p1, p2) , fill=colour, width = thickness) draw.line((p1, p2), fill=colour, width=thickness)
draw.line( (p3, p4) , fill=colour, width = thickness) draw.line((p3, p4), fill=colour, width=thickness)
draw.line( (p5, p6) , fill=colour, width = thickness) draw.line((p5, p6), fill=colour, width=thickness)
draw.line( (p7, p8) , fill=colour, width = thickness) draw.line((p7, p8), fill=colour, width=thickness)
if radius != 0: if radius != 0:
draw.arc( (c1, c2) , 180, 270, fill=colour, width=thickness) draw.arc((c1, c2), 180, 270, fill=colour, width=thickness)
draw.arc( (c3, c4) , 270, 360, fill=colour, width=thickness) draw.arc((c3, c4), 270, 360, fill=colour, width=thickness)
draw.arc( (c5, c6) , 0, 90, fill=colour, width=thickness) draw.arc((c5, c6), 0, 90, fill=colour, width=thickness)
draw.arc( (c7, c8) , 90, 180, fill=colour, width=thickness) draw.arc((c7, c8), 90, 180, fill=colour, width=thickness)

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
9.7" driver class 9.7" driver class
Copyright by aceisace Copyright by aceisace
@@ -9,42 +9,42 @@ from os.path import exists
from PIL import Image from PIL import Image
# Display resolution # Display resolution
EPD_WIDTH = 1200 EPD_WIDTH = 1200
EPD_HEIGHT = 825 EPD_HEIGHT = 825
driver_dir = top_level + '/inkycal/display/drivers/9_in_7_drivers/'
driver_dir = top_level+'/inkycal/display/drivers/9_in_7_drivers/'
class EPD: class EPD:
def __init__(self): def __init__(self):
"""9.7" epaper class""" """9.7" epaper class"""
# Check if zipped folders are present, if yes, assume # Check if zipped folders are present, if yes, assume
# drivers have not been installed yet # drivers have not been installed yet
if exists(f'{driver_dir}IT8951.zip'): if exists(f'{driver_dir}IT8951.zip'):
print('Additional steps are required to install drivers for 9.7" E-Paper. ' print('Additional steps are required to install drivers for 9.7" E-Paper. '
'Please run the following command in Terminal, then retry:\n' 'Please run the following command in Terminal, then retry:\n'
f'bash {driver_dir}install.sh') f'bash {driver_dir}install.sh')
def init(self): def init(self):
pass pass
def display(self, command): def display(self, command):
"""displays an image""" """displays an image"""
try: try:
run_command = command.split() run_command = command.split()
run(run_command) run(run_command)
except: except:
print("oops, something didn't work right :/") print("oops, something didn't work right :/")
def getbuffer(self, image): def getbuffer(self, image):
"""ad-hoc""" """ad-hoc"""
image = image.rotate(90, expand=True) image = image.rotate(90, expand=True)
image.convert('RGB').save(images+'canvas.bmp', 'BMP') image.convert('RGB').save(images + 'canvas.bmp', 'BMP')
command = 'sudo {}IT8951/IT8951 0 0 {}'.format(driver_dir, images+'canvas.bmp') command = 'sudo {}IT8951/IT8951 0 0 {}'.format(driver_dir, images + 'canvas.bmp')
#print(command) # print(command)
return command return command
def sleep(self):
pass
def sleep(self):
pass

View File

@@ -34,13 +34,14 @@ from PIL import Image
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
# Display resolution # Display resolution
EPD_WIDTH = 400 EPD_WIDTH = 400
EPD_HEIGHT = 300 EPD_HEIGHT = 300
GRAY1 = 0xff # white
GRAY2 = 0xC0
GRAY3 = 0x80 # gray
GRAY4 = 0x00 # Blackest
GRAY1 = 0xff #white
GRAY2 = 0xC0
GRAY3 = 0x80 #gray
GRAY4 = 0x00 #Blackest
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -50,117 +51,117 @@ class EPD:
self.cs_pin = epdconfig.CS_PIN self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH self.width = EPD_WIDTH
self.height = EPD_HEIGHT self.height = EPD_HEIGHT
self.GRAY1 = GRAY1 #white self.GRAY1 = GRAY1 # white
self.GRAY2 = GRAY2 self.GRAY2 = GRAY2
self.GRAY3 = GRAY3 #gray self.GRAY3 = GRAY3 # gray
self.GRAY4 = GRAY4 #Blackest self.GRAY4 = GRAY4 # Blackest
lut_vcom0 = [ lut_vcom0 = [
0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02,
0x00, 0x17, 0x17, 0x00, 0x00, 0x02, 0x00, 0x17, 0x17, 0x00, 0x00, 0x02,
0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x01, 0x00, 0x00, 0x01,
0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
lut_ww = [ lut_ww = [
0x40, 0x17, 0x00, 0x00, 0x00, 0x02, 0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
lut_bw = [ lut_bw = [
0x40, 0x17, 0x00, 0x00, 0x00, 0x02, 0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
lut_wb = [ lut_wb = [
0x80, 0x17, 0x00, 0x00, 0x00, 0x02, 0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, 0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
lut_bb = [ lut_bb = [
0x80, 0x17, 0x00, 0x00, 0x00, 0x02, 0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, 0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
#******************************gray*********************************/ # ******************************gray*********************************/
#0~3 gray # 0~3 gray
EPD_4IN2_4Gray_lut_vcom =[ EPD_4IN2_4Gray_lut_vcom = [
0x00 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x60 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, 0x60, 0x14, 0x14, 0x00, 0x00, 0x01,
0x00 ,0x14 ,0x00 ,0x00 ,0x00 ,0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
0x00 ,0x13 ,0x0A ,0x01 ,0x00 ,0x01, 0x00, 0x13, 0x0A, 0x01, 0x00, 0x01,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
] ]
#R21 # R21
EPD_4IN2_4Gray_lut_ww =[ EPD_4IN2_4Gray_lut_ww = [
0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, 0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, 0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
0x10 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, 0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
0xA0 ,0x13 ,0x01 ,0x00 ,0x00 ,0x01, 0xA0, 0x13, 0x01, 0x00, 0x00, 0x01,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
#R22H r # R22H r
EPD_4IN2_4Gray_lut_bw =[ EPD_4IN2_4Gray_lut_bw = [
0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, 0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, 0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
0x00 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, 0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
0x99 ,0x0C ,0x01 ,0x03 ,0x04 ,0x01, 0x99, 0x0C, 0x01, 0x03, 0x04, 0x01,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
#R23H w # R23H w
EPD_4IN2_4Gray_lut_wb =[ EPD_4IN2_4Gray_lut_wb = [
0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, 0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, 0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
0x00 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, 0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
0x99 ,0x0B ,0x04 ,0x04 ,0x01 ,0x01, 0x99, 0x0B, 0x04, 0x04, 0x01, 0x01,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
#R24H b # R24H b
EPD_4IN2_4Gray_lut_bb =[ EPD_4IN2_4Gray_lut_bb = [
0x80 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, 0x80, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, 0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
0x20 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, 0x20, 0x14, 0x0A, 0x00, 0x00, 0x01,
0x50 ,0x13 ,0x01 ,0x00 ,0x00 ,0x01, 0x50, 0x13, 0x01, 0x00, 0x00, 0x01,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
] ]
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(10) epdconfig.delay_ms(10)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -173,306 +174,309 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
self.send_command(0x71) self.send_command(0x71)
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
self.send_command(0x71) self.send_command(0x71)
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
def set_lut(self): def set_lut(self):
self.send_command(0x20) # vcom self.send_command(0x20) # vcom
for count in range(0, 44): for count in range(0, 44):
self.send_data(self.lut_vcom0[count]) self.send_data(self.lut_vcom0[count])
self.send_command(0x21) # ww -- self.send_command(0x21) # ww --
for count in range(0, 42): for count in range(0, 42):
self.send_data(self.lut_ww[count]) self.send_data(self.lut_ww[count])
self.send_command(0x22) # bw r self.send_command(0x22) # bw r
for count in range(0, 42): for count in range(0, 42):
self.send_data(self.lut_bw[count]) self.send_data(self.lut_bw[count])
self.send_command(0x23) # wb w self.send_command(0x23) # wb w
for count in range(0, 42): for count in range(0, 42):
self.send_data(self.lut_bb[count]) self.send_data(self.lut_bb[count])
self.send_command(0x24) # bb b self.send_command(0x24) # bb b
for count in range(0, 42): for count in range(0, 42):
self.send_data(self.lut_wb[count]) self.send_data(self.lut_wb[count])
def Gray_SetLut(self): def Gray_SetLut(self):
self.send_command(0x20) #vcom self.send_command(0x20) # vcom
for count in range(0, 42): for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_vcom[count]) self.send_data(self.EPD_4IN2_4Gray_lut_vcom[count])
self.send_command(0x21) #red not use self.send_command(0x21) # red not use
for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_ww[count])
self.send_command(0x22) #bw r
for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_bw[count])
self.send_command(0x23) #wb w
for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_wb[count])
self.send_command(0x24) #bb b
for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_bb[count])
self.send_command(0x25) #vcom
for count in range(0, 42): for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_ww[count]) self.send_data(self.EPD_4IN2_4Gray_lut_ww[count])
self.send_command(0x22) # bw r
for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_bw[count])
self.send_command(0x23) # wb w
for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_wb[count])
self.send_command(0x24) # bb b
for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_bb[count])
self.send_command(0x25) # vcom
for count in range(0, 42):
self.send_data(self.EPD_4IN2_4Gray_lut_ww[count])
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
# EPD hardware init start # EPD hardware init start
self.reset() self.reset()
self.send_command(0x01) # POWER SETTING
self.send_data(0x03) # VDS_EN, VDG_EN
self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0]
self.send_data(0x2b) # VDH
self.send_data(0x2b) # VDL
self.send_command(0x06) # boost soft start
self.send_data(0x17)
self.send_data(0x17)
self.send_data(0x17)
self.send_command(0x04) # POWER_ON
self.ReadBusy()
self.send_command(0x00) # panel setting
self.send_data(0xbf) # KW-BF KWR-AF BWROTP 0f
self.send_data(0x0d)
self.send_command(0x30) # PLL setting
self.send_data(0x3c) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
self.send_command(0x61) # resolution setting self.send_command(0x01) # POWER SETTING
self.send_data(0x03) # VDS_EN, VDG_EN
self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0]
self.send_data(0x2b) # VDH
self.send_data(0x2b) # VDL
self.send_command(0x06) # boost soft start
self.send_data(0x17)
self.send_data(0x17)
self.send_data(0x17)
self.send_command(0x04) # POWER_ON
self.ReadBusy()
self.send_command(0x00) # panel setting
self.send_data(0xbf) # KW-BF KWR-AF BWROTP 0f
self.send_data(0x0d)
self.send_command(0x30) # PLL setting
self.send_data(0x3c) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
self.send_command(0x61) # resolution setting
self.send_data(0x01)
self.send_data(0x90) # 128
self.send_data(0x01) self.send_data(0x01)
self.send_data(0x90) # 128
self.send_data(0x01)
self.send_data(0x2c) self.send_data(0x2c)
self.send_command(0x82) # vcom_DC setting self.send_command(0x82) # vcom_DC setting
self.send_data(0x28) self.send_data(0x28)
self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING
self.send_data(0x97) # 97white border 77black border VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 self.send_data(
0x97) # 97white border 77black border VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7
self.set_lut() self.set_lut()
# EPD hardware init end # EPD hardware init end
return 0 return 0
def Init_4Gray(self): def Init_4Gray(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
# EPD hardware init start # EPD hardware init start
self.reset() self.reset()
self.send_command(0x01) #POWER SETTING
self.send_data (0x03)
self.send_data (0x00) #VGH=20V,VGL=-20V
self.send_data (0x2b) #VDH=15V
self.send_data (0x2b) #VDL=-15V
self.send_data (0x13)
self.send_command(0x06) #booster soft start self.send_command(0x01) # POWER SETTING
self.send_data (0x17) #A self.send_data(0x03)
self.send_data (0x17) #B self.send_data(0x00) # VGH=20V,VGL=-20V
self.send_data (0x17) #C self.send_data(0x2b) # VDH=15V
self.send_data(0x2b) # VDL=-15V
self.send_data(0x13)
self.send_command(0x06) # booster soft start
self.send_data(0x17) # A
self.send_data(0x17) # B
self.send_data(0x17) # C
self.send_command(0x04) self.send_command(0x04)
self.ReadBusy() self.ReadBusy()
self.send_command(0x00) #panel setting self.send_command(0x00) # panel setting
self.send_data(0x3f) #KW-3f KWR-2F BWROTP 0f BWOTP 1f self.send_data(0x3f) # KW-3f KWR-2F BWROTP 0f BWOTP 1f
self.send_command(0x30) #PLL setting self.send_command(0x30) # PLL setting
self.send_data (0x3c) #100hz self.send_data(0x3c) # 100hz
self.send_command(0x61) #resolution setting self.send_command(0x61) # resolution setting
self.send_data (0x01) #400 self.send_data(0x01) # 400
self.send_data (0x90) self.send_data(0x90)
self.send_data (0x01) #300 self.send_data(0x01) # 300
self.send_data (0x2c) self.send_data(0x2c)
self.send_command(0x82) #vcom_DC setting self.send_command(0x82) # vcom_DC setting
self.send_data (0x12) self.send_data(0x12)
self.send_command(0X50) #VCOM AND DATA INTERVAL SETTING self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING
self.send_data(0x97) self.send_data(0x97)
def getbuffer(self, image): def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 8) * self.height)
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Horizontal") logging.debug("Horizontal")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf return buf
def getbuffer_4Gray(self, image): def getbuffer_4Gray(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width / 4) * self.height) buf = [0xFF] * (int(self.width / 4) * self.height)
image_monocolor = image.convert('L') image_monocolor = image.convert('L')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
i=0 i = 0
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if(pixels[x, y] == 0xC0): if (pixels[x, y] == 0xC0):
pixels[x, y] = 0x80 pixels[x, y] = 0x80
elif (pixels[x, y] == 0x80): elif (pixels[x, y] == 0x80):
pixels[x, y] = 0x40 pixels[x, y] = 0x40
i= i+1 i = i + 1
if(i%4 == 0): if (i % 4 == 0):
buf[int((x + (y * self.width))/4)] = ((pixels[x-3, y]&0xc0) | (pixels[x-2, y]&0xc0)>>2 | (pixels[x-1, y]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) buf[int((x + (y * self.width)) / 4)] = (
(pixels[x - 3, y] & 0xc0) | (pixels[x - 2, y] & 0xc0) >> 2 | (
elif(imwidth == self.height and imheight == self.width): pixels[x - 1, y] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
elif (imwidth == self.height and imheight == self.width):
logging.debug("Horizontal") logging.debug("Horizontal")
for x in range(imwidth): for x in range(imwidth):
for y in range(imheight): for y in range(imheight):
newx = y newx = y
newy = x newy = x
if(pixels[x, y] == 0xC0): if (pixels[x, y] == 0xC0):
pixels[x, y] = 0x80 pixels[x, y] = 0x80
elif (pixels[x, y] == 0x80): elif (pixels[x, y] == 0x80):
pixels[x, y] = 0x40 pixels[x, y] = 0x40
i= i+1 i = i + 1
if(i%4 == 0): if (i % 4 == 0):
buf[int((newx + (newy * self.width))/4)] = ((pixels[x, y-3]&0xc0) | (pixels[x, y-2]&0xc0)>>2 | (pixels[x, y-1]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) buf[int((newx + (newy * self.width)) / 4)] = (
(pixels[x, y - 3] & 0xc0) | (pixels[x, y - 2] & 0xc0) >> 2 | (
pixels[x, y - 1] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
return buf return buf
def display(self, image): def display(self, image):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF) self.send_data(0xFF)
self.send_command(0x13) self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(image[i]) self.send_data(image[i])
self.send_command(0x12) self.send_command(0x12)
self.ReadBusy() self.ReadBusy()
def display_4Gray(self, image): def display_4Gray(self, image):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, EPD_WIDTH * EPD_HEIGHT / 8): # EPD_WIDTH * EPD_HEIGHT / 4 for i in range(0, EPD_WIDTH * EPD_HEIGHT / 8): # EPD_WIDTH * EPD_HEIGHT / 4
temp3=0 temp3 = 0
for j in range(0, 2): for j in range(0, 2):
temp1 = image[i*2+j] temp1 = image[i * 2 + j]
for k in range(0, 2): for k in range(0, 2):
temp2 = temp1&0xC0 temp2 = temp1 & 0xC0
if(temp2 == 0xC0): if (temp2 == 0xC0):
temp3 |= 0x01#white temp3 |= 0x01 # white
elif(temp2 == 0x00): elif (temp2 == 0x00):
temp3 |= 0x00 #black temp3 |= 0x00 # black
elif(temp2 == 0x80): elif (temp2 == 0x80):
temp3 |= 0x01 #gray1 temp3 |= 0x01 # gray1
else: #0x40 else: # 0x40
temp3 |= 0x00 #gray2 temp3 |= 0x00 # gray2
temp3 <<= 1 temp3 <<= 1
temp1 <<= 2 temp1 <<= 2
temp2 = temp1&0xC0 temp2 = temp1 & 0xC0
if(temp2 == 0xC0): #white if (temp2 == 0xC0): # white
temp3 |= 0x01 temp3 |= 0x01
elif(temp2 == 0x00): #black elif (temp2 == 0x00): # black
temp3 |= 0x00 temp3 |= 0x00
elif(temp2 == 0x80): elif (temp2 == 0x80):
temp3 |= 0x01 #gray1 temp3 |= 0x01 # gray1
else : #0x40 else: # 0x40
temp3 |= 0x00 #gray2 temp3 |= 0x00 # gray2
if(j!=1 or k!=1): if (j != 1 or k != 1):
temp3 <<= 1 temp3 <<= 1
temp1 <<= 2 temp1 <<= 2
self.send_data(temp3) self.send_data(temp3)
self.send_command(0x13) self.send_command(0x13)
for i in range(0, EPD_WIDTH * EPD_HEIGHT / 8): #5808*4 46464 for i in range(0, EPD_WIDTH * EPD_HEIGHT / 8): # 5808*4 46464
temp3=0 temp3 = 0
for j in range(0, 2): for j in range(0, 2):
temp1 = image[i*2+j] temp1 = image[i * 2 + j]
for k in range(0, 2): for k in range(0, 2):
temp2 = temp1&0xC0 temp2 = temp1 & 0xC0
if(temp2 == 0xC0): if (temp2 == 0xC0):
temp3 |= 0x01#white temp3 |= 0x01 # white
elif(temp2 == 0x00): elif (temp2 == 0x00):
temp3 |= 0x00 #black temp3 |= 0x00 # black
elif(temp2 == 0x80): elif (temp2 == 0x80):
temp3 |= 0x00 #gray1 temp3 |= 0x00 # gray1
else: #0x40 else: # 0x40
temp3 |= 0x01 #gray2 temp3 |= 0x01 # gray2
temp3 <<= 1 temp3 <<= 1
temp1 <<= 2 temp1 <<= 2
temp2 = temp1&0xC0 temp2 = temp1 & 0xC0
if(temp2 == 0xC0): #white if (temp2 == 0xC0): # white
temp3 |= 0x01 temp3 |= 0x01
elif(temp2 == 0x00): #black elif (temp2 == 0x00): # black
temp3 |= 0x00 temp3 |= 0x00
elif(temp2 == 0x80): elif (temp2 == 0x80):
temp3 |= 0x00 #gray1 temp3 |= 0x00 # gray1
else: #0x40 else: # 0x40
temp3 |= 0x01 #gray2 temp3 |= 0x01 # gray2
if(j!=1 or k!=1): if (j != 1 or k != 1):
temp3 <<= 1 temp3 <<= 1
temp1 <<= 2 temp1 <<= 2
self.send_data(temp3) self.send_data(temp3)
self.Gray_SetLut() self.Gray_SetLut()
self.send_command(0x12) self.send_command(0x12)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
self.ReadBusy() self.ReadBusy()
# pass # pass
def Clear(self): def Clear(self):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF) self.send_data(0xFF)
self.send_command(0x13) self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF) self.send_data(0xFF)
self.send_command(0x12) self.send_command(0x12)
self.ReadBusy() self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0x02) # POWER_OFF self.send_command(0x02) # POWER_OFF
self.ReadBusy() self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP self.send_command(0x07) # DEEP_SLEEP
self.send_data(0XA5) self.send_data(0XA5)
epdconfig.module_exit()
### END OF FILE ###
epdconfig.module_exit()
### END OF FILE ###

View File

@@ -31,8 +31,9 @@ import logging
from inkycal.display.drivers import epdconfig from inkycal.display.drivers import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 400 EPD_WIDTH = 400
EPD_HEIGHT = 300 EPD_HEIGHT = 300
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -46,11 +47,12 @@ class EPD:
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(5) # support v2 displays in favor of v1 displays.Change this to 10 for legacy v1 display support epdconfig.delay_ms(
5) # support v2 displays in favor of v1 displays.Change this to 10 for legacy v1 display support
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -63,86 +65,85 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
logging.debug("e-Paper busy release") logging.debug("e-Paper busy release")
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
self.reset() self.reset()
self.send_command(0x06) # BOOSTER_SOFT_START self.send_command(0x06) # BOOSTER_SOFT_START
self.send_data (0x17) self.send_data(0x17)
self.send_data (0x17) self.send_data(0x17)
self.send_data (0x17) # 07 0f 17 1f 27 2F 37 2f self.send_data(0x17) # 07 0f 17 1f 27 2F 37 2f
self.send_command(0x04) # POWER_ON self.send_command(0x04) # POWER_ON
self.ReadBusy() self.ReadBusy()
self.send_command(0x00) # PANEL_SETTING self.send_command(0x00) # PANEL_SETTING
self.send_data(0x0F) # LUT from OTP self.send_data(0x0F) # LUT from OTP
return 0 return 0
def getbuffer(self, image): def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 8) * self.height)
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Horizontal") logging.debug("Horizontal")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf return buf
def display(self, imageblack, imagered): def display(self, imageblack, imagered):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i]) self.send_data(imageblack[i])
self.send_command(0x13) self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(imagered[i]) self.send_data(imagered[i])
self.send_command(0x12) self.send_command(0x12)
self.ReadBusy() self.ReadBusy()
def Clear(self): def Clear(self):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF) self.send_data(0xFF)
self.send_command(0x13) self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF) self.send_data(0xFF)
self.send_command(0x12) self.send_command(0x12)
self.ReadBusy() self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0x02) # POWER_OFF self.send_command(0x02) # POWER_OFF
self.ReadBusy() self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP self.send_command(0x07) # DEEP_SLEEP
self.send_data(0xA5) # check code self.send_data(0xA5) # check code
epdconfig.module_exit() epdconfig.module_exit()
### END OF FILE ### ### END OF FILE ###

View File

@@ -32,8 +32,9 @@ import logging
from inkycal.display.drivers import epdconfig from inkycal.display.drivers import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 600 EPD_WIDTH = 600
EPD_HEIGHT = 448 EPD_HEIGHT = 448
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -43,15 +44,15 @@ class EPD:
self.cs_pin = epdconfig.CS_PIN self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH self.width = EPD_WIDTH
self.height = EPD_HEIGHT self.height = EPD_HEIGHT
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(10) epdconfig.delay_ms(10)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -64,59 +65,59 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
logging.debug("e-Paper busy release") logging.debug("e-Paper busy release")
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
# EPD hardware init start # EPD hardware init start
self.reset() self.reset()
self.send_command(0x01) # POWER_SETTING self.send_command(0x01) # POWER_SETTING
self.send_data(0x37) self.send_data(0x37)
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x00) # PANEL_SETTING self.send_command(0x00) # PANEL_SETTING
self.send_data(0xCF) self.send_data(0xCF)
self.send_data(0x08) self.send_data(0x08)
self.send_command(0x06) # BOOSTER_SOFT_START self.send_command(0x06) # BOOSTER_SOFT_START
self.send_data(0xc7) self.send_data(0xc7)
self.send_data(0xcc) self.send_data(0xcc)
self.send_data(0x28) self.send_data(0x28)
self.send_command(0x04) # POWER_ON self.send_command(0x04) # POWER_ON
self.ReadBusy() self.ReadBusy()
self.send_command(0x30) # PLL_CONTROL self.send_command(0x30) # PLL_CONTROL
self.send_data(0x3c) self.send_data(0x3c)
self.send_command(0x41) # TEMPERATURE_CALIBRATION self.send_command(0x41) # TEMPERATURE_CALIBRATION
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
self.send_data(0x77) self.send_data(0x77)
self.send_command(0x60) # TCON_SETTING self.send_command(0x60) # TCON_SETTING
self.send_data(0x22) self.send_data(0x22)
self.send_command(0x61) # TCON_RESOLUTION self.send_command(0x61) # TCON_RESOLUTION
self.send_data(0x02) # source 600 self.send_data(0x02) # source 600
self.send_data(0x58) self.send_data(0x58)
self.send_data(0x01) # gate 448 self.send_data(0x01) # gate 448
self.send_data(0xC0) self.send_data(0xC0)
self.send_command(0x82) # VCM_DC_SETTING self.send_command(0x82) # VCM_DC_SETTING
self.send_data(0x1E) # decide by LUT file self.send_data(0x1E) # decide by LUT file
self.send_command(0xe5) # FLASH MODE self.send_command(0xe5) # FLASH MODE
self.send_data(0x03) self.send_data(0x03)
# EPD hardware init end # EPD hardware init end
return 0 return 0
@@ -125,30 +126,30 @@ class EPD:
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
logging.debug('imwidth = %d imheight = %d ',imwidth, imheight) logging.debug('imwidth = %d imheight = %d ', imwidth, imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] < 64: # black if pixels[x, y] < 64: # black
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
elif pixels[x, y] < 192: # convert gray to red elif pixels[x, y] < 192: # convert gray to red
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2) buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2)
else: # white else: # white
buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2) buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2)
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] < 64: # black if pixels[x, y] < 64: # black
buf[int((newx + newy*self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2)) buf[int((newx + newy * self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2))
elif pixels[x, y] < 192: # convert gray to red elif pixels[x, y] < 192: # convert gray to red
buf[int((newx + newy*self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2)) buf[int((newx + newy * self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2))
buf[int((newx + newy*self.width) / 4)] |= 0x40 >> (y % 4 * 2) buf[int((newx + newy * self.width) / 4)] |= 0x40 >> (y % 4 * 2)
else: # white else: # white
buf[int((newx + newy*self.width) / 4)] |= 0xC0 >> (y % 4 * 2) buf[int((newx + newy * self.width) / 4)] |= 0xC0 >> (y % 4 * 2)
return buf return buf
def display(self, image): def display(self, image):
@@ -166,7 +167,7 @@ class EPD:
temp2 = (temp2 << 4) & 0xFF temp2 = (temp2 << 4) & 0xFF
temp1 = (temp1 << 2) & 0xFF temp1 = (temp1 << 2) & 0xFF
j += 1 j += 1
if((temp1 & 0xC0) == 0xC0): if ((temp1 & 0xC0) == 0xC0):
temp2 |= 0x03 temp2 |= 0x03
elif ((temp1 & 0xC0) == 0x00): elif ((temp1 & 0xC0) == 0x00):
temp2 |= 0x00 temp2 |= 0x00
@@ -175,11 +176,11 @@ class EPD:
temp1 = (temp1 << 2) & 0xFF temp1 = (temp1 << 2) & 0xFF
self.send_data(temp2) self.send_data(temp2)
j += 1 j += 1
self.send_command(0x12) self.send_command(0x12)
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def Clear(self): def Clear(self):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width / 4 * self.height)): for i in range(0, int(self.width / 4 * self.height)):
@@ -189,12 +190,11 @@ class EPD:
self.ReadBusy() self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0x02) # POWER_OFF self.send_command(0x02) # POWER_OFF
self.ReadBusy() self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP self.send_command(0x07) # DEEP_SLEEP
self.send_data(0XA5) self.send_data(0XA5)
epdconfig.module_exit()
### END OF FILE ###
epdconfig.module_exit()
### END OF FILE ###

View File

@@ -32,8 +32,9 @@ import logging
from inkycal.display.drivers import epdconfig from inkycal.display.drivers import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 600 EPD_WIDTH = 600
EPD_HEIGHT = 448 EPD_HEIGHT = 448
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -47,11 +48,11 @@ class EPD:
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(10) epdconfig.delay_ms(10)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -64,80 +65,80 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
logging.debug("e-Paper busy release") logging.debug("e-Paper busy release")
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
self.reset() self.reset()
self.send_command(0x01) # POWER_SETTING self.send_command(0x01) # POWER_SETTING
self.send_data(0x37) self.send_data(0x37)
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x00) # PANEL_SETTING self.send_command(0x00) # PANEL_SETTING
self.send_data(0xCF) self.send_data(0xCF)
self.send_data(0x08) self.send_data(0x08)
self.send_command(0x30) # PLL_CONTROL
self.send_data(0x3A) # PLL: 0-15:0x3C, 15+:0x3A
self.send_command(0X82) # VCOM VOLTAGE SETTING
self.send_data(0x28) # all temperature range
self.send_command(0x06) # boost self.send_command(0x30) # PLL_CONTROL
self.send_data(0xc7) self.send_data(0x3A) # PLL: 0-15:0x3C, 15+:0x3A
self.send_data(0xcc) self.send_command(0X82) # VCOM VOLTAGE SETTING
self.send_data(0x15) self.send_data(0x28) # all temperature range
self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING self.send_command(0x06) # boost
self.send_data(0x77) self.send_data(0xc7)
self.send_data(0xcc)
self.send_data(0x15)
self.send_command(0X60) # TCON SETTING self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING
self.send_data(0x22) self.send_data(0x77)
self.send_command(0X65) # FLASH CONTROL self.send_command(0X60) # TCON SETTING
self.send_data(0x22)
self.send_command(0X65) # FLASH CONTROL
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x61) # tres self.send_command(0x61) # tres
self.send_data(0x02) # source 600 self.send_data(0x02) # source 600
self.send_data(0x58) self.send_data(0x58)
self.send_data(0x01) # gate 448 self.send_data(0x01) # gate 448
self.send_data(0xc0) self.send_data(0xc0)
self.send_command(0xe5) # FLASH MODE self.send_command(0xe5) # FLASH MODE
self.send_data(0x03)
self.send_data(0x03) self.send_data(0x03)
self.send_data(0x03)
return 0 return 0
def getbuffer(self, image): def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 8) * self.height)
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
logging.debug('imwidth = %d imheight = %d ',imwidth, imheight) logging.debug('imwidth = %d imheight = %d ', imwidth, imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Horizontal") logging.debug("Horizontal")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf return buf
def display(self, imageblack, imagered): def display(self, imageblack, imagered):
@@ -148,33 +149,33 @@ class EPD:
j = 0 j = 0
while (j < 8): while (j < 8):
if ((temp2 & 0x80) == 0x00): if ((temp2 & 0x80) == 0x00):
temp3 = 0x04 #red temp3 = 0x04 # red
elif ((temp1 & 0x80) == 0x00): elif ((temp1 & 0x80) == 0x00):
temp3 = 0x00 #black temp3 = 0x00 # black
else: else:
temp3 = 0x03 #white temp3 = 0x03 # white
temp3 = (temp3 << 4) & 0xFF temp3 = (temp3 << 4) & 0xFF
temp1 = (temp1 << 1) & 0xFF temp1 = (temp1 << 1) & 0xFF
temp2 = (temp2 << 1) & 0xFF temp2 = (temp2 << 1) & 0xFF
j += 1 j += 1
if((temp2 & 0x80) == 0x00): if ((temp2 & 0x80) == 0x00):
temp3 |= 0x04 #red temp3 |= 0x04 # red
elif ((temp1 & 0x80) == 0x00): elif ((temp1 & 0x80) == 0x00):
temp3 |= 0x00 #black temp3 |= 0x00 # black
else: else:
temp3 |= 0x03 #white temp3 |= 0x03 # white
temp1 = (temp1 << 1) & 0xFF temp1 = (temp1 << 1) & 0xFF
temp2 = (temp2 << 1) & 0xFF temp2 = (temp2 << 1) & 0xFF
self.send_data(temp3) self.send_data(temp3)
j += 1 j += 1
self.send_command(0x04) # POWER ON self.send_command(0x04) # POWER ON
self.ReadBusy() self.ReadBusy()
self.send_command(0x12) # display refresh self.send_command(0x12) # display refresh
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def Clear(self): def Clear(self):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width / 8 * self.height)): for i in range(0, int(self.width / 8 * self.height)):
@@ -182,19 +183,18 @@ class EPD:
self.send_data(0x33) self.send_data(0x33)
self.send_data(0x33) self.send_data(0x33)
self.send_data(0x33) self.send_data(0x33)
self.send_command(0x04) # POWER ON self.send_command(0x04) # POWER ON
self.ReadBusy() self.ReadBusy()
self.send_command(0x12) # display refresh self.send_command(0x12) # display refresh
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0x02) # POWER_OFF self.send_command(0x02) # POWER_OFF
self.ReadBusy() self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP self.send_command(0x07) # DEEP_SLEEP
self.send_data(0xA5) # check code self.send_data(0xA5) # check code
epdconfig.module_exit() epdconfig.module_exit()
### END OF FILE ### ### END OF FILE ###

View File

@@ -32,8 +32,9 @@ import logging
from inkycal.display.drivers import epdconfig from inkycal.display.drivers import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 640 EPD_WIDTH = 640
EPD_HEIGHT = 384 EPD_HEIGHT = 384
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -43,15 +44,15 @@ class EPD:
self.cs_pin = epdconfig.CS_PIN self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH self.width = EPD_WIDTH
self.height = EPD_HEIGHT self.height = EPD_HEIGHT
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(10) epdconfig.delay_ms(10)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -64,59 +65,59 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
logging.debug("e-Paper busy release") logging.debug("e-Paper busy release")
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
# EPD hardware init start # EPD hardware init start
self.reset() self.reset()
self.send_command(0x01) # POWER_SETTING self.send_command(0x01) # POWER_SETTING
self.send_data(0x37) self.send_data(0x37)
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x00) # PANEL_SETTING self.send_command(0x00) # PANEL_SETTING
self.send_data(0xCF) self.send_data(0xCF)
self.send_data(0x08) self.send_data(0x08)
self.send_command(0x06) # BOOSTER_SOFT_START self.send_command(0x06) # BOOSTER_SOFT_START
self.send_data(0xc7) self.send_data(0xc7)
self.send_data(0xcc) self.send_data(0xcc)
self.send_data(0x28) self.send_data(0x28)
self.send_command(0x04) # POWER_ON self.send_command(0x04) # POWER_ON
self.ReadBusy() self.ReadBusy()
self.send_command(0x30) # PLL_CONTROL self.send_command(0x30) # PLL_CONTROL
self.send_data(0x3c) self.send_data(0x3c)
self.send_command(0x41) # TEMPERATURE_CALIBRATION self.send_command(0x41) # TEMPERATURE_CALIBRATION
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
self.send_data(0x77) self.send_data(0x77)
self.send_command(0x60) # TCON_SETTING self.send_command(0x60) # TCON_SETTING
self.send_data(0x22) self.send_data(0x22)
self.send_command(0x61) # TCON_RESOLUTION self.send_command(0x61) # TCON_RESOLUTION
self.send_data(EPD_WIDTH >> 8) #source 640 self.send_data(EPD_WIDTH >> 8) # source 640
self.send_data(EPD_WIDTH & 0xff) self.send_data(EPD_WIDTH & 0xff)
self.send_data(EPD_HEIGHT >> 8) #gate 384 self.send_data(EPD_HEIGHT >> 8) # gate 384
self.send_data(EPD_HEIGHT & 0xff) self.send_data(EPD_HEIGHT & 0xff)
self.send_command(0x82) # VCM_DC_SETTING self.send_command(0x82) # VCM_DC_SETTING
self.send_data(0x1E) # decide by LUT file self.send_data(0x1E) # decide by LUT file
self.send_command(0xe5) # FLASH MODE self.send_command(0xe5) # FLASH MODE
self.send_data(0x03) self.send_data(0x03)
# EPD hardware init end # EPD hardware init end
return 0 return 0
@@ -126,32 +127,32 @@ class EPD:
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
logging.debug('imwidth = %d imheight = %d ',imwidth, imheight) logging.debug('imwidth = %d imheight = %d ', imwidth, imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] < 64: # black if pixels[x, y] < 64: # black
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
elif pixels[x, y] < 192: # convert gray to red elif pixels[x, y] < 192: # convert gray to red
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2) buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2)
else: # white else: # white
buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2) buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2)
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] < 64: # black if pixels[x, y] < 64: # black
buf[int((newx + newy*self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2)) buf[int((newx + newy * self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2))
elif pixels[x, y] < 192: # convert gray to red elif pixels[x, y] < 192: # convert gray to red
buf[int((newx + newy*self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2)) buf[int((newx + newy * self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2))
buf[int((newx + newy*self.width) / 4)] |= 0x40 >> (y % 4 * 2) buf[int((newx + newy * self.width) / 4)] |= 0x40 >> (y % 4 * 2)
else: # white else: # white
buf[int((newx + newy*self.width) / 4)] |= 0xC0 >> (y % 4 * 2) buf[int((newx + newy * self.width) / 4)] |= 0xC0 >> (y % 4 * 2)
return buf return buf
def display(self, image): def display(self, image):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width / 4 * self.height)): for i in range(0, int(self.width / 4 * self.height)):
@@ -167,7 +168,7 @@ class EPD:
temp2 = (temp2 << 4) & 0xFF temp2 = (temp2 << 4) & 0xFF
temp1 = (temp1 << 2) & 0xFF temp1 = (temp1 << 2) & 0xFF
j += 1 j += 1
if((temp1 & 0xC0) == 0xC0): if ((temp1 & 0xC0) == 0xC0):
temp2 |= 0x03 temp2 |= 0x03
elif ((temp1 & 0xC0) == 0x00): elif ((temp1 & 0xC0) == 0x00):
temp2 |= 0x00 temp2 |= 0x00
@@ -176,27 +177,26 @@ class EPD:
temp1 = (temp1 << 2) & 0xFF temp1 = (temp1 << 2) & 0xFF
self.send_data(temp2) self.send_data(temp2)
j += 1 j += 1
self.send_command(0x12) self.send_command(0x12)
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def Clear(self): def Clear(self):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width / 4 * self.height)): for i in range(0, int(self.width / 4 * self.height)):
for j in range(0, 4): for j in range(0, 4):
self.send_data(0x33) self.send_data(0x33)
self.send_command(0x12) self.send_command(0x12)
self.ReadBusy() self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0x02) # POWER_OFF self.send_command(0x02) # POWER_OFF
self.ReadBusy() self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP self.send_command(0x07) # DEEP_SLEEP
self.send_data(0XA5) self.send_data(0XA5)
epdconfig.module_exit() epdconfig.module_exit()
### END OF FILE ### ### END OF FILE ###

View File

@@ -32,8 +32,9 @@ import logging
from inkycal.display.drivers import epdconfig from inkycal.display.drivers import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 640 EPD_WIDTH = 640
EPD_HEIGHT = 384 EPD_HEIGHT = 384
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -47,11 +48,11 @@ class EPD:
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(10) epdconfig.delay_ms(10)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -64,80 +65,80 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
logging.debug("e-Paper busy release") logging.debug("e-Paper busy release")
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
self.reset() self.reset()
self.send_command(0x01) # POWER_SETTING self.send_command(0x01) # POWER_SETTING
self.send_data(0x37) self.send_data(0x37)
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x00) # PANEL_SETTING self.send_command(0x00) # PANEL_SETTING
self.send_data(0xCF) self.send_data(0xCF)
self.send_data(0x08) self.send_data(0x08)
self.send_command(0x30) # PLL_CONTROL
self.send_data(0x3A) # PLL: 0-15:0x3C, 15+:0x3A
self.send_command(0x82) # VCM_DC_SETTING
self.send_data(0x28) #all temperature range
self.send_command(0x06) # BOOSTER_SOFT_START self.send_command(0x30) # PLL_CONTROL
self.send_data(0x3A) # PLL: 0-15:0x3C, 15+:0x3A
self.send_command(0x82) # VCM_DC_SETTING
self.send_data(0x28) # all temperature range
self.send_command(0x06) # BOOSTER_SOFT_START
self.send_data(0xc7) self.send_data(0xc7)
self.send_data(0xcc) self.send_data(0xcc)
self.send_data(0x15) self.send_data(0x15)
self.send_command(0x50) # VCOM AND DATA INTERVAL SETTING self.send_command(0x50) # VCOM AND DATA INTERVAL SETTING
self.send_data(0x77) self.send_data(0x77)
self.send_command(0x60) # TCON_SETTING self.send_command(0x60) # TCON_SETTING
self.send_data(0x22) self.send_data(0x22)
self.send_command(0x65) # FLASH CONTROL self.send_command(0x65) # FLASH CONTROL
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x61) # TCON_RESOLUTION self.send_command(0x61) # TCON_RESOLUTION
self.send_data(self.width >> 8) # source 640 self.send_data(self.width >> 8) # source 640
self.send_data(self.width & 0xff) self.send_data(self.width & 0xff)
self.send_data(self.height >> 8) # gate 384 self.send_data(self.height >> 8) # gate 384
self.send_data(self.height & 0xff) self.send_data(self.height & 0xff)
self.send_command(0xe5) # FLASH MODE self.send_command(0xe5) # FLASH MODE
self.send_data(0x03) self.send_data(0x03)
return 0 return 0
def getbuffer(self, image): def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 8) * self.height)
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
logging.debug('imwidth = %d imheight = %d ',imwidth, imheight) logging.debug('imwidth = %d imheight = %d ', imwidth, imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Horizontal") logging.debug("Horizontal")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf return buf
def display(self, imageblack, imagered): def display(self, imageblack, imagered):
@@ -148,33 +149,33 @@ class EPD:
j = 0 j = 0
while (j < 8): while (j < 8):
if ((temp2 & 0x80) == 0x00): if ((temp2 & 0x80) == 0x00):
temp3 = 0x04 #red temp3 = 0x04 # red
elif ((temp1 & 0x80) == 0x00): elif ((temp1 & 0x80) == 0x00):
temp3 = 0x00 #black temp3 = 0x00 # black
else: else:
temp3 = 0x03 #white temp3 = 0x03 # white
temp3 = (temp3 << 4) & 0xFF temp3 = (temp3 << 4) & 0xFF
temp1 = (temp1 << 1) & 0xFF temp1 = (temp1 << 1) & 0xFF
temp2 = (temp2 << 1) & 0xFF temp2 = (temp2 << 1) & 0xFF
j += 1 j += 1
if((temp2 & 0x80) == 0x00): if ((temp2 & 0x80) == 0x00):
temp3 |= 0x04 #red temp3 |= 0x04 # red
elif ((temp1 & 0x80) == 0x00): elif ((temp1 & 0x80) == 0x00):
temp3 |= 0x00 #black temp3 |= 0x00 # black
else: else:
temp3 |= 0x03 #white temp3 |= 0x03 # white
temp1 = (temp1 << 1) & 0xFF temp1 = (temp1 << 1) & 0xFF
temp2 = (temp2 << 1) & 0xFF temp2 = (temp2 << 1) & 0xFF
self.send_data(temp3) self.send_data(temp3)
j += 1 j += 1
self.send_command(0x04) # POWER ON self.send_command(0x04) # POWER ON
self.ReadBusy() self.ReadBusy()
self.send_command(0x12) # display refresh self.send_command(0x12) # display refresh
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def Clear(self): def Clear(self):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width / 8 * self.height)): for i in range(0, int(self.width / 8 * self.height)):
@@ -182,20 +183,19 @@ class EPD:
self.send_data(0x33) self.send_data(0x33)
self.send_data(0x33) self.send_data(0x33)
self.send_data(0x33) self.send_data(0x33)
self.send_command(0x04) # POWER ON self.send_command(0x04) # POWER ON
self.ReadBusy() self.ReadBusy()
self.send_command(0x12) # display refresh self.send_command(0x12) # display refresh
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0x02) # POWER_OFF self.send_command(0x02) # POWER_OFF
self.ReadBusy() self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP self.send_command(0x07) # DEEP_SLEEP
self.send_data(0XA5) self.send_data(0XA5)
epdconfig.module_exit() epdconfig.module_exit()
### END OF FILE ### ### END OF FILE ###

View File

@@ -32,8 +32,9 @@ import logging
from inkycal.display.drivers import epdconfig from inkycal.display.drivers import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 800 EPD_WIDTH = 800
EPD_HEIGHT = 480 EPD_HEIGHT = 480
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -43,15 +44,15 @@ class EPD:
self.cs_pin = epdconfig.CS_PIN self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH self.width = EPD_WIDTH
self.height = EPD_HEIGHT self.height = EPD_HEIGHT
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(2) epdconfig.delay_ms(2)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -64,49 +65,49 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
self.send_command(0x71) self.send_command(0x71)
busy = epdconfig.digital_read(self.busy_pin) busy = epdconfig.digital_read(self.busy_pin)
while(busy == 0): while (busy == 0):
self.send_command(0x71) self.send_command(0x71)
busy = epdconfig.digital_read(self.busy_pin) busy = epdconfig.digital_read(self.busy_pin)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
# EPD hardware init start # EPD hardware init start
self.reset() self.reset()
self.send_command(0x01) #POWER SETTING
self.send_data(0x07)
self.send_data(0x07) #VGH=20V,VGL=-20V
self.send_data(0x3f) #VDH=15V
self.send_data(0x3f) #VDL=-15V
self.send_command(0x04) #POWER ON self.send_command(0x01) # POWER SETTING
self.send_data(0x07)
self.send_data(0x07) # VGH=20V,VGL=-20V
self.send_data(0x3f) # VDH=15V
self.send_data(0x3f) # VDL=-15V
self.send_command(0x04) # POWER ON
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
self.send_command(0X00) #PANNEL SETTING self.send_command(0X00) # PANNEL SETTING
self.send_data(0x1F) #KW-3f KWR-2F BWROTP 0f BWOTP 1f self.send_data(0x1F) # KW-3f KWR-2F BWROTP 0f BWOTP 1f
self.send_command(0x61) #tres self.send_command(0x61) # tres
self.send_data(0x03) #source 800 self.send_data(0x03) # source 800
self.send_data(0x20) self.send_data(0x20)
self.send_data(0x01) #gate 480 self.send_data(0x01) # gate 480
self.send_data(0xE0) self.send_data(0xE0)
self.send_command(0X15) self.send_command(0X15)
self.send_data(0x00) self.send_data(0x00)
self.send_command(0X50) #VCOM AND DATA INTERVAL SETTING self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING
self.send_data(0x10) self.send_data(0x10)
self.send_data(0x07) self.send_data(0x07)
self.send_command(0X60) #TCON SETTING self.send_command(0X60) # TCON SETTING
self.send_data(0x22) self.send_data(0x22)
# EPD hardware init end # EPD hardware init end
@@ -114,57 +115,56 @@ class EPD:
def getbuffer(self, image): def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 8) * self.height)
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
logging.debug("Horizontal") logging.debug("Horizontal")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf return buf
def display(self, image): def display(self, image):
self.send_command(0x13) self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(~image[i]); self.send_data(~image[i]);
self.send_command(0x12) self.send_command(0x12)
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def Clear(self): def Clear(self):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x13) self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x12) self.send_command(0x12)
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0x02) # POWER_OFF self.send_command(0x02) # POWER_OFF
self.ReadBusy() self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP self.send_command(0x07) # DEEP_SLEEP
self.send_data(0XA5) self.send_data(0XA5)
epdconfig.module_exit() epdconfig.module_exit()
### END OF FILE ### ### END OF FILE ###

View File

@@ -32,8 +32,9 @@ import logging
from inkycal.display.drivers import epdconfig from inkycal.display.drivers import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 800 EPD_WIDTH = 800
EPD_HEIGHT = 480 EPD_HEIGHT = 480
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -47,11 +48,11 @@ class EPD:
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(4) epdconfig.delay_ms(4)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -64,110 +65,109 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
self.send_command(0x71) self.send_command(0x71)
busy = epdconfig.digital_read(self.busy_pin) busy = epdconfig.digital_read(self.busy_pin)
while(busy == 0): while (busy == 0):
self.send_command(0x71) self.send_command(0x71)
busy = epdconfig.digital_read(self.busy_pin) busy = epdconfig.digital_read(self.busy_pin)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
self.reset()
self.send_command(0x01); #POWER SETTING
self.send_data(0x07);
self.send_data(0x07); #VGH=20V,VGL=-20V
self.send_data(0x3f); #VDH=15V
self.send_data(0x3f); #VDL=-15V
self.send_command(0x04); #POWER ON self.reset()
self.send_command(0x01); # POWER SETTING
self.send_data(0x07);
self.send_data(0x07); # VGH=20V,VGL=-20V
self.send_data(0x3f); # VDH=15V
self.send_data(0x3f); # VDL=-15V
self.send_command(0x04); # POWER ON
epdconfig.delay_ms(100); epdconfig.delay_ms(100);
self.ReadBusy(); self.ReadBusy();
self.send_command(0X00); #PANNEL SETTING self.send_command(0X00); # PANNEL SETTING
self.send_data(0x0F); #KW-3f KWR-2F BWROTP 0f BWOTP 1f self.send_data(0x0F); # KW-3f KWR-2F BWROTP 0f BWOTP 1f
self.send_command(0x61); #tres self.send_command(0x61); # tres
self.send_data(0x03); #source 800 self.send_data(0x03); # source 800
self.send_data(0x20); self.send_data(0x20);
self.send_data(0x01); #gate 480 self.send_data(0x01); # gate 480
self.send_data(0xE0); self.send_data(0xE0);
self.send_command(0X15); self.send_command(0X15);
self.send_data(0x00); self.send_data(0x00);
self.send_command(0X50); #VCOM AND DATA INTERVAL SETTING self.send_command(0X50); # VCOM AND DATA INTERVAL SETTING
self.send_data(0x11); self.send_data(0x11);
self.send_data(0x07); self.send_data(0x07);
self.send_command(0X60); #TCON SETTING self.send_command(0X60); # TCON SETTING
self.send_data(0x22); self.send_data(0x22);
return 0 return 0
def getbuffer(self, image): def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 8) * self.height)
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
logging.debug('imwidth = %d imheight = %d ',imwidth, imheight) logging.debug('imwidth = %d imheight = %d ', imwidth, imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Horizontal") logging.debug("Horizontal")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf return buf
def display(self, imageblack, imagered): def display(self, imageblack, imagered):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i]); self.send_data(imageblack[i]);
self.send_command(0x13) self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(~imagered[i]); self.send_data(~imagered[i]);
self.send_command(0x12) self.send_command(0x12)
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def Clear(self): def Clear(self):
self.send_command(0x10) self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xff) self.send_data(0xff)
self.send_command(0x13) self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0x00) self.send_data(0x00)
self.send_command(0x12) self.send_command(0x12)
epdconfig.delay_ms(100) epdconfig.delay_ms(100)
self.ReadBusy() self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0x02) # POWER_OFF self.send_command(0x02) # POWER_OFF
self.ReadBusy() self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP self.send_command(0x07) # DEEP_SLEEP
self.send_data(0XA5) self.send_data(0XA5)
epdconfig.module_exit() epdconfig.module_exit()
### END OF FILE ### ### END OF FILE ###

View File

@@ -32,8 +32,9 @@ import logging
from . import epdconfig from . import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 880 EPD_WIDTH = 880
EPD_HEIGHT = 528 EPD_HEIGHT = 528
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -43,15 +44,15 @@ class EPD:
self.cs_pin = epdconfig.CS_PIN self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH self.width = EPD_WIDTH
self.height = EPD_HEIGHT self.height = EPD_HEIGHT
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(2) epdconfig.delay_ms(2)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -64,22 +65,22 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
busy = epdconfig.digital_read(self.busy_pin) busy = epdconfig.digital_read(self.busy_pin)
while(busy == 1): while (busy == 1):
busy = epdconfig.digital_read(self.busy_pin) busy = epdconfig.digital_read(self.busy_pin)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
# EPD hardware init start # EPD hardware init start
self.reset() self.reset()
self.ReadBusy(); self.ReadBusy();
self.send_command(0x12); #SWRESET self.send_command(0x12); # SWRESET
self.ReadBusy(); self.ReadBusy();
self.send_command(0x46); # Auto Write Red RAM self.send_command(0x46); # Auto Write Red RAM
@@ -94,44 +95,42 @@ class EPD:
self.send_data(0xC7); self.send_data(0xC7);
self.send_data(0xC3); self.send_data(0xC3);
self.send_data(0xC0); self.send_data(0xC0);
self.send_data(0x40); self.send_data(0x40);
self.send_command(0x01); # Set MUX as 527 self.send_command(0x01); # Set MUX as 527
self.send_data(0xAF); self.send_data(0xAF);
self.send_data(0x02); self.send_data(0x02);
self.send_data(0x01);#0x01 self.send_data(0x01); # 0x01
self.send_command(0x11); # Data entry mode self.send_command(0x11); # Data entry mode
self.send_data(0x01); self.send_data(0x01);
self.send_command(0x44); self.send_command(0x44);
self.send_data(0x00); # RAM x address start at 0 self.send_data(0x00); # RAM x address start at 0
self.send_data(0x00); self.send_data(0x00);
self.send_data(0x6F); self.send_data(0x6F);
self.send_data(0x03); self.send_data(0x03);
self.send_command(0x45); self.send_command(0x45);
self.send_data(0xAF); self.send_data(0xAF);
self.send_data(0x02); self.send_data(0x02);
self.send_data(0x00); self.send_data(0x00);
self.send_data(0x00); self.send_data(0x00);
self.send_command(0x3C); # VBD self.send_command(0x3C); # VBD
self.send_data(0x05); # LUT1, for white self.send_data(0x05); # LUT1, for white
self.send_command(0x18); self.send_command(0x18);
self.send_data(0X80); self.send_data(0X80);
self.send_command(0x22); self.send_command(0x22);
self.send_data(0XB1); #Load Temperature and waveform setting. self.send_data(0XB1); # Load Temperature and waveform setting.
self.send_command(0x20); self.send_command(0x20);
self.ReadBusy(); self.ReadBusy();
self.send_command(0x4E); # set RAM x address count to 0; self.send_command(0x4E); # set RAM x address count to 0;
self.send_data(0x00); self.send_data(0x00);
self.send_data(0x00); self.send_data(0x00);
self.send_command(0x4F); self.send_command(0x4F);
self.send_data(0x00); self.send_data(0x00);
self.send_data(0x00); self.send_data(0x00);
# EPD hardware init end # EPD hardware init end
@@ -139,56 +138,56 @@ class EPD:
def getbuffer(self, image): def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 8) * self.height)
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
logging.debug("Horizontal") logging.debug("Horizontal")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf return buf
def display(self, image): def display(self, image):
self.send_command(0x4F); self.send_command(0x4F);
self.send_data(0x00); self.send_data(0x00);
self.send_data(0x00); self.send_data(0x00);
self.send_command(0x24); self.send_command(0x24);
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(image[i]); self.send_data(image[i]);
self.send_command(0x22); self.send_command(0x22);
self.send_data(0xF7);#Load LUT from MCU(0x32) self.send_data(0xF7); # Load LUT from MCU(0x32)
self.send_command(0x20); self.send_command(0x20);
epdconfig.delay_ms(10); epdconfig.delay_ms(10);
self.ReadBusy(); self.ReadBusy();
def Clear(self): def Clear(self):
self.send_command(0x4F); self.send_command(0x4F);
self.send_data(0x00); self.send_data(0x00);
self.send_data(0x00); self.send_data(0x00);
self.send_command(0x24) self.send_command(0x24)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xff) self.send_data(0xff)
self.send_command(0x26) self.send_command(0x26)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xff) self.send_data(0xff)
self.send_command(0x22); self.send_command(0x22);
self.send_data(0xF7);#Load LUT from MCU(0x32) self.send_data(0xF7); # Load LUT from MCU(0x32)
self.send_command(0x20); self.send_command(0x20);
epdconfig.delay_ms(10); epdconfig.delay_ms(10);
self.ReadBusy(); self.ReadBusy();
@@ -196,6 +195,6 @@ class EPD:
def sleep(self): def sleep(self):
self.send_command(0x10); self.send_command(0x10);
self.send_data(0x01); self.send_data(0x01);
epdconfig.module_exit() epdconfig.module_exit()
### END OF FILE ### ### END OF FILE ###

View File

@@ -32,8 +32,9 @@ import logging
from inkycal.display.drivers import epdconfig from inkycal.display.drivers import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 880 EPD_WIDTH = 880
EPD_HEIGHT = 528 EPD_HEIGHT = 528
class EPD: class EPD:
def __init__(self): def __init__(self):
@@ -47,11 +48,11 @@ class EPD:
# Hardware reset # Hardware reset
def reset(self): def reset(self):
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0) epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(4) epdconfig.delay_ms(4)
epdconfig.digital_write(self.reset_pin, 1) epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
@@ -64,37 +65,37 @@ class EPD:
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self): def ReadBusy(self):
logging.debug("e-Paper busy") logging.debug("e-Paper busy")
busy = epdconfig.digital_read(self.busy_pin) busy = epdconfig.digital_read(self.busy_pin)
while(busy == 1): while (busy == 1):
busy = epdconfig.digital_read(self.busy_pin) busy = epdconfig.digital_read(self.busy_pin)
epdconfig.delay_ms(200) epdconfig.delay_ms(200)
def init(self): def init(self):
if (epdconfig.module_init() != 0): if (epdconfig.module_init() != 0):
return -1 return -1
self.reset() self.reset()
self.send_command(0x12); #SWRESET self.send_command(0x12); # SWRESET
self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal self.ReadBusy(); # waiting for the electronic paper IC to release the idle signal
self.send_command(0x46); # Auto Write RAM self.send_command(0x46); # Auto Write RAM
self.send_data(0xF7); self.send_data(0xF7);
self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal self.ReadBusy(); # waiting for the electronic paper IC to release the idle signal
self.send_command(0x47); # Auto Write RAM self.send_command(0x47); # Auto Write RAM
self.send_data(0xF7); self.send_data(0xF7);
self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal self.ReadBusy(); # waiting for the electronic paper IC to release the idle signal
self.send_command(0x0C); # Soft start setting self.send_command(0x0C); # Soft start setting
self.send_data(0xAE); self.send_data(0xAE);
self.send_data(0xC7); self.send_data(0xC7);
self.send_data(0xC3); self.send_data(0xC3);
self.send_data(0xC0); self.send_data(0xC0);
self.send_data(0x40); self.send_data(0x40);
self.send_command(0x01); # Set MUX as 527 self.send_command(0x01); # Set MUX as 527
self.send_data(0xAF); self.send_data(0xAF);
@@ -105,100 +106,98 @@ class EPD:
self.send_data(0x01); self.send_data(0x01);
self.send_command(0x44); self.send_command(0x44);
self.send_data(0x00); # RAM x address start at 0 self.send_data(0x00); # RAM x address start at 0
self.send_data(0x00); self.send_data(0x00);
self.send_data(0x6F); # RAM x address end at 36Fh -> 879 self.send_data(0x6F); # RAM x address end at 36Fh -> 879
self.send_data(0x03); self.send_data(0x03);
self.send_command(0x45); self.send_command(0x45);
self.send_data(0xAF); # RAM y address start at 20Fh; self.send_data(0xAF); # RAM y address start at 20Fh;
self.send_data(0x02); self.send_data(0x02);
self.send_data(0x00); # RAM y address end at 00h; self.send_data(0x00); # RAM y address end at 00h;
self.send_data(0x00); self.send_data(0x00);
self.send_command(0x3C); # VBD self.send_command(0x3C); # VBD
self.send_data(0x01); # LUT1, for white self.send_data(0x01); # LUT1, for white
self.send_command(0x18); self.send_command(0x18);
self.send_data(0X80); self.send_data(0X80);
self.send_command(0x22); self.send_command(0x22);
self.send_data(0XB1); #Load Temperature and waveform setting. self.send_data(0XB1); # Load Temperature and waveform setting.
self.send_command(0x20); self.send_command(0x20);
self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal self.ReadBusy(); # waiting for the electronic paper IC to release the idle signal
self.send_command(0x4E); self.send_command(0x4E);
self.send_data(0x00); self.send_data(0x00);
self.send_data(0x00); self.send_data(0x00);
self.send_command(0x4F); self.send_command(0x4F);
self.send_data(0xAF); self.send_data(0xAF);
self.send_data(0x02); self.send_data(0x02);
return 0 return 0
def getbuffer(self, image): def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height) # logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 8) * self.height)
image_monocolor = image.convert('1') image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load() pixels = image_monocolor.load()
logging.debug('imwidth = %d imheight = %d ',imwidth, imheight) logging.debug('imwidth = %d imheight = %d ', imwidth, imheight)
if(imwidth == self.width and imheight == self.height): if (imwidth == self.width and imheight == self.height):
logging.debug("Horizontal") logging.debug("Horizontal")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width): elif (imwidth == self.height and imheight == self.width):
logging.debug("Vertical") logging.debug("Vertical")
for y in range(imheight): for y in range(imheight):
for x in range(imwidth): for x in range(imwidth):
newx = y newx = y
newy = self.height - x - 1 newy = self.height - x - 1
if pixels[x, y] == 0: if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf return buf
def display(self, imageblack, imagered): def display(self, imageblack, imagered):
self.send_command(0x4F); self.send_command(0x4F);
self.send_data(0xAf); self.send_data(0xAf);
self.send_command(0x24) self.send_command(0x24)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i]); self.send_data(imageblack[i]);
self.send_command(0x26) self.send_command(0x26)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(~imagered[i]); self.send_data(~imagered[i]);
self.send_command(0x22); self.send_command(0x22);
self.send_data(0xC7); #Load LUT from MCU(0x32) self.send_data(0xC7); # Load LUT from MCU(0x32)
self.send_command(0x20); self.send_command(0x20);
epdconfig.delay_ms(200); #!!!The delay here is necessary, 200uS at least!!! epdconfig.delay_ms(200); # !!!The delay here is necessary, 200uS at least!!!
self.ReadBusy(); self.ReadBusy();
def Clear(self): def Clear(self):
self.send_command(0x4F); self.send_command(0x4F);
self.send_data(0xAf); self.send_data(0xAf);
self.send_command(0x24) self.send_command(0x24)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xff); self.send_data(0xff);
self.send_command(0x26) self.send_command(0x26)
for i in range(0, int(self.width * self.height / 8)): for i in range(0, int(self.width * self.height / 8)):
self.send_data(0x00); self.send_data(0x00);
self.send_command(0x22); self.send_command(0x22);
self.send_data(0xC7); #Load LUT from MCU(0x32) self.send_data(0xC7); # Load LUT from MCU(0x32)
self.send_command(0x20); self.send_command(0x20);
epdconfig.delay_ms(200); #!!!The delay here is necessary, 200uS at least!!! epdconfig.delay_ms(200); # !!!The delay here is necessary, 200uS at least!!!
self.ReadBusy(); self.ReadBusy();
def sleep(self): def sleep(self):
self.send_command(0x10); #deep sleep self.send_command(0x10); # deep sleep
self.send_data(0x01); self.send_data(0x01);
epdconfig.module_exit() epdconfig.module_exit()
### END OF FILE ### ### END OF FILE ###

View File

@@ -35,12 +35,13 @@ import time
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class RaspberryPi: class RaspberryPi:
# Pin definition # Pin definition
RST_PIN = 17 RST_PIN = 17
DC_PIN = 25 DC_PIN = 25
CS_PIN = 8 CS_PIN = 8
BUSY_PIN = 24 BUSY_PIN = 24
def __init__(self): def __init__(self):
import spidev import spidev
@@ -76,7 +77,7 @@ class RaspberryPi:
def module_exit(self): def module_exit(self):
logger.debug("spi end") logger.debug("spi end")
#self.SPI.close() #removed as it causes some problems # self.SPI.close() #removed as it causes some problems
logger.debug("close 5V, Module enters 0 power consumption ...") logger.debug("close 5V, Module enters 0 power consumption ...")
self.GPIO.output(self.RST_PIN, 0) self.GPIO.output(self.RST_PIN, 0)
@@ -87,10 +88,10 @@ class RaspberryPi:
class JetsonNano: class JetsonNano:
# Pin definition # Pin definition
RST_PIN = 17 RST_PIN = 17
DC_PIN = 25 DC_PIN = 25
CS_PIN = 8 CS_PIN = 8
BUSY_PIN = 24 BUSY_PIN = 24
def __init__(self): def __init__(self):
import ctypes import ctypes
@@ -152,5 +153,4 @@ else:
for func in [x for x in dir(implementation) if not x.startswith('_')]: for func in [x for x in dir(implementation) if not x.startswith('_')]:
setattr(sys.modules[__name__], func, getattr(implementation, func)) setattr(sys.modules[__name__], func, getattr(implementation, func))
### END OF FILE ### ### END OF FILE ###

View File

@@ -7,4 +7,4 @@ from .inkycal_image import Inkyimage
from .inkycal_jokes import Jokes from .inkycal_jokes import Jokes
from .inkycal_stocks import Stocks from .inkycal_stocks import Stocks
from .inkycal_slideshow import Slideshow from .inkycal_slideshow import Slideshow
#from .inkycal_server import Inkyserver # from .inkycal_server import Inkyserver

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Third party module template (inkycal-compatible module) Third party module template (inkycal-compatible module)
@@ -13,7 +13,6 @@ Copyright by aceisace
from inkycal.modules.template import inkycal_module from inkycal.modules.template import inkycal_module
from inkycal.custom import * from inkycal.custom import *
############################################################################# #############################################################################
# Built-in library imports (change as desired) # Built-in library imports (change as desired)
############################################################################# #############################################################################
@@ -21,7 +20,6 @@ from inkycal.custom import *
# Built-in libraries go here # Built-in libraries go here
from random import shuffle from random import shuffle
############################################################################# #############################################################################
# External library imports (always use try-except) # External library imports (always use try-except)
############################################################################# #############################################################################
@@ -30,11 +28,10 @@ from random import shuffle
# use try...except ImportError to check if it has been installed # use try...except ImportError to check if it has been installed
# If it is not found, print a short message on how to install this dependency # If it is not found, print a short message on how to install this dependency
try: try:
import feedparser import feedparser
except ImportError: except ImportError:
print('feedparser is not installed! Please install with:') print('feedparser is not installed! Please install with:')
print('pip3 install feedparser') print('pip3 install feedparser')
############################################################################# #############################################################################
# Filename + logging (do not remove) # Filename + logging (do not remove)
@@ -44,6 +41,7 @@ except ImportError:
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
############################################################################# #############################################################################
# Class setup # Class setup
############################################################################# #############################################################################
@@ -52,181 +50,177 @@ logger = logging.getLogger(filename)
# Avoid naming the class with too long names # Avoid naming the class with too long names
class Simple(inkycal_module): class Simple(inkycal_module):
""" Simple Class """ Simple Class
Once sentence describing what this module does, Once sentence describing what this module does,
e.g. Display hello world with your name! e.g. Display hello world with your name!
""" """
# name is the name that will be shown on the web-ui # name is the name that will be shown on the web-ui
# may be same or different to the class name (Do not remove this) # may be same or different to the class name (Do not remove this)
name = "Simple - say hello world" name = "Simple - say hello world"
# create a dictionary containing variables which your module must have # create a dictionary containing variables which your module must have
# to run correctly, e.g. if your module needs an 'api-key' and a 'name': # to run correctly, e.g. if your module needs an 'api-key' and a 'name':
requires = { requires = {
# A simple text input; users can choose what to enter by keyboard # A simple text input; users can choose what to enter by keyboard
'api_key': {"label" : "Please enter your api-key from some-website"}, 'api_key': {"label": "Please enter your api-key from some-website"},
# A simple text input; users can choose what to enter by keyboard # A simple text input; users can choose what to enter by keyboard
'username': {"label": "Please enter a username"}, 'username': {"label": "Please enter a username"},
} }
# The format for the above is: |"key_name": {"Desription what this means"},| # The format for the above is: |"key_name": {"Desription what this means"},|
# create a dictionary containing variables which your module optionally # create a dictionary containing variables which your module optionally
# can have to run correctly, e.g. if your module needs has optional # can have to run correctly, e.g. if your module needs has optional
# parameters like: 'api-key' and a 'name': # parameters like: 'api-key' and a 'name':
######################################################################### #########################################################################
optional = { optional = {
# A simple text input with multiple values separated by a comma # A simple text input with multiple values separated by a comma
'hobbies': {"label": "What is/are your hobbies? Separate multiple ones " 'hobbies': {"label": "What is/are your hobbies? Separate multiple ones "
"with a comma"}, "with a comma"},
# A simple text input which should be a number # A simple text input which should be a number
'age': {"label": "What is your age? Please enter a number"}, 'age': {"label": "What is your age? Please enter a number"},
# A dropdown list variable. This will allow users to select something
# from the list in options. Instead of True/False, you can have
# strings, numbers and other datatypes. Add as many options as you need
'likes_inkycal': {
"label": "Do you like Inkycal?",
"options": [True, False],
},
# A dropdown list with a fallback value in case the user didn't select # A dropdown list variable. This will allow users to select something
# anything # from the list in options. Instead of True/False, you can have
'show_smiley': { # strings, numbers and other datatypes. Add as many options as you need
"label": "Show a smiley next to your name?", 'likes_inkycal': {
"options": [True, False], "label": "Do you like Inkycal?",
"default": True, "options": [True, False],
}, },
}
########################################################################
# Initialise the class (do not remove) # A dropdown list with a fallback value in case the user didn't select
def __init__(self, config): # anything
"""Initialize your module module""" 'show_smiley': {
"label": "Show a smiley next to your name?",
"options": [True, False],
"default": True,
},
}
# Initialise this module via the inkycal_module template (required) ########################################################################
super().__init__(config)
config = config['config'] # Initialise the class (do not remove)
def __init__(self, config):
"""Initialize your module module"""
# Check if all required parameters are present # Initialise this module via the inkycal_module template (required)
# remove this if your module has no required parameters super().__init__(config)
for param in self.requires:
if not param in config:
raise Exception('config is missing {}'.format(param))
# the web-UI removes any blank space from the input config = config['config']
# It can only output strings or booleans, integers and lists need to be
# converted manually, e.g.
# if you need a boolean (True/False), no conversion is needed: # Check if all required parameters are present
self.show_smiley = config['show_smiley'] # remove this if your module has no required parameters
for param in self.requires:
if not param in config:
raise Exception('config is missing {}'.format(param))
# if you need a single word input, like the api-ley, no conversion is needed # the web-UI removes any blank space from the input
self.api_key = config['api_key'] # It can only output strings or booleans, integers and lists need to be
# converted manually, e.g.
# if you need a integer (number) input, you have to convert this to a int # if you need a boolean (True/False), no conversion is needed:
#-----------------------------------------------------------------------# self.show_smiley = config['show_smiley']
# bad example :/
self.age = int( config["age"] )
# Remember age was a optional parameter? What if no age was entered
# and there is no fallback value? Then the age would be None.
# This would cause crashing right here
# good example :)
if config["age"] and isinstance(config["age"], str):
self.age = int( config["age"] )
else:
self.age = 10 # just a joke, no offense
# -> Check if age was entered and if it's a string (entered via web-UI)
# If something was entered for age, convert it to a number
# The else statement is executed when nothing was entered for age
# You could assign a custom value now or print something.
#-----------------------------------------------------------------------#
# if you need a list of words, you have to convert the string to a list # if you need a single word input, like the api-ley, no conversion is needed
#-----------------------------------------------------------------------# self.api_key = config['api_key']
# good example :)
if config["hobbies"] and isinstance(config["hobbies"], str):
self.hobbies = config["age"].split(",")
# split splits the string on each comma -> gives a list
# even if a single value was entered, it will be converted to a list
else:
self.hobbies = [] # empty list if nothing was entered by user
#-----------------------------------------------------------------------#
# give an OK message # if you need a integer (number) input, you have to convert this to a int
print(f'{filename} loaded') # -----------------------------------------------------------------------#
# bad example :/
self.age = int(config["age"])
# Remember age was a optional parameter? What if no age was entered
# and there is no fallback value? Then the age would be None.
# This would cause crashing right here
############################################################################# # good example :)
# Validation of module specific parameters (optional) # if config["age"] and isinstance(config["age"], str):
############################################################################# self.age = int(config["age"])
else:
self.age = 10 # just a joke, no offense
# -> Check if age was entered and if it's a string (entered via web-UI)
# If something was entered for age, convert it to a number
# The else statement is executed when nothing was entered for age
# You could assign a custom value now or print something.
# -----------------------------------------------------------------------#
def _validate(self): # if you need a list of words, you have to convert the string to a list
"""Validate module-specific parameters""" # -----------------------------------------------------------------------#
# Check the type of module-specific parameters # good example :)
# This function is optional, but useful for debugging. if config["hobbies"] and isinstance(config["hobbies"], str):
self.hobbies = config["age"].split(",")
# split splits the string on each comma -> gives a list
# even if a single value was entered, it will be converted to a list
else:
self.hobbies = [] # empty list if nothing was entered by user
# -----------------------------------------------------------------------#
# Here, we are checking if do_something (from init) is True/False # give an OK message
if not isinstance(self.age, int): print(f'{filename} loaded')
print(f"age has to be a number, but given value is {self.age}")
#############################################################################
# Validation of module specific parameters (optional) #
#############################################################################
############################################################################# def _validate(self):
# Generating the image # """Validate module-specific parameters"""
############################################################################# # Check the type of module-specific parameters
# This function is optional, but useful for debugging.
def generate_image(self): # Here, we are checking if do_something (from init) is True/False
"""Generate image for this module""" if not isinstance(self.age, int):
print(f"age has to be a number, but given value is {self.age}")
# Define new image size with respect to padding #############################################################################
im_width = int(self.width - (2 * self.padding_left)) # Generating the image #
im_height = int(self.height - (2 * self.padding_top)) #############################################################################
im_size = im_width, im_height
logger.info('image size: {} x {} px'.format(im_width, im_height))
# Use logger.info(), logger.debug(), logger.warning() to display def generate_image(self):
# useful information for the developer """Generate image for this module"""
logger.info('image size: {} x {} px'.format(im_width, im_height))
# Create an image for black pixels and one for coloured pixels (required) # Define new image size with respect to padding
im_black = Image.new('RGB', size = im_size, color = 'white') im_width = int(self.width - (2 * self.padding_left))
im_colour = Image.new('RGB', size = im_size, color = 'white') im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
logger.info('image size: {} x {} px'.format(im_width, im_height))
################################################################# # Use logger.info(), logger.debug(), logger.warning() to display
# useful information for the developer
logger.info('image size: {} x {} px'.format(im_width, im_height))
# Your code goes here # # Create an image for black pixels and one for coloured pixels (required)
im_black = Image.new('RGB', size=im_size, color='white')
# Write/Draw something on the image im_colour = Image.new('RGB', size=im_size, color='white')
# You can use these custom functions to help you create the image: #################################################################
# - write() -> write text on the image
# - get_fonts() -> see which fonts are available
# - get_system_tz() -> Get the system's current timezone
# - auto_fontsize() -> Scale the fontsize to the provided height
# - textwrap() -> Split a paragraph into smaller lines
# - internet_available() -> Check if internet is available
# - draw_border() -> Draw a border around the specified area
# If these aren't enough, take a look at python Pillow (imaging library)'s # Your code goes here #
# documentation.
# Write/Draw something on the image
#################################################################
# return the images ready for the display # You can use these custom functions to help you create the image:
return im_black, im_colour # - write() -> write text on the image
# - get_fonts() -> see which fonts are available
# - get_system_tz() -> Get the system's current timezone
# - auto_fontsize() -> Scale the fontsize to the provided height
# - textwrap() -> Split a paragraph into smaller lines
# - internet_available() -> Check if internet is available
# - draw_border() -> Draw a border around the specified area
# If these aren't enough, take a look at python Pillow (imaging library)'s
# documentation.
#################################################################
# return the images ready for the display
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print('running {0} in standalone mode'.format(filename)) print('running {0} in standalone mode'.format(filename))
################################################################################ ################################################################################
# Last steps # Last steps
@@ -248,4 +242,3 @@ if __name__ == '__main__':
# How do I now import my module? # How do I now import my module?
# from inkycal.modules import Class # from inkycal.modules import Class
# Where Class is the name of the class inside your module (e.g. Simple) # Where Class is the name of the class inside your module (e.g. Simple)

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
iCalendar (parsing) module for Inky-Calendar Project iCalendar (parsing) module for Inky-Calendar Project
Copyright by aceisace Copyright by aceisace
@@ -20,199 +20,196 @@ import time
import os import os
try: try:
import recurring_ical_events import recurring_ical_events
except ModuleNotFoundError: except ModuleNotFoundError:
print('recurring-ical-events library could not be found.') print('recurring-ical-events library could not be found.')
print('Please install this with: pip3 install recurring-ical-events') print('Please install this with: pip3 install recurring-ical-events')
try: try:
from icalendar import Calendar, Event from icalendar import Calendar, Event
except ModuleNotFoundError: except ModuleNotFoundError:
print('icalendar library could not be found. Please install this with:') print('icalendar library could not be found. Please install this with:')
print('pip3 install icalendar') print('pip3 install icalendar')
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class iCalendar: class iCalendar:
"""iCalendar parsing moudule for inkycal. """iCalendar parsing moudule for inkycal.
Parses events from given iCalendar URLs / paths""" Parses events from given iCalendar URLs / paths"""
def __init__(self): def __init__(self):
self.icalendars = [] self.icalendars = []
self.parsed_events = [] self.parsed_events = []
def load_url(self, url, username=None, password=None): def load_url(self, url, username=None, password=None):
"""Input a string or list of strings containing valid iCalendar URLs """Input a string or list of strings containing valid iCalendar URLs
example: 'URL1' (single url) OR ['URL1', 'URL2'] (multiple URLs) example: 'URL1' (single url) OR ['URL1', 'URL2'] (multiple URLs)
add username and password to access protected files add username and password to access protected files
""" """
if type(url) == list: if type(url) == list:
if (username == None) and (password == None): if (username == None) and (password == None):
ical = [Calendar.from_ical(str(urlopen(_).read().decode())) ical = [Calendar.from_ical(str(urlopen(_).read().decode()))
for _ in url] for _ in url]
else: else:
ical = [auth_ical(each_url, username, password) for each_url in url] ical = [auth_ical(each_url, username, password) for each_url in url]
elif type(url) == str: elif type(url) == str:
if (username == None) and (password == None): if (username == None) and (password == None):
ical = [Calendar.from_ical(str(urlopen(url).read().decode()))] ical = [Calendar.from_ical(str(urlopen(url).read().decode()))]
else: else:
ical = [auth_ical(url, username, password)] ical = [auth_ical(url, username, password)]
else: else:
raise Exception (f"Input: '{url}' is not a string or list!") raise Exception(f"Input: '{url}' is not a string or list!")
def auth_ical(url, uname, passwd):
"""Authorisation helper for protected ical files"""
def auth_ical(url, uname, passwd): # Credit to Joshka
"""Authorisation helper for protected ical files""" password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None, url, username, password)
handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
opener = urllib.request.build_opener(handler)
ical = Calendar.from_ical(str(opener.open(url).read().decode()))
return ical
# Credit to Joshka # Add the parsed icalendar/s to the self.icalendars list
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() if ical: self.icalendars += ical
password_mgr.add_password(None, url, username, password) logger.info('loaded iCalendars from URLs')
handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
opener = urllib.request.build_opener(handler)
ical = Calendar.from_ical(str(opener.open(url).read().decode()))
return ical
# Add the parsed icalendar/s to the self.icalendars list def load_from_file(self, filepath):
if ical: self.icalendars += ical """Input a string or list of strings containing valid iCalendar filepaths
logger.info('loaded iCalendars from URLs') example: 'path1' (single file) OR ['path1', 'path2'] (multiple files)
returns a list of iCalendars as string (raw)
"""
if isinstance(filepath, list):
for path in filepath:
with open(path, mode='r') as ical_file:
ical = (Calendar.from_ical(ical_file.read()))
self.icalendars += ical
def load_from_file(self, filepath): elif isinstance(filepath, str):
"""Input a string or list of strings containing valid iCalendar filepaths with open(filepath, mode='r') as ical_file:
example: 'path1' (single file) OR ['path1', 'path2'] (multiple files) ical = (Calendar.from_ical(ical_file.read()))
returns a list of iCalendars as string (raw) self.icalendars += ical
""" else:
if isinstance(filepath, list): raise Exception(f"Input: '{filepath}' is not a string or list!")
for path in filepath:
with open(path, mode='r') as ical_file:
ical = (Calendar.from_ical(ical_file.read()))
self.icalendars += ical
elif isinstance(filepath, str): logger.info('loaded iCalendars from filepaths')
with open(filepath, mode='r') as ical_file:
ical = (Calendar.from_ical(ical_file.read()))
self.icalendars += ical
else:
raise Exception (f"Input: '{filepath}' is not a string or list!")
logger.info('loaded iCalendars from filepaths') def get_events(self, timeline_start, timeline_end, timezone=None):
"""Input an arrow (time) object for:
* the beginning of timeline (events have to end after this time)
* the end of the timeline (events have to begin before this time)
* timezone if events should be formatted to local time
Returns a list of events sorted by date
"""
if type(timeline_start) == arrow.arrow.Arrow:
if timezone == None:
timezone = 'UTC'
t_start = timeline_start
t_end = timeline_end
else:
raise Exception('Please input a valid arrow (time) object!')
def get_events(self, timeline_start, timeline_end, timezone=None): # parse non-recurring events
"""Input an arrow (time) object for:
* the beginning of timeline (events have to end after this time)
* the end of the timeline (events have to begin before this time)
* timezone if events should be formatted to local time
Returns a list of events sorted by date
"""
if type(timeline_start) == arrow.arrow.Arrow:
if timezone == None:
timezone = 'UTC'
t_start = timeline_start
t_end = timeline_end
else:
raise Exception('Please input a valid arrow (time) object!')
# parse non-recurring events # Recurring events time-span has to be in this format:
# "%Y%m%dT%H%M%SZ" (python strftime)
fmt = lambda date: (date.year, date.month, date.day, date.hour,
date.minute, date.second)
# Recurring events time-span has to be in this format: t_start_recurring = fmt(t_start)
# "%Y%m%dT%H%M%SZ" (python strftime) t_end_recurring = fmt(t_end)
fmt = lambda date: (date.year, date.month, date.day, date.hour,
date.minute, date.second)
t_start_recurring = fmt(t_start) # Fetch recurring events
t_end_recurring = fmt(t_end) recurring_events = (recurring_ical_events.of(ical).between(
t_start_recurring, t_end_recurring)
for ical in self.icalendars)
# Fetch recurring events events = (
recurring_events = (recurring_ical_events.of(ical).between( {
t_start_recurring, t_end_recurring) 'title': events.get('SUMMARY').lstrip(),
for ical in self.icalendars)
events = ( 'begin': arrow.get(events.get('DTSTART').dt).to(timezone) if (
{ arrow.get(events.get('dtstart').dt).format('HH:mm') != '00:00')
'title': events.get('SUMMARY').lstrip(), else arrow.get(events.get('DTSTART').dt).replace(tzinfo=timezone),
'begin': arrow.get(events.get('DTSTART').dt).to(timezone) if ( 'end': arrow.get(events.get("DTEND").dt).to(timezone) if (
arrow.get(events.get('dtstart').dt).format('HH:mm') != '00:00') arrow.get(events.get('dtstart').dt).format('HH:mm') != '00:00')
else arrow.get(events.get('DTSTART').dt).replace(tzinfo=timezone), else arrow.get(events.get('DTEND').dt).replace(tzinfo=timezone)
'end':arrow.get(events.get("DTEND").dt).to(timezone) if ( } for ical in recurring_events for events in ical)
arrow.get(events.get('dtstart').dt).format('HH:mm') != '00:00')
else arrow.get(events.get('DTEND').dt).replace(tzinfo=timezone)
} for ical in recurring_events for events in ical) # if any recurring events were found, add them to parsed_events
if events: self.parsed_events += list(events)
# Sort events by their beginning date
self.sort()
# if any recurring events were found, add them to parsed_events return self.parsed_events
if events: self.parsed_events += list(events)
# Sort events by their beginning date def sort(self):
self.sort() """Sort all parsed events in order of beginning time"""
if not self.parsed_events:
logger.debug('no events found to be sorted')
else:
# sort events by date
by_date = lambda event: event['begin']
self.parsed_events.sort(key=by_date)
return self.parsed_events def clear_events(self):
"""clear previously parsed events"""
def sort(self): self.parsed_events = []
"""Sort all parsed events in order of beginning time"""
if not self.parsed_events:
logger.debug('no events found to be sorted')
else:
# sort events by date
by_date = lambda event: event['begin']
self.parsed_events.sort(key=by_date)
@staticmethod
def all_day(event):
"""Check if an event is an all day event.
Returns True if event is all day, else False
"""
if not ('end' and 'begin') in event:
print('Events must have a starting and ending time')
raise Exception('This event is not valid!')
else:
begin, end = event['begin'], event['end']
duration = end - begin
if (begin.format('HH:mm') == '00:00' and end.format('HH:mm') == '00:00'
and duration.days >= 1):
return True
else:
return False
def clear_events(self): @staticmethod
"""clear previously parsed events""" def get_system_tz():
"""Get the timezone set by the system"""
self.parsed_events = [] try:
local_tz = time.tzname[1]
except:
print('System timezone could not be parsed!')
print('Please set timezone manually!. Setting timezone to None...')
local_tz = None
return local_tz
@staticmethod def show_events(self, fmt='DD MMM YY HH:mm'):
def all_day(event): """print all parsed events in a more readable way
"""Check if an event is an all day event. use the format (fmt) parameter to specify the date format
Returns True if event is all day, else False see https://arrow.readthedocs.io/en/latest/#supported-tokens
""" for more info tokens
if not ('end' and 'begin') in event: """
print('Events must have a starting and ending time')
raise Exception('This event is not valid!')
else:
begin, end = event['begin'], event['end']
duration = end - begin
if (begin.format('HH:mm') == '00:00' and end.format('HH:mm') == '00:00'
and duration.days >= 1):
return True
else:
return False
@staticmethod if not self.parsed_events:
def get_system_tz(): logger.debug('no events found to be shown')
"""Get the timezone set by the system""" else:
line_width = max(len(_['title']) for _ in self.parsed_events)
try: for events in self.parsed_events:
local_tz = time.tzname[1] title = events['title']
except: begin, end = events['begin'].format(fmt), events['end'].format(fmt)
print('System timezone could not be parsed!') print('{0} {1} | {2} | {3}'.format(
print('Please set timezone manually!. Setting timezone to None...') title, ' ' * (line_width - len(title)), begin, end))
local_tz = None
return local_tz
def show_events(self, fmt='DD MMM YY HH:mm'):
"""print all parsed events in a more readable way
use the format (fmt) parameter to specify the date format
see https://arrow.readthedocs.io/en/latest/#supported-tokens
for more info tokens
"""
if not self.parsed_events:
logger.debug('no events found to be shown')
else:
line_width = max(len(_['title']) for _ in self.parsed_events)
for events in self.parsed_events:
title = events['title']
begin, end = events['begin'].format(fmt), events['end'].format(fmt)
print('{0} {1} | {2} | {3}'.format(
title, ' ' * (line_width - len(title)), begin, end))
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone mode') print(f'running {filename} in standalone mode')

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Custom image class for Inkycal Project Custom image class for Inkycal Project
@@ -18,317 +18,316 @@ import logging
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Inkyimage: class Inkyimage:
"""Custom Imge class written for commonly used image operations. """Custom Imge class written for commonly used image operations.
"""
def __init__(self, image=None):
"""Initialize Inkyimage module"""
# no image initially
self.image = image
# give an OK message
logger.info(f'{filename} loaded')
def load(self, path):
"""loads an image from a URL or filepath.
Args:
- path:The full path or url of the image file
e.g. `https://sample.com/logo.png` or `/home/pi/Downloads/nice_pic.png`
Raises:
- FileNotFoundError: This Exception is raised when the file could not be
found.
- OSError: A OSError is raised when the URL doesn't point to the correct
file-format, i.e. is not an image
- TypeError: if the URLS doesn't start with htpp
""" """
# Try to open the image if it exists and is an image file
try:
if path.startswith('http'):
logger.info('loading image from URL')
image = Image.open(requests.get(path, stream=True).raw)
else:
logger.info('loading image from local path')
image = Image.open(path)
except FileNotFoundError:
logger.error('No image file found', exc_info=True)
raise Exception('Your file could not be found. Please check the filepath')
except OSError:
logger.error('Invalid Image file provided', exc_info=True)
raise Exception('Please check if the path points to an image file.')
logger.info(f'width: {image.width}, height: {image.height}') def __init__(self, image=None):
"""Initialize Inkyimage module"""
image.convert(mode='RGBA') #convert to a more suitable format # no image initially
self.image = image
logger.info('loaded Image')
def clear(self):
"""Removes currently saved image if present."""
if self.image:
self.image = None
logger.info('cleared previous image')
def _preview(self):
"""Preview the image on gpicview (only works on Rapsbian with Desktop)"""
if self._image_loaded():
path = '/home/pi/Desktop/'
self.image.save(path+'temp.png')
os.system("gpicview "+path+'temp.png')
os.system('rm '+path+'temp.png')
@staticmethod
def preview(image):
""""Previews an image on gpicview (only works on Rapsbian with Desktop).
"""
path = '/home/pi/Desktop/'
image.save(path+'temp.png')
os.system("gpicview "+path+'temp.png')
os.system('rm '+path+'temp.png')
def _image_loaded(self):
"""returns True if image was loaded"""
if self.image:
return True
else:
logger.error('image not loaded')
return False
def flip(self, angle):
"""Flips the image by the given angle.
Args:
- angle:->int. A multiple of 90, e.g. 90, 180, 270, 360.
"""
if self._image_loaded():
image = self.image
if not angle % 90 == 0:
logger.error('Angle must be a multiple of 90')
return
image = image.rotate(angle, expand = True)
self.image = image
logger.info(f'flipped image by {angle} degrees')
def autoflip(self, layout):
"""flips the image automatically to the given layout.
Args:
- layout:-> str. Choose `horizontal` or `vertical`.
Checks the image's width and height.
In horizontal mode, the image is flipped if the image height is greater
than the image width.
In vertical mode, the image is flipped if the image width is greater
than the image height.
"""
if self._image_loaded():
image = self.image
if layout == 'horizontal':
if (image.height > image.width):
logger.info('image width greater than image height, flipping')
image = image.rotate(90, expand=True)
elif layout == 'vertical':
if (image.width > image.height):
logger.info('image width greater than image height, flipping')
image = image.rotate(90, expand=True)
else:
logger.error('layout not supported')
return
self.image = image
def remove_alpha(self):
"""Removes transparency if image has transparency.
Checks if an image has an alpha band and replaces the transparency with
white pixels.
"""
if self._image_loaded():
image = self.image
if len(image.getbands()) == 4:
logger.info('removing alpha channel')
bg = Image.new('RGBA', (image.width, image.height), 'white')
im = Image.alpha_composite(bg, image)
self.image.paste(im, (0,0))
logger.info('removed transparency')
def resize(self, width=None, height=None):
"""Resize an image to desired width or height"""
if self._image_loaded():
if width == None and height == None:
logger.error('no height of width specified')
return
image = self.image
if width:
initial_width = image.width
wpercent = (width/float(image.width))
hsize = int((float(image.height)*float(wpercent)))
image = image.resize((width, hsize), Image.ANTIALIAS)
logger.info(f"resized image from {initial_width} to {image.width}")
self.image = image self.image = image
if height: # give an OK message
initial_height = image.height logger.info(f'{filename} loaded')
hpercent = (height / float(image.height))
wsize = int(float(image.width) * float(hpercent)) def load(self, path):
image = image.resize((wsize, height), Image.ANTIALIAS) """loads an image from a URL or filepath.
logger.info(f"resized image from {initial_height} to {image.height}")
Args:
- path:The full path or url of the image file
e.g. `https://sample.com/logo.png` or `/home/pi/Downloads/nice_pic.png`
Raises:
- FileNotFoundError: This Exception is raised when the file could not be
found.
- OSError: A OSError is raised when the URL doesn't point to the correct
file-format, i.e. is not an image
- TypeError: if the URLS doesn't start with htpp
"""
# Try to open the image if it exists and is an image file
try:
if path.startswith('http'):
logger.info('loading image from URL')
image = Image.open(requests.get(path, stream=True).raw)
else:
logger.info('loading image from local path')
image = Image.open(path)
except FileNotFoundError:
logger.error('No image file found', exc_info=True)
raise Exception('Your file could not be found. Please check the filepath')
except OSError:
logger.error('Invalid Image file provided', exc_info=True)
raise Exception('Please check if the path points to an image file.')
logger.info(f'width: {image.width}, height: {image.height}')
image.convert(mode='RGBA') # convert to a more suitable format
self.image = image self.image = image
logger.info('loaded Image')
@staticmethod def clear(self):
def merge(image1, image2): """Removes currently saved image if present."""
"""Merges two images into one. if self.image:
self.image = None
logger.info('cleared previous image')
Replaces white pixels of the first image with transparent ones. Then pastes def _preview(self):
the first image on the second one. """Preview the image on gpicview (only works on Rapsbian with Desktop)"""
if self._image_loaded():
path = '/home/pi/Desktop/'
self.image.save(path + 'temp.png')
os.system("gpicview " + path + 'temp.png')
os.system('rm ' + path + 'temp.png')
Args: @staticmethod
- image1: A PIL Image object in 'RGBA' mode. def preview(image):
- image2: A PIL Image object in 'RGBA' mode. """"Previews an image on gpicview (only works on Rapsbian with Desktop).
"""
path = '/home/pi/Desktop/'
image.save(path + 'temp.png')
os.system("gpicview " + path + 'temp.png')
os.system('rm ' + path + 'temp.png')
Returns: def _image_loaded(self):
- A single image. """returns True if image was loaded"""
""" if self.image:
return True
else:
logger.error('image not loaded')
return False
def clear_white(img): def flip(self, angle):
"""Replace all white pixels from image with transparent pixels""" """Flips the image by the given angle.
x = numpy.asarray(img.convert('RGBA')).copy()
x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(numpy.uint8)
return Image.fromarray(x)
image2 = clear_white(image2) Args:
image1.paste(image2, (0,0), image2) - angle:->int. A multiple of 90, e.g. 90, 180, 270, 360.
logger.info('merged given images into one') """
if self._image_loaded():
return image1 image = self.image
if not angle % 90 == 0:
logger.error('Angle must be a multiple of 90')
return
image = image.rotate(angle, expand=True)
self.image = image
logger.info(f'flipped image by {angle} degrees')
def to_palette(self, palette, dither=True): def autoflip(self, layout):
"""Maps an image to a given colour palette. """flips the image automatically to the given layout.
Maps each pixel from the image to a colour from the palette. Args:
- layout:-> str. Choose `horizontal` or `vertical`.
Args: Checks the image's width and height.
- palette: A supported token. (see below)
- dither:->bool. Use dithering? Set to `False` for solid colour fills.
Returns: In horizontal mode, the image is flipped if the image height is greater
- two images: one for the coloured band and one for the black band. than the image width.
Raises: In vertical mode, the image is flipped if the image width is greater
- ValueError if palette token is not supported than the image height.
"""
if self._image_loaded():
Supported palette tokens: image = self.image
if layout == 'horizontal':
if (image.height > image.width):
logger.info('image width greater than image height, flipping')
image = image.rotate(90, expand=True)
>>> 'bwr' # black-white-red elif layout == 'vertical':
>>> 'bwy' # black-white-yellow if (image.width > image.height):
>>> 'bw' # black-white logger.info('image width greater than image height, flipping')
""" image = image.rotate(90, expand=True)
# Check if an image is loaded else:
if self._image_loaded(): logger.error('layout not supported')
image = self.image.convert('RGB') return
else: self.image = image
logger.error('No image loaded')
if palette == 'bwr': def remove_alpha(self):
# black-white-red palette """Removes transparency if image has transparency.
pal = [255,255,255, 0,0,0, 255,0,0]
elif palette == 'bwy': Checks if an image has an alpha band and replaces the transparency with
# black-white-yellow palette white pixels.
pal = [255,255,255, 0,0,0, 255,255,0] """
if self._image_loaded():
image = self.image
elif palette == 'bw': if len(image.getbands()) == 4:
pal = None logger.info('removing alpha channel')
bg = Image.new('RGBA', (image.width, image.height), 'white')
im = Image.alpha_composite(bg, image)
else: self.image.paste(im, (0, 0))
logger.error('The given palette is unsupported.') logger.info('removed transparency')
raise ValueError('The given palette is not supported.')
if pal: def resize(self, width=None, height=None):
# The palette needs to have 256 colors, for this, the black-colour """Resize an image to desired width or height"""
# is added until the if self._image_loaded():
colours = len(pal) // 3
#print(f'The palette has {colours} colours')
if 256 % colours != 0: if width == None and height == None:
#print('Filling palette with black') logger.error('no height of width specified')
pal += (256 % colours) * [0,0,0] return
#print(pal) image = self.image
colours = len(pal) // 3
#print(f'The palette now has {colours} colours')
# Create a dummy image to be used as a palette if width:
palette_im = Image.new('P', (1,1)) initial_width = image.width
wpercent = (width / float(image.width))
hsize = int((float(image.height) * float(wpercent)))
image = image.resize((width, hsize), Image.ANTIALIAS)
logger.info(f"resized image from {initial_width} to {image.width}")
self.image = image
# Attach the created palette. The palette should have 256 colours if height:
# equivalent to 768 integers initial_height = image.height
palette_im.putpalette(pal* (256//colours)) hpercent = (height / float(image.height))
wsize = int(float(image.width) * float(hpercent))
image = image.resize((wsize, height), Image.ANTIALIAS)
logger.info(f"resized image from {initial_height} to {image.height}")
self.image = image
# Quantize the image to given palette @staticmethod
quantized_im = image.quantize(palette=palette_im, dither=dither) def merge(image1, image2):
quantized_im = quantized_im.convert('RGB') """Merges two images into one.
# get rgb of the non-black-white colour from the palette Replaces white pixels of the first image with transparent ones. Then pastes
rgb = [pal[x:x+3] for x in range(0, len(pal),3)] the first image on the second one.
rgb = [col for col in rgb if col != [0,0,0] and col != [255,255,255]][0]
r_col, g_col, b_col = rgb
#print(f'r:{r_col} g:{g_col} b:{b_col}')
# Create an image buffer for black pixels Args:
buffer1 = numpy.array(quantized_im) - image1: A PIL Image object in 'RGBA' mode.
- image2: A PIL Image object in 'RGBA' mode.
# Get RGB values of each pixel Returns:
r,g,b = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2] - A single image.
"""
# convert coloured pixels to white def clear_white(img):
buffer1[numpy.logical_and(r==r_col, g==g_col)] = [255,255,255] """Replace all white pixels from image with transparent pixels"""
x = numpy.asarray(img.convert('RGBA')).copy()
x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(numpy.uint8)
return Image.fromarray(x)
# reconstruct image for black-band image2 = clear_white(image2)
im_black = Image.fromarray(buffer1) image1.paste(image2, (0, 0), image2)
logger.info('merged given images into one')
# Create a buffer for coloured pixels return image1
buffer2 = numpy.array(quantized_im)
# Get RGB values of each pixel def to_palette(self, palette, dither=True):
r,g,b = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2] """Maps an image to a given colour palette.
# convert black pixels to white Maps each pixel from the image to a colour from the palette.
buffer2[numpy.logical_and(r==0, g==0)] = [255,255,255]
# convert non-white pixels to black Args:
buffer2[numpy.logical_and(g==g_col, b==0)] = [0,0,0] - palette: A supported token. (see below)
- dither:->bool. Use dithering? Set to `False` for solid colour fills.
# reconstruct image for colour-band Returns:
im_colour = Image.fromarray(buffer2) - two images: one for the coloured band and one for the black band.
#self.preview(im_black) Raises:
#self.preview(im_colour) - ValueError if palette token is not supported
else: Supported palette tokens:
im_black = image.convert('1', dither=dither)
im_colour = Image.new(mode='RGB', size=im_black.size, color='white')
logger.info('mapped image to specified palette') >>> 'bwr' # black-white-red
>>> 'bwy' # black-white-yellow
>>> 'bw' # black-white
"""
# Check if an image is loaded
if self._image_loaded():
image = self.image.convert('RGB')
else:
logger.error('No image loaded')
return im_black, im_colour if palette == 'bwr':
# black-white-red palette
pal = [255, 255, 255, 0, 0, 0, 255, 0, 0]
elif palette == 'bwy':
# black-white-yellow palette
pal = [255, 255, 255, 0, 0, 0, 255, 255, 0]
elif palette == 'bw':
pal = None
else:
logger.error('The given palette is unsupported.')
raise ValueError('The given palette is not supported.')
if pal:
# The palette needs to have 256 colors, for this, the black-colour
# is added until the
colours = len(pal) // 3
# print(f'The palette has {colours} colours')
if 256 % colours != 0:
# print('Filling palette with black')
pal += (256 % colours) * [0, 0, 0]
# print(pal)
colours = len(pal) // 3
# print(f'The palette now has {colours} colours')
# Create a dummy image to be used as a palette
palette_im = Image.new('P', (1, 1))
# Attach the created palette. The palette should have 256 colours
# equivalent to 768 integers
palette_im.putpalette(pal * (256 // colours))
# Quantize the image to given palette
quantized_im = image.quantize(palette=palette_im, dither=dither)
quantized_im = quantized_im.convert('RGB')
# get rgb of the non-black-white colour from the palette
rgb = [pal[x:x + 3] for x in range(0, len(pal), 3)]
rgb = [col for col in rgb if col != [0, 0, 0] and col != [255, 255, 255]][0]
r_col, g_col, b_col = rgb
# print(f'r:{r_col} g:{g_col} b:{b_col}')
# Create an image buffer for black pixels
buffer1 = numpy.array(quantized_im)
# Get RGB values of each pixel
r, g, b = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2]
# convert coloured pixels to white
buffer1[numpy.logical_and(r == r_col, g == g_col)] = [255, 255, 255]
# reconstruct image for black-band
im_black = Image.fromarray(buffer1)
# Create a buffer for coloured pixels
buffer2 = numpy.array(quantized_im)
# Get RGB values of each pixel
r, g, b = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2]
# convert black pixels to white
buffer2[numpy.logical_and(r == 0, g == 0)] = [255, 255, 255]
# convert non-white pixels to black
buffer2[numpy.logical_and(g == g_col, b == 0)] = [0, 0, 0]
# reconstruct image for colour-band
im_colour = Image.fromarray(buffer2)
# self.preview(im_black)
# self.preview(im_colour)
else:
im_black = image.convert('1', dither=dither)
im_colour = Image.new(mode='RGB', size=im_black.size, color='white')
logger.info('mapped image to specified palette')
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone/debug mode') print(f'running {filename} in standalone/debug mode')

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Agenda module for Inky-Calendar Project Agenda module for Inky-Calendar Project
Copyright by aceisace Copyright by aceisace
@@ -15,219 +15,219 @@ import arrow
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Agenda(inkycal_module): class Agenda(inkycal_module):
"""Agenda class """Agenda class
Create agenda and show events from given icalendars Create agenda and show events from given icalendars
""" """
name = "Agenda - Display upcoming events from given iCalendars" name = "Agenda - Display upcoming events from given iCalendars"
requires = { requires = {
"ical_urls" : { "ical_urls": {
"label":"iCalendar URL/s, separate multiple ones with a comma", "label": "iCalendar URL/s, separate multiple ones with a comma",
}, },
} }
optional = { optional = {
"ical_files" : { "ical_files": {
"label":"iCalendar filepaths, separated with a comma", "label": "iCalendar filepaths, separated with a comma",
}, },
"date_format":{ "date_format": {
"label":"Use an arrow-supported token for custom date formatting "+ "label": "Use an arrow-supported token for custom date formatting " +
"see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. ddd D MMM", "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. ddd D MMM",
"default": "ddd D MMM", "default": "ddd D MMM",
}, },
"time_format":{
"label":"Use an arrow-supported token for custom time formatting "+
"see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm",
"default":"HH:mm",
},
"time_format": {
"label": "Use an arrow-supported token for custom time formatting " +
"see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm",
"default": "HH:mm",
},
} }
def __init__(self, config): def __init__(self, config):
"""Initialize inkycal_agenda module""" """Initialize inkycal_agenda module"""
super().__init__(config) super().__init__(config)
config = config['config'] config = config['config']
# Check if all required parameters are present # Check if all required parameters are present
for param in self.requires: for param in self.requires:
if not param in config: if not param in config:
raise Exception(f'config is missing {param}') raise Exception(f'config is missing {param}')
logger.exception(f'config is missing "{param}"') logger.exception(f'config is missing "{param}"')
# module specific parameters # module specific parameters
self.date_format = config['date_format'] self.date_format = config['date_format']
self.time_format = config['time_format'] self.time_format = config['time_format']
self.language = config['language'] self.language = config['language']
# Check if ical_files is an empty string # Check if ical_files is an empty string
if config['ical_urls'] and isinstance(config['ical_urls'], str): if config['ical_urls'] and isinstance(config['ical_urls'], str):
self.ical_urls = config['ical_urls'].split(',') self.ical_urls = config['ical_urls'].split(',')
else: else:
self.ical_urls = config['ical_urls'] self.ical_urls = config['ical_urls']
# Check if ical_files is an empty string # Check if ical_files is an empty string
if config['ical_files'] and isinstance(config['ical_files'], str): if config['ical_files'] and isinstance(config['ical_files'], str):
self.ical_files = config['ical_files'].split(',') self.ical_files = config['ical_files'].split(',')
else: else:
self.ical_files = config['ical_files'] self.ical_files = config['ical_files']
# Additional config # Additional config
self.timezone = get_system_tz() self.timezone = get_system_tz()
# give an OK message # give an OK message
print(f'{filename} loaded') print(f'{filename} loaded')
def generate_image(self): def generate_image(self):
"""Generate image for this module""" """Generate image for this module"""
# Define new image size with respect to padding # Define new image size with respect to padding
im_width = int(self.width - (2 * self.padding_left)) im_width = int(self.width - (2 * self.padding_left))
im_height = int(self.height - (2 * self.padding_top)) im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height im_size = im_width, im_height
logger.info(f'Image size: {im_size}') logger.info(f'Image size: {im_size}')
# Create an image for black pixels and one for coloured pixels # Create an image for black pixels and one for coloured pixels
im_black = Image.new('RGB', size = im_size, color = 'white') im_black = Image.new('RGB', size=im_size, color='white')
im_colour = Image.new('RGB', size = im_size, color = 'white') im_colour = Image.new('RGB', size=im_size, color='white')
# Calculate the max number of lines that can fit on the image # Calculate the max number of lines that can fit on the image
line_spacing = 1 line_spacing = 1
line_height = int(self.font.getsize('hg')[1]) + line_spacing line_height = int(self.font.getsize('hg')[1]) + line_spacing
line_width = im_width line_width = im_width
max_lines = im_height // line_height max_lines = im_height // line_height
logger.debug(f'max lines: {max_lines}') logger.debug(f'max lines: {max_lines}')
# Create timeline for agenda # Create timeline for agenda
now = arrow.now() now = arrow.now()
today = now.floor('day') today = now.floor('day')
# Create a list of dates for the next days # Create a list of dates for the next days
agenda_events = [ agenda_events = [
{'begin':today.shift(days=+_), {'begin': today.shift(days=+_),
'title': today.shift(days=+_).format( 'title': today.shift(days=+_).format(
self.date_format,locale=self.language)} self.date_format, locale=self.language)}
for _ in range(max_lines)] for _ in range(max_lines)]
# Load icalendar from config # Load icalendar from config
self.ical = iCalendar() self.ical = iCalendar()
parser = self.ical parser = self.ical
if self.ical_urls: if self.ical_urls:
parser.load_url(self.ical_urls) parser.load_url(self.ical_urls)
if self.ical_files: if self.ical_files:
parser.load_from_file(self.ical_files) parser.load_from_file(self.ical_files)
# Load events from all icalendar in timerange # Load events from all icalendar in timerange
upcoming_events = parser.get_events(today, agenda_events[-1]['begin'], upcoming_events = parser.get_events(today, agenda_events[-1]['begin'],
self.timezone) self.timezone)
# Sort events by beginning time # Sort events by beginning time
parser.sort() parser.sort()
#parser.show_events() # parser.show_events()
# Set the width for date, time and event titles # Set the width for date, time and event titles
date_width = int(max([self.font.getsize( date_width = int(max([self.font.getsize(
dates['begin'].format(self.date_format, locale=self.language))[0] dates['begin'].format(self.date_format, locale=self.language))[0]
for dates in agenda_events]) * 1.2) for dates in agenda_events]) * 1.2)
logger.debug(f'date_width: {date_width}') logger.debug(f'date_width: {date_width}')
# Calculate positions for each line # Calculate positions for each line
line_pos = [(0, int(line * line_height)) for line in range(max_lines)] line_pos = [(0, int(line * line_height)) for line in range(max_lines)]
logger.debug(f'line_pos: {line_pos}') logger.debug(f'line_pos: {line_pos}')
# Check if any events were filtered # Check if any events were filtered
if upcoming_events: if upcoming_events:
logger.info('Managed to parse events from urls') logger.info('Managed to parse events from urls')
# Find out how much space the event times take # Find out how much space the event times take
time_width = int(max([self.font.getsize( time_width = int(max([self.font.getsize(
events['begin'].format(self.time_format, locale=self.language))[0] events['begin'].format(self.time_format, locale=self.language))[0]
for events in upcoming_events]) * 1.2) for events in upcoming_events]) * 1.2)
logger.debug(f'time_width: {time_width}') logger.debug(f'time_width: {time_width}')
# Calculate x-pos for time # Calculate x-pos for time
x_time = date_width x_time = date_width
logger.debug(f'x-time: {x_time}') logger.debug(f'x-time: {x_time}')
# Find out how much space is left for event titles # Find out how much space is left for event titles
event_width = im_width - time_width - date_width event_width = im_width - time_width - date_width
logger.debug(f'width for events: {event_width}') logger.debug(f'width for events: {event_width}')
# Calculate x-pos for event titles # Calculate x-pos for event titles
x_event = date_width + time_width x_event = date_width + time_width
logger.debug(f'x-event: {x_event}') logger.debug(f'x-event: {x_event}')
# Merge list of dates and list of events # Merge list of dates and list of events
agenda_events += upcoming_events agenda_events += upcoming_events
# Sort the combined list in chronological order of dates # Sort the combined list in chronological order of dates
by_date = lambda event: event['begin'] by_date = lambda event: event['begin']
agenda_events.sort(key = by_date) agenda_events.sort(key=by_date)
# Delete more entries than can be displayed (max lines) # Delete more entries than can be displayed (max lines)
del agenda_events[max_lines:] del agenda_events[max_lines:]
self._agenda_events = agenda_events self._agenda_events = agenda_events
cursor = 0 cursor = 0
for _ in agenda_events: for _ in agenda_events:
title = _['title'] title = _['title']
# Check if item is a date # Check if item is a date
if not 'end' in _: if not 'end' in _:
ImageDraw.Draw(im_colour).line( ImageDraw.Draw(im_colour).line(
(0, line_pos[cursor][1], im_width, line_pos[cursor][1]), (0, line_pos[cursor][1], im_width, line_pos[cursor][1]),
fill = 'black') fill='black')
write(im_black, line_pos[cursor], (date_width, line_height), write(im_black, line_pos[cursor], (date_width, line_height),
title, font = self.font, alignment='left') title, font=self.font, alignment='left')
cursor += 1 cursor += 1
# Check if item is an event # Check if item is an event
if 'end' in _: if 'end' in _:
time = _['begin'].format(self.time_format) time = _['begin'].format(self.time_format)
# Check if event is all day, if not, add the time # Check if event is all day, if not, add the time
if parser.all_day(_) == False: if parser.all_day(_) == False:
write(im_black, (x_time, line_pos[cursor][1]), write(im_black, (x_time, line_pos[cursor][1]),
(time_width, line_height), time, (time_width, line_height), time,
font = self.font, alignment='left') font=self.font, alignment='left')
write(im_black, (x_event, line_pos[cursor][1]), write(im_black, (x_event, line_pos[cursor][1]),
(event_width, line_height), (event_width, line_height),
''+title, font = self.font, alignment='left') '' + title, font=self.font, alignment='left')
cursor += 1 cursor += 1
# If no events were found, write only dates and lines # If no events were found, write only dates and lines
else: else:
logger.info('no events found') logger.info('no events found')
cursor = 0 cursor = 0
for _ in agenda_events: for _ in agenda_events:
title = _['title'] title = _['title']
ImageDraw.Draw(im_colour).line( ImageDraw.Draw(im_colour).line(
(0, line_pos[cursor][1], im_width, line_pos[cursor][1]), (0, line_pos[cursor][1], im_width, line_pos[cursor][1]),
fill = 'black') fill='black')
write(im_black, line_pos[cursor], (date_width, line_height), write(im_black, line_pos[cursor], (date_width, line_height),
title, font = self.font, alignment='left') title, font=self.font, alignment='left')
cursor += 1 cursor += 1
# return the images ready for the display
return im_black, im_colour
# return the images ready for the display
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone mode') print(f'running {filename} in standalone mode')

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Calendar module for Inky-Calendar Project Calendar module for Inky-Calendar Project
Copyright by aceisace Copyright by aceisace
@@ -15,332 +15,330 @@ logger = logging.getLogger(filename)
class Calendar(inkycal_module): class Calendar(inkycal_module):
"""Calendar class """Calendar class
Create monthly calendar and show events from given icalendars Create monthly calendar and show events from given icalendars
""" """
name = "Calendar - Show monthly calendar with events from iCalendars" name = "Calendar - Show monthly calendar with events from iCalendars"
optional = { optional = {
"week_starts_on" : { "week_starts_on": {
"label":"When does your week start? (default=Monday)", "label": "When does your week start? (default=Monday)",
"options": ["Monday", "Sunday"], "options": ["Monday", "Sunday"],
"default": "Monday" "default": "Monday"
}, },
"show_events" : { "show_events": {
"label":"Show parsed events? (default = True)", "label": "Show parsed events? (default = True)",
"options": [True, False], "options": [True, False],
"default": True "default": True
}, },
"ical_urls" : { "ical_urls": {
"label":"iCalendar URL/s, separate multiple ones with a comma", "label": "iCalendar URL/s, separate multiple ones with a comma",
}, },
"ical_files" : { "ical_files": {
"label":"iCalendar filepaths, separated with a comma", "label": "iCalendar filepaths, separated with a comma",
}, },
"date_format":{ "date_format": {
"label":"Use an arrow-supported token for custom date formatting "+ "label": "Use an arrow-supported token for custom date formatting " +
"see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM", "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM",
"default": "D MMM", "default": "D MMM",
}, },
"time_format":{ "time_format": {
"label":"Use an arrow-supported token for custom time formatting "+ "label": "Use an arrow-supported token for custom time formatting " +
"see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm", "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm",
"default": "HH:mm" "default": "HH:mm"
}, },
} }
def __init__(self, config): def __init__(self, config):
"""Initialize inkycal_calendar module""" """Initialize inkycal_calendar module"""
super().__init__(config) super().__init__(config)
config = config['config'] config = config['config']
# optional parameters # optional parameters
self.weekstart = config['week_starts_on'] self.weekstart = config['week_starts_on']
self.show_events = config['show_events'] self.show_events = config['show_events']
self.date_format = config["date_format"] self.date_format = config["date_format"]
self.time_format = config['time_format'] self.time_format = config['time_format']
self.language = config['language'] self.language = config['language']
if config['ical_urls'] and isinstance(config['ical_urls'], str): if config['ical_urls'] and isinstance(config['ical_urls'], str):
self.ical_urls = config['ical_urls'].split(',') self.ical_urls = config['ical_urls'].split(',')
else: else:
self.ical_urls = config['ical_urls'] self.ical_urls = config['ical_urls']
if config['ical_files'] and isinstance(config['ical_files'], str): if config['ical_files'] and isinstance(config['ical_files'], str):
self.ical_files = config['ical_files'].split(',') self.ical_files = config['ical_files'].split(',')
else: else:
self.ical_files = config['ical_files'] self.ical_files = config['ical_files']
# additional configuration # additional configuration
self.timezone = get_system_tz() self.timezone = get_system_tz()
self.num_font = ImageFont.truetype( self.num_font = ImageFont.truetype(
fonts['NotoSans-SemiCondensed'], size = self.fontsize) fonts['NotoSans-SemiCondensed'], size=self.fontsize)
# give an OK message # give an OK message
print(f'{filename} loaded') print(f'{filename} loaded')
def generate_image(self): def generate_image(self):
"""Generate image for this module""" """Generate image for this module"""
# Define new image size with respect to padding # Define new image size with respect to padding
im_width = int(self.width - (2 * self.padding_left)) im_width = int(self.width - (2 * self.padding_left))
im_height = int(self.height - (2 * self.padding_top)) im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height im_size = im_width, im_height
logger.info(f'Image size: {im_size}') logger.info(f'Image size: {im_size}')
# Create an image for black pixels and one for coloured pixels # Create an image for black pixels and one for coloured pixels
im_black = Image.new('RGB', size = im_size, color = 'white') im_black = Image.new('RGB', size=im_size, color='white')
im_colour = Image.new('RGB', size = im_size, color = 'white') im_colour = Image.new('RGB', size=im_size, color='white')
# Allocate space for month-names, weekdays etc. # Allocate space for month-names, weekdays etc.
month_name_height = int(im_height * 0.10) month_name_height = int(im_height * 0.10)
weekdays_height = int(self.font.getsize('hg')[1] * 1.25) weekdays_height = int(self.font.getsize('hg')[1] * 1.25)
logger.debug(f"month_name_height: {month_name_height}") logger.debug(f"month_name_height: {month_name_height}")
logger.debug(f"weekdays_height: {weekdays_height}") logger.debug(f"weekdays_height: {weekdays_height}")
if self.show_events == True: if self.show_events == True:
logger.debug("Allocating space for events") logger.debug("Allocating space for events")
calendar_height = int(im_height * 0.6) calendar_height = int(im_height * 0.6)
events_height = im_height - month_name_height - weekdays_height - calendar_height events_height = im_height - month_name_height - weekdays_height - calendar_height
logger.debug(f'calendar-section size: {im_width} x {calendar_height} px') logger.debug(f'calendar-section size: {im_width} x {calendar_height} px')
logger.debug(f'events-section size: {im_width} x {events_height} px') logger.debug(f'events-section size: {im_width} x {events_height} px')
else: else:
logger.debug("Not allocating space for events") logger.debug("Not allocating space for events")
calendar_height = im_height - month_name_height - weekdays_height calendar_height = im_height - month_name_height - weekdays_height
logger.debug(f'calendar-section size: {im_width} x {calendar_height} px') logger.debug(f'calendar-section size: {im_width} x {calendar_height} px')
# Create a 7x6 grid and calculate icon sizes # Create a 7x6 grid and calculate icon sizes
calendar_rows, calendar_cols = 6, 7 calendar_rows, calendar_cols = 6, 7
icon_width = im_width // calendar_cols icon_width = im_width // calendar_cols
icon_height = calendar_height // calendar_rows icon_height = calendar_height // calendar_rows
logger.debug(f"icon_size: {icon_width}x{icon_height}px") logger.debug(f"icon_size: {icon_width}x{icon_height}px")
# Calculate spacings for calendar area # Calculate spacings for calendar area
x_spacing_calendar = int((im_width % calendar_cols) / 2) x_spacing_calendar = int((im_width % calendar_cols) / 2)
y_spacing_calendar = int((im_height % calendar_rows) / 2) y_spacing_calendar = int((im_height % calendar_rows) / 2)
logger.debug(f"x_spacing_calendar: {x_spacing_calendar}") logger.debug(f"x_spacing_calendar: {x_spacing_calendar}")
logger.debug(f"y_spacing_calendar :{y_spacing_calendar}") logger.debug(f"y_spacing_calendar :{y_spacing_calendar}")
# Calculate positions for days of month # Calculate positions for days of month
grid_start_y = (month_name_height + weekdays_height + y_spacing_calendar) grid_start_y = (month_name_height + weekdays_height + y_spacing_calendar)
grid_start_x = x_spacing_calendar grid_start_x = x_spacing_calendar
grid_coordinates = [(grid_start_x + icon_width*x, grid_start_y + icon_height*y) grid_coordinates = [(grid_start_x + icon_width * x, grid_start_y + icon_height * y)
for y in range(calendar_rows) for x in range(calendar_cols)] for y in range(calendar_rows) for x in range(calendar_cols)]
weekday_pos = [(grid_start_x + icon_width*_, month_name_height) for _ in weekday_pos = [(grid_start_x + icon_width * _, month_name_height) for _ in
range(calendar_cols)] range(calendar_cols)]
now = arrow.now(tz = self.timezone) now = arrow.now(tz=self.timezone)
# Set weekstart of calendar to specified weekstart # Set weekstart of calendar to specified weekstart
if self.weekstart == "Monday": if self.weekstart == "Monday":
cal.setfirstweekday(cal.MONDAY) cal.setfirstweekday(cal.MONDAY)
weekstart = now.shift(days = - now.weekday()) weekstart = now.shift(days=- now.weekday())
else: else:
cal.setfirstweekday(cal.SUNDAY) cal.setfirstweekday(cal.SUNDAY)
weekstart = now.shift(days = - now.isoweekday()) weekstart = now.shift(days=- now.isoweekday())
# Write the name of current month # Write the name of current month
write(im_black, (0,0),(im_width, month_name_height), write(im_black, (0, 0), (im_width, month_name_height),
str(now.format('MMMM',locale=self.language)), font = self.font, str(now.format('MMMM', locale=self.language)), font=self.font,
autofit = True) autofit=True)
# Set up weeknames in local language and add to main section # Set up weeknames in local language and add to main section
weekday_names = [weekstart.shift(days=+_).format('ddd',locale=self.language) weekday_names = [weekstart.shift(days=+_).format('ddd', locale=self.language)
for _ in range(7)] for _ in range(7)]
logger.debug(f'weekday names: {weekday_names}') logger.debug(f'weekday names: {weekday_names}')
for _ in range(len(weekday_pos)): for _ in range(len(weekday_pos)):
write( write(
im_black, im_black,
weekday_pos[_], weekday_pos[_],
(icon_width, weekdays_height), (icon_width, weekdays_height),
weekday_names[_], weekday_names[_],
font = self.font, font=self.font,
autofit = True, autofit=True,
fill_height=1.0 fill_height=1.0
)
# Create a calendar template and flatten (remove nestings)
flatten = lambda z: [x for y in z for x in y]
calendar_flat = flatten(cal.monthcalendar(now.year, now.month))
#logger.debug(f" calendar_flat: {calendar_flat}")
# Map days of month to co-ordinates of grid -> 3: (row2_x,col3_y)
grid = {}
for i in calendar_flat:
if i != 0:
grid[i] = grid_coordinates[calendar_flat.index(i)]
#logger.debug(f"grid:{grid}")
# remove zeros from calendar since they are not required
calendar_flat = [num for num in calendar_flat if num != 0]
# Add the numbers on the correct positions
for number in calendar_flat:
if number != int(now.day):
write(im_black, grid[number], (icon_width, icon_height),
str(number), font = self.num_font, fill_height = 0.5, fill_width=0.5)
# Draw a red/black circle with the current day of month in white
icon = Image.new('RGBA', (icon_width, icon_height))
current_day_pos = grid[int(now.day)]
x_circle,y_circle = int(icon_width/2), int(icon_height/2)
radius = int(icon_width * 0.2)
ImageDraw.Draw(icon).ellipse(
(x_circle-radius, y_circle-radius, x_circle+radius, y_circle+radius),
fill= 'black', outline=None)
write(icon, (0,0), (icon_width, icon_height), str(now.day),
font=self.num_font, fill_height = 0.5, colour='white')
im_colour.paste(icon, current_day_pos, icon)
# If events should be loaded and shown...
if self.show_events == True:
# If this month requires 5 instead of 6 rows, increase event section height
if len(cal.monthcalendar(now.year, now.month)) == 5:
events_height += icon_height
# If this month requires 4 instead of 6 rows, increase event section height
elif len(cal.monthcalendar(now.year, now.month)) == 4:
events_height += icon_height * 2
# import the ical-parser
from inkycal.modules.ical_parser import iCalendar
# find out how many lines can fit at max in the event section
line_spacing = 0
max_event_lines = events_height // (self.font.getsize('hg')[1] +
line_spacing)
# generate list of coordinates for each line
events_offset = im_height - events_height
event_lines = [(0, events_offset + int(events_height/max_event_lines*_))
for _ in range(max_event_lines)]
#logger.debug(f"event_lines {event_lines}")
# timeline for filtering events within this month
month_start = arrow.get(now.floor('month'))
month_end = arrow.get(now.ceil('month'))
# fetch events from given icalendars
self.ical = iCalendar()
parser = self.ical
if self.ical_urls:
parser.load_url(self.ical_urls)
if self.ical_files:
parser.load_from_file(self.ical_files)
# Filter events for full month (even past ones) for drawing event icons
month_events = parser.get_events(month_start, month_end, self.timezone)
parser.sort()
self.month_events = month_events
# find out on which days of this month events are taking place
days_with_events = [int(events['begin'].format('D')) for events in
month_events]
# remove duplicates (more than one event in a single day)
list(set(days_with_events)).sort()
self._days_with_events = days_with_events
# Draw a border with specified parameters around days with events
for days in days_with_events:
if days in grid:
draw_border(
im_colour,
grid[days],
(icon_width, icon_height),
radius = 6,
thickness= 1,
shrinkage = (0.4, 0.2)
) )
# Filter upcoming events until 4 weeks in the future # Create a calendar template and flatten (remove nestings)
parser.clear_events() flatten = lambda z: [x for y in z for x in y]
upcoming_events = parser.get_events(now, now.shift(weeks=4), calendar_flat = flatten(cal.monthcalendar(now.year, now.month))
self.timezone) # logger.debug(f" calendar_flat: {calendar_flat}")
self._upcoming_events = upcoming_events
# delete events which won't be able to fit (more events than lines) # Map days of month to co-ordinates of grid -> 3: (row2_x,col3_y)
upcoming_events[:max_event_lines] grid = {}
for i in calendar_flat:
if i != 0:
grid[i] = grid_coordinates[calendar_flat.index(i)]
# logger.debug(f"grid:{grid}")
# remove zeros from calendar since they are not required
calendar_flat = [num for num in calendar_flat if num != 0]
# Check if any events were found in the given timerange # Add the numbers on the correct positions
if upcoming_events: for number in calendar_flat:
if number != int(now.day):
write(im_black, grid[number], (icon_width, icon_height),
str(number), font=self.num_font, fill_height=0.5, fill_width=0.5)
# Find out how much space (width) the date format requires # Draw a red/black circle with the current day of month in white
lang = self.language icon = Image.new('RGBA', (icon_width, icon_height))
current_day_pos = grid[int(now.day)]
x_circle, y_circle = int(icon_width / 2), int(icon_height / 2)
radius = int(icon_width * 0.2)
ImageDraw.Draw(icon).ellipse(
(x_circle - radius, y_circle - radius, x_circle + radius, y_circle + radius),
fill='black', outline=None)
write(icon, (0, 0), (icon_width, icon_height), str(now.day),
font=self.num_font, fill_height=0.5, colour='white')
im_colour.paste(icon, current_day_pos, icon)
date_width = int(max([self.font.getsize( # If events should be loaded and shown...
events['begin'].format(self.date_format,locale=lang))[0] if self.show_events == True:
for events in upcoming_events]) * 1.1)
time_width = int(max([self.font.getsize( # If this month requires 5 instead of 6 rows, increase event section height
events['begin'].format(self.time_format, locale=lang))[0] if len(cal.monthcalendar(now.year, now.month)) == 5:
for events in upcoming_events]) * 1.1) events_height += icon_height
line_height = self.font.getsize('hg')[1] + line_spacing # If this month requires 4 instead of 6 rows, increase event section height
elif len(cal.monthcalendar(now.year, now.month)) == 4:
events_height += icon_height * 2
event_width_s = im_width - date_width - time_width # import the ical-parser
event_width_l = im_width - date_width from inkycal.modules.ical_parser import iCalendar
# Display upcoming events below calendar # find out how many lines can fit at max in the event section
tomorrow = now.shift(days=1).floor('day') line_spacing = 0
in_two_days = now.shift(days=2).floor('day') max_event_lines = events_height // (self.font.getsize('hg')[1] +
line_spacing)
cursor = 0 # generate list of coordinates for each line
for event in upcoming_events: events_offset = im_height - events_height
if cursor < len(event_lines): event_lines = [(0, events_offset + int(events_height / max_event_lines * _))
name = event['title'] for _ in range(max_event_lines)]
date = event['begin'].format(self.date_format, locale=lang)
time = event['begin'].format(self.time_format, locale=lang)
#logger.debug(f"name:{name} date:{date} time:{time}")
if now < event['end']: # logger.debug(f"event_lines {event_lines}")
write(im_colour, event_lines[cursor], (date_width, line_height),
date, font=self.font, alignment = 'left')
# Check if event is all day # timeline for filtering events within this month
if parser.all_day(event) == True: month_start = arrow.get(now.floor('month'))
write(im_black, (date_width, event_lines[cursor][1]), month_end = arrow.get(now.ceil('month'))
(event_width_l, line_height), name, font=self.font,
alignment = 'left')
else:
write(im_black, (date_width, event_lines[cursor][1]),
(time_width, line_height), time, font=self.font,
alignment = 'left')
write(im_black, (date_width+time_width,event_lines[cursor][1]), # fetch events from given icalendars
(event_width_s, line_height), name, font=self.font, self.ical = iCalendar()
alignment = 'left') parser = self.ical
cursor += 1
else: if self.ical_urls:
symbol = '- ' parser.load_url(self.ical_urls)
while self.font.getsize(symbol)[0] < im_width*0.9: if self.ical_files:
symbol += ' -' parser.load_from_file(self.ical_files)
write(im_black, event_lines[0],
(im_width, self.font.getsize(symbol)[1]), symbol, # Filter events for full month (even past ones) for drawing event icons
font = self.font) month_events = parser.get_events(month_start, month_end, self.timezone)
parser.sort()
self.month_events = month_events
# find out on which days of this month events are taking place
days_with_events = [int(events['begin'].format('D')) for events in
month_events]
# remove duplicates (more than one event in a single day)
list(set(days_with_events)).sort()
self._days_with_events = days_with_events
# Draw a border with specified parameters around days with events
for days in days_with_events:
if days in grid:
draw_border(
im_colour,
grid[days],
(icon_width, icon_height),
radius=6,
thickness=1,
shrinkage=(0.4, 0.2)
)
# Filter upcoming events until 4 weeks in the future
parser.clear_events()
upcoming_events = parser.get_events(now, now.shift(weeks=4),
self.timezone)
self._upcoming_events = upcoming_events
# delete events which won't be able to fit (more events than lines)
upcoming_events[:max_event_lines]
# Check if any events were found in the given timerange
if upcoming_events:
# Find out how much space (width) the date format requires
lang = self.language
date_width = int(max([self.font.getsize(
events['begin'].format(self.date_format, locale=lang))[0]
for events in upcoming_events]) * 1.1)
time_width = int(max([self.font.getsize(
events['begin'].format(self.time_format, locale=lang))[0]
for events in upcoming_events]) * 1.1)
line_height = self.font.getsize('hg')[1] + line_spacing
event_width_s = im_width - date_width - time_width
event_width_l = im_width - date_width
# Display upcoming events below calendar
tomorrow = now.shift(days=1).floor('day')
in_two_days = now.shift(days=2).floor('day')
cursor = 0
for event in upcoming_events:
if cursor < len(event_lines):
name = event['title']
date = event['begin'].format(self.date_format, locale=lang)
time = event['begin'].format(self.time_format, locale=lang)
# logger.debug(f"name:{name} date:{date} time:{time}")
if now < event['end']:
write(im_colour, event_lines[cursor], (date_width, line_height),
date, font=self.font, alignment='left')
# Check if event is all day
if parser.all_day(event) == True:
write(im_black, (date_width, event_lines[cursor][1]),
(event_width_l, line_height), name, font=self.font,
alignment='left')
else:
write(im_black, (date_width, event_lines[cursor][1]),
(time_width, line_height), time, font=self.font,
alignment='left')
write(im_black, (date_width + time_width, event_lines[cursor][1]),
(event_width_s, line_height), name, font=self.font,
alignment='left')
cursor += 1
else:
symbol = '- '
while self.font.getsize(symbol)[0] < im_width * 0.9:
symbol += ' -'
write(im_black, event_lines[0],
(im_width, self.font.getsize(symbol)[1]), symbol,
font=self.font)
# return the images ready for the display
return im_black, im_colour
# return the images ready for the display
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone mode') print(f'running {filename} in standalone mode')

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Feeds module for InkyCal Project Feeds module for InkyCal Project
@@ -11,147 +10,149 @@ from inkycal.modules.template import inkycal_module
from inkycal.custom import * from inkycal.custom import *
from random import shuffle from random import shuffle
try: try:
import feedparser import feedparser
except ImportError: except ImportError:
print('feedparser is not installed! Please install with:') print('feedparser is not installed! Please install with:')
print('pip3 install feedparser') print('pip3 install feedparser')
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Feeds(inkycal_module): class Feeds(inkycal_module):
"""RSS class """RSS class
parses rss/atom feeds from given urls parses rss/atom feeds from given urls
""" """
name = "RSS / Atom - Display feeds from given RSS/ATOM feeds" name = "RSS / Atom - Display feeds from given RSS/ATOM feeds"
requires = { requires = {
"feed_urls" : { "feed_urls": {
"label":"Please enter ATOM or RSS feed URL/s, separated by a comma", "label": "Please enter ATOM or RSS feed URL/s, separated by a comma",
}, },
} }
optional = { optional = {
"shuffle_feeds": { "shuffle_feeds": {
"label": "Should the parsed RSS feeds be shuffled? (default=True)", "label": "Should the parsed RSS feeds be shuffled? (default=True)",
"options": [True, False], "options": [True, False],
"default": True "default": True
}, },
} }
def __init__(self, config): def __init__(self, config):
"""Initialize inkycal_feeds module""" """Initialize inkycal_feeds module"""
super().__init__(config) super().__init__(config)
config = config['config'] config = config['config']
# Check if all required parameters are present # Check if all required parameters are present
for param in self.requires: for param in self.requires:
if not param in config: if not param in config:
raise Exception(f'config is missing {param}') raise Exception(f'config is missing {param}')
# required parameters # required parameters
if config["feed_urls"] and isinstance(config['feed_urls'], str): if config["feed_urls"] and isinstance(config['feed_urls'], str):
self.feed_urls = config["feed_urls"].split(",") self.feed_urls = config["feed_urls"].split(",")
else: else:
self.feed_urls = config["feed_urls"] self.feed_urls = config["feed_urls"]
# optional parameters # optional parameters
self.shuffle_feeds = config["shuffle_feeds"] self.shuffle_feeds = config["shuffle_feeds"]
# give an OK message # give an OK message
print(f'{filename} loaded') print(f'{filename} loaded')
def _validate(self): def _validate(self):
"""Validate module-specific parameters""" """Validate module-specific parameters"""
if not isinstance(self.shuffle_feeds, bool): if not isinstance(self.shuffle_feeds, bool):
print('shuffle_feeds has to be a boolean: True/False') print('shuffle_feeds has to be a boolean: True/False')
def generate_image(self):
"""Generate image for this module"""
def generate_image(self): # Define new image size with respect to padding
"""Generate image for this module""" im_width = int(self.width - (2 * self.padding_left))
im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
logger.info(f'Image size: {im_size}')
# Define new image size with respect to padding # Create an image for black pixels and one for coloured pixels
im_width = int(self.width - (2 * self.padding_left)) im_black = Image.new('RGB', size=im_size, color='white')
im_height = int(self.height - (2 * self.padding_top)) im_colour = Image.new('RGB', size=im_size, color='white')
im_size = im_width, im_height
logger.info(f'Image size: {im_size}')
# Create an image for black pixels and one for coloured pixels # Check if internet is available
im_black = Image.new('RGB', size = im_size, color = 'white') if internet_available() == True:
im_colour = Image.new('RGB', size = im_size, color = 'white') logger.info('Connection test passed')
else:
raise Exception('Network could not be reached :/')
# Check if internet is available # Set some parameters for formatting feeds
if internet_available() == True: line_spacing = 1
logger.info('Connection test passed') line_height = self.font.getsize('hg')[1] + line_spacing
else: line_width = im_width
raise Exception('Network could not be reached :/') max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
# Set some parameters for formatting feeds # Calculate padding from top so the lines look centralised
line_spacing = 1 spacing_top = int(im_height % line_height / 2)
line_height = self.font.getsize('hg')[1] + line_spacing
line_width = im_width
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
# Calculate padding from top so the lines look centralised # Calculate line_positions
spacing_top = int( im_height % line_height / 2 ) line_positions = [
(0, spacing_top + _ * line_height) for _ in range(max_lines)]
# Calculate line_positions # Create list containing all feeds from all urls
line_positions = [ parsed_feeds = []
(0, spacing_top + _ * line_height ) for _ in range(max_lines)] for feeds in self.feed_urls:
text = feedparser.parse(feeds)
for posts in text.entries:
summary = posts.summary
parsed_feeds.append(
f"{posts.title}: {re.sub('<[^<]+?>', '', posts.summary)}")
# Create list containing all feeds from all urls self._parsed_feeds = parsed_feeds
parsed_feeds = []
for feeds in self.feed_urls:
text = feedparser.parse(feeds)
for posts in text.entries:
summary = posts.summary
parsed_feeds.append(
f"{posts.title}: {re.sub('<[^<]+?>', '', posts.summary)}")
self._parsed_feeds = parsed_feeds # Shuffle the list to prevent showing the same content
if self.shuffle_feeds == True:
shuffle(parsed_feeds)
# Shuffle the list to prevent showing the same content # Trim down the list to the max number of lines
if self.shuffle_feeds == True: del parsed_feeds[max_lines:]
shuffle(parsed_feeds)
# Trim down the list to the max number of lines # Wrap long text from feeds (line-breaking)
del parsed_feeds[max_lines:] flatten = lambda z: [x for y in z for x in y]
filtered_feeds, counter = [], 0
# Wrap long text from feeds (line-breaking) for posts in parsed_feeds:
flatten = lambda z: [x for y in z for x in y] wrapped = text_wrap(posts, font=self.font, max_width=line_width)
filtered_feeds, counter = [], 0 counter += len(wrapped)
if counter < max_lines:
filtered_feeds.append(wrapped)
filtered_feeds = flatten(filtered_feeds)
self._filtered_feeds = filtered_feeds
for posts in parsed_feeds: logger.debug(f'filtered feeds -> {filtered_feeds}')
wrapped = text_wrap(posts, font = self.font, max_width = line_width)
counter += len(wrapped)
if counter < max_lines:
filtered_feeds.append(wrapped)
filtered_feeds = flatten(filtered_feeds)
self._filtered_feeds = filtered_feeds
logger.debug(f'filtered feeds -> {filtered_feeds}') # Check if feeds could be parsed and can be displayed
if len(filtered_feeds) == 0 and len(parsed_feeds) > 0:
print('Feeds could be parsed, but the text is too long to be displayed:/')
elif len(filtered_feeds) == 0 and len(parsed_feeds) == 0:
print('No feeds could be parsed :/')
else:
# Write feeds on image
for _ in range(len(filtered_feeds)):
write(im_black, line_positions[_], (line_width, line_height),
filtered_feeds[_], font=self.font, alignment='left')
# Check if feeds could be parsed and can be displayed # return images
if len(filtered_feeds) == 0 and len(parsed_feeds) > 0: return im_black, im_colour
print('Feeds could be parsed, but the text is too long to be displayed:/')
elif len(filtered_feeds) == 0 and len(parsed_feeds) == 0:
print('No feeds could be parsed :/')
else:
# Write feeds on image
for _ in range(len(filtered_feeds)):
write(im_black, line_positions[_], (line_width, line_height),
filtered_feeds[_], font = self.font, alignment= 'left')
# return images
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone/debug mode') print(f'running {filename} in standalone/debug mode')

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Image module for Inkycal Project Image module for Inkycal Project
@@ -14,95 +13,96 @@ from inkycal.modules.inky_image import Inkyimage as Images
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Inkyimage(inkycal_module): class Inkyimage(inkycal_module):
"""Displays an image from URL or local path """Displays an image from URL or local path
""" """
name = "Inkycal Image - show an image from a URL or local path" name = "Inkycal Image - show an image from a URL or local path"
requires = { requires = {
"path":{
"label":"Path to a local folder, e.g. /home/pi/Desktop/images. "
"Only PNG and JPG/JPEG images are used for the slideshow."
},
"palette": { "path": {
"label":"Which palette should be used for converting images?", "label": "Path to a local folder, e.g. /home/pi/Desktop/images. "
"options": ["bw", "bwr", "bwy"] "Only PNG and JPG/JPEG images are used for the slideshow."
}
}
optional = {
"autoflip":{
"label":"Should the image be flipped automatically?",
"options": [True, False]
}, },
"orientation":{ "palette": {
"label": "Please select the desired orientation", "label": "Which palette should be used for converting images?",
"options": ["vertical", "horizontal"] "options": ["bw", "bwr", "bwy"]
} }
} }
def __init__(self, config): optional = {
"""Initialize module"""
super().__init__(config) "autoflip": {
"label": "Should the image be flipped automatically?",
"options": [True, False]
},
config = config['config'] "orientation": {
"label": "Please select the desired orientation",
"options": ["vertical", "horizontal"]
}
}
# required parameters def __init__(self, config):
for param in self.requires: """Initialize module"""
if not param in config:
raise Exception(f'config is missing {param}')
# optional parameters super().__init__(config)
self.path = config['path']
self.palette = config['palette']
self.autoflip = config['autoflip']
self.orientation = config['orientation']
# give an OK message config = config['config']
print(f'{filename} loaded')
# required parameters
for param in self.requires:
if not param in config:
raise Exception(f'config is missing {param}')
def generate_image(self): # optional parameters
"""Generate image for this module""" self.path = config['path']
self.palette = config['palette']
self.autoflip = config['autoflip']
self.orientation = config['orientation']
# Define new image size with respect to padding # give an OK message
im_width = int(self.width - (2 * self.padding_left)) print(f'{filename} loaded')
im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
logger.info(f'Image size: {im_size}') def generate_image(self):
"""Generate image for this module"""
# initialize custom image class # Define new image size with respect to padding
im = Images() im_width = int(self.width - (2 * self.padding_left))
im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
# use the image at the first index logger.info(f'Image size: {im_size}')
im.load(self.path)
# Remove background if present # initialize custom image class
im.remove_alpha() im = Images()
# if autoflip was enabled, flip the image # use the image at the first index
if self.autoflip == True: im.load(self.path)
im.autoflip(self.orientation)
# resize the image so it can fit on the epaper # Remove background if present
im.resize( width=im_width, height=im_height ) im.remove_alpha()
# convert images according to specified palette # if autoflip was enabled, flip the image
im_black, im_colour = im.to_palette(self.palette) if self.autoflip == True:
im.autoflip(self.orientation)
# with the images now send, clear the current image # resize the image so it can fit on the epaper
im.clear() im.resize(width=im_width, height=im_height)
# convert images according to specified palette
im_black, im_colour = im.to_palette(self.palette)
# with the images now send, clear the current image
im.clear()
# return images
return im_black, im_colour
# return images
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone/debug mode') print(f'running {filename} in standalone/debug mode')

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
iCanHazDadJoke module for InkyCal Project iCanHazDadJoke module for InkyCal Project
@@ -11,94 +10,95 @@ from inkycal.modules.template import inkycal_module
from inkycal.custom import * from inkycal.custom import *
import requests import requests
# Show less logging for request module # Show less logging for request module
logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING)
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Jokes(inkycal_module): class Jokes(inkycal_module):
"""Icanhazdad-api class """Icanhazdad-api class
parses rss/atom feeds from given urls parses rss/atom feeds from given urls
""" """
name = "iCanHazDad API - grab a random joke from icanhazdad api" name = "iCanHazDad API - grab a random joke from icanhazdad api"
def __init__(self, config):
"""Initialize inkycal_feeds module"""
def __init__(self, config): super().__init__(config)
"""Initialize inkycal_feeds module"""
super().__init__(config) config = config['config']
config = config['config'] # give an OK message
print(f'{filename} loaded')
# give an OK message def generate_image(self):
print(f'{filename} loaded') """Generate image for this module"""
def generate_image(self): # Define new image size with respect to padding
"""Generate image for this module""" im_width = int(self.width - (2 * self.padding_left))
im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
logger.info(f'image size: {im_width} x {im_height} px')
# Define new image size with respect to padding # Create an image for black pixels and one for coloured pixels
im_width = int(self.width - (2 * self.padding_left)) im_black = Image.new('RGB', size=im_size, color='white')
im_height = int(self.height - (2 * self.padding_top)) im_colour = Image.new('RGB', size=im_size, color='white')
im_size = im_width, im_height
logger.info(f'image size: {im_width} x {im_height} px')
# Create an image for black pixels and one for coloured pixels # Check if internet is available
im_black = Image.new('RGB', size = im_size, color = 'white') if internet_available() == True:
im_colour = Image.new('RGB', size = im_size, color = 'white') logger.info('Connection test passed')
else:
raise Exception('Network could not be reached :/')
# Check if internet is available # Set some parameters for formatting feeds
if internet_available() == True: line_spacing = 1
logger.info('Connection test passed') line_height = self.font.getsize('hg')[1] + line_spacing
else: line_width = im_width
raise Exception('Network could not be reached :/') max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
# Set some parameters for formatting feeds logger.debug(f"max_lines: {max_lines}")
line_spacing = 1
line_height = self.font.getsize('hg')[1] + line_spacing
line_width = im_width
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
logger.debug(f"max_lines: {max_lines}") # Calculate padding from top so the lines look centralised
spacing_top = int(im_height % line_height / 2)
# Calculate padding from top so the lines look centralised # Calculate line_positions
spacing_top = int( im_height % line_height / 2 ) line_positions = [
(0, spacing_top + _ * line_height) for _ in range(max_lines)]
# Calculate line_positions logger.debug(f'line positions: {line_positions}')
line_positions = [
(0, spacing_top + _ * line_height ) for _ in range(max_lines)]
logger.debug(f'line positions: {line_positions}') # Get the actual joke
url = "https://icanhazdadjoke.com"
header = {"accept": "text/plain"}
response = requests.get(url, headers=header)
response.encoding = 'utf-8' # Change encoding to UTF-8
joke = response.text.rstrip() # use to remove newlines
logger.debug(f"joke: {joke}")
# Get the actual joke # wrap text in case joke is too large
url = "https://icanhazdadjoke.com" wrapped = text_wrap(joke, font=self.font, max_width=line_width)
header = {"accept": "text/plain"} logger.debug(f"wrapped: {wrapped}")
response = requests.get(url, headers=header)
response.encoding = 'utf-8' # Change encoding to UTF-8
joke = response.text.rstrip() # use to remove newlines
logger.debug(f"joke: {joke}")
# wrap text in case joke is too large # Check if joke can actually fit on the provided space
wrapped = text_wrap(joke, font = self.font, max_width = line_width) if len(wrapped) > max_lines:
logger.debug(f"wrapped: {wrapped}") logger.error("Ohoh, Joke is too large for given space, please consider "
"increasing the size for this module")
# Check if joke can actually fit on the provided space # Write the joke on the image
if len(wrapped) > max_lines: for _ in range(len(wrapped)):
logger.error("Ohoh, Joke is too large for given space, please consider " if _ + 1 > max_lines:
"increasing the size for this module") logger.error('Ran out of lines for this joke :/')
break
write(im_black, line_positions[_], (line_width, line_height),
wrapped[_], font=self.font, alignment='left')
# Write the joke on the image # Return images for black and colour channels
for _ in range(len(wrapped)): return im_black, im_colour
if _+1 > max_lines:
logger.error('Ran out of lines for this joke :/')
break
write(im_black, line_positions[_], (line_width, line_height),
wrapped[_], font = self.font, alignment= 'left')
# Return images for black and colour channels
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone/debug mode') print(f'running {filename} in standalone/debug mode')

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Inkycal-server module for Inkycal Project Inkycal-server module for Inkycal Project
@@ -17,112 +16,113 @@ from inkycal.modules.inky_image import Inkyimage as Images
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Inkyserver(inkycal_module): class Inkyserver(inkycal_module):
"""Displays an image from URL or local path """Displays an image from URL or local path
""" """
name = "Inykcal Server - fetches an image from Inkycal-server - (https://inkycal.robertsirre.nl/)" name = "Inykcal Server - fetches an image from Inkycal-server - (https://inkycal.robertsirre.nl/)"
requires = { requires = {
"path":{ "path": {
"label": "Which URL should be used to get the image?" "label": "Which URL should be used to get the image?"
},
"palette": {
"label":"Which palette should be used to convert the images?",
"options": ['bw', 'bwr', 'bwy']
}
}
optional = {
"path_body":{
"label":"Send this data to the server via POST. Use a comma to "
"separate multiple items",
}, },
"dither":{
"label": "Dither images before sending to E-Paper? Default is False.", "palette": {
"options": [False, True], "label": "Which palette should be used to convert the images?",
} "options": ['bw', 'bwr', 'bwy']
}
} }
def __init__(self, config): optional = {
"""Initialize module"""
super().__init__(config) "path_body": {
"label": "Send this data to the server via POST. Use a comma to "
"separate multiple items",
},
"dither": {
"label": "Dither images before sending to E-Paper? Default is False.",
"options": [False, True],
}
config = config['config'] }
# required parameters def __init__(self, config):
for param in self.requires: """Initialize module"""
if not param in config:
raise Exception(f'config is missing {param}')
# optional parameters super().__init__(config)
self.path = config['path']
self.palette = config['palette']
self.dither = config['dither']
# convert path_body to list, if not already config = config['config']
if config['path_body'] and isinstance(config['path_body'], str):
self.path_body = config['path_body'].split(',')
else:
self.path_body = config['path_body']
# give an OK message # required parameters
print(f'{filename} loaded') for param in self.requires:
if not param in config:
raise Exception(f'config is missing {param}')
# optional parameters
self.path = config['path']
self.palette = config['palette']
self.dither = config['dither']
def generate_image(self): # convert path_body to list, if not already
"""Generate image for this module""" if config['path_body'] and isinstance(config['path_body'], str):
self.path_body = config['path_body'].split(',')
else:
self.path_body = config['path_body']
# Define new image size with respect to padding # give an OK message
im_width = int(self.width - (2 * self.padding_left)) print(f'{filename} loaded')
im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
logger.info(f'Image size: {im_size}') def generate_image(self):
"""Generate image for this module"""
# replace width and height of url # Define new image size with respect to padding
print(self.path) im_width = int(self.width - (2 * self.padding_left))
self.path = self.path.format(width=im_width, height=im_height) im_height = int(self.height - (2 * self.padding_top))
print(f"modified path: {self.path}") im_size = im_width, im_height
# initialize custom image class logger.info(f'Image size: {im_size}')
im = Images()
# when no path_body is provided, use plain GET # replace width and height of url
if not self.path_body: print(self.path)
self.path = self.path.format(width=im_width, height=im_height)
print(f"modified path: {self.path}")
# use the image at the first index # initialize custom image class
im.load(self.path) im = Images()
# else use POST request # when no path_body is provided, use plain GET
else: if not self.path_body:
# Get the response image
response = Image.open(requests.post(
self.path, json=self.path_body, stream=True).raw)
# initialize custom image class with response # use the image at the first index
im = Images(response) im.load(self.path)
# resize the image to respect padding # else use POST request
im.resize( width=im_width, height=im_height ) else:
# Get the response image
response = Image.open(requests.post(
self.path, json=self.path_body, stream=True).raw)
# convert image to given palette # initialize custom image class with response
im_black, im_colour = im.to_palette(self.palette, dither=self.dither) im = Images(response)
# with the images now send, clear the current image # resize the image to respect padding
im.clear() im.resize(width=im_width, height=im_height)
# convert image to given palette
im_black, im_colour = im.to_palette(self.palette, dither=self.dither)
# with the images now send, clear the current image
im.clear()
# return images
return im_black, im_colour
# return images
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone/debug mode') print(f'running {filename} in standalone/debug mode')
## 'https://inkycal.robertsirre.nl/panel/calendar/{model}?width={width}&height={height}' ## 'https://inkycal.robertsirre.nl/panel/calendar/{model}?width={width}&height={height}'
##path = path.replace('{model}', model).replace('{width}',str(display_width)).replace('{height}',str(display_height)) ##path = path.replace('{model}', model).replace('{width}',str(display_width)).replace('{height}',str(display_height))
@@ -131,4 +131,3 @@ if __name__ == '__main__':
##inkycal_image_path_body = [ ##inkycal_image_path_body = [
## 'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics', ## 'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics',
## 'https ## 'https

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Image module for Inkycal Project Image module for Inkycal Project
@@ -16,119 +15,121 @@ from inkycal.modules.inky_image import Inkyimage as Images
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Slideshow(inkycal_module): class Slideshow(inkycal_module):
"""Cycles through images in a local image folder """Cycles through images in a local image folder
""" """
name = "Slideshow - cycle through images from a local folder" name = "Slideshow - cycle through images from a local folder"
requires = { requires = {
"path":{
"label":"Path to a local folder, e.g. /home/pi/Desktop/images. "
"Only PNG and JPG/JPEG images are used for the slideshow."
},
"palette": { "path": {
"label":"Which palette should be used for converting images?", "label": "Path to a local folder, e.g. /home/pi/Desktop/images. "
"options": ["bw", "bwr", "bwy"] "Only PNG and JPG/JPEG images are used for the slideshow."
}
}
optional = {
"autoflip":{
"label":"Should the image be flipped automatically? Default is False",
"options": [False, True]
}, },
"orientation":{ "palette": {
"label": "Please select the desired orientation", "label": "Which palette should be used for converting images?",
"options": ["vertical", "horizontal"] "options": ["bw", "bwr", "bwy"]
} }
} }
def __init__(self, config): optional = {
"""Initialize module"""
super().__init__(config) "autoflip": {
"label": "Should the image be flipped automatically? Default is False",
"options": [False, True]
},
config = config['config'] "orientation": {
"label": "Please select the desired orientation",
"options": ["vertical", "horizontal"]
}
}
# required parameters def __init__(self, config):
for param in self.requires: """Initialize module"""
if not param in config:
raise Exception(f'config is missing {param}')
# optional parameters super().__init__(config)
self.path = config['path']
self.palette = config['palette']
self.autoflip = config['autoflip']
self.orientation = config['orientation']
# Get the full path of all png/jpg/jpeg images in the given folder config = config['config']
all_files = glob.glob(f'{self.path}/*')
self.images = [i for i in all_files
if i.split('.')[-1].lower() in ('jpg', 'jpeg', 'png')]
if not self.images: # required parameters
logger.error('No images found in the given folder, please ' for param in self.requires:
'double check your path!') if not param in config:
raise Exception('No images found in the given folder path :/') raise Exception(f'config is missing {param}')
# set a 'first run' signal # optional parameters
self._first_run = True self.path = config['path']
self.palette = config['palette']
self.autoflip = config['autoflip']
self.orientation = config['orientation']
# give an OK message # Get the full path of all png/jpg/jpeg images in the given folder
print(f'{filename} loaded') all_files = glob.glob(f'{self.path}/*')
self.images = [i for i in all_files
if i.split('.')[-1].lower() in ('jpg', 'jpeg', 'png')]
def generate_image(self): if not self.images:
"""Generate image for this module""" logger.error('No images found in the given folder, please '
'double check your path!')
raise Exception('No images found in the given folder path :/')
# Define new image size with respect to padding # set a 'first run' signal
im_width = int(self.width - (2 * self.padding_left)) self._first_run = True
im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
logger.info(f'Image size: {im_size}') # give an OK message
print(f'{filename} loaded')
# rotates list items by 1 index def generate_image(self):
def rotate(somelist): """Generate image for this module"""
return somelist[1:] + somelist[:1]
# Switch to the next image if this is not the first run # Define new image size with respect to padding
if self._first_run == True: im_width = int(self.width - (2 * self.padding_left))
self._first_run = False im_height = int(self.height - (2 * self.padding_top))
else: im_size = im_width, im_height
self.images = rotate(self.images)
# initialize custom image class logger.info(f'Image size: {im_size}')
im = Images()
# temporary print method, prints current filename # rotates list items by 1 index
print(f'slideshow - current image name: {self.images[0].split("/")[-1]}') def rotate(somelist):
return somelist[1:] + somelist[:1]
# use the image at the first index # Switch to the next image if this is not the first run
im.load(self.images[0]) if self._first_run == True:
self._first_run = False
else:
self.images = rotate(self.images)
# Remove background if present # initialize custom image class
im.remove_alpha() im = Images()
# if autoflip was enabled, flip the image # temporary print method, prints current filename
if self.autoflip == True: print(f'slideshow - current image name: {self.images[0].split("/")[-1]}')
im.autoflip(self.orientation)
# resize the image so it can fit on the epaper # use the image at the first index
im.resize( width=im_width, height=im_height ) im.load(self.images[0])
# convert images according to specified palette # Remove background if present
im_black, im_colour = im.to_palette(self.palette) im.remove_alpha()
# with the images now send, clear the current image # if autoflip was enabled, flip the image
im.clear() if self.autoflip == True:
im.autoflip(self.orientation)
# resize the image so it can fit on the epaper
im.resize(width=im_width, height=im_height)
# convert images according to specified palette
im_black, im_colour = im.to_palette(self.palette)
# with the images now send, clear the current image
im.clear()
# return images
return im_black, im_colour
# return images
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone/debug mode') print(f'running {filename} in standalone/debug mode')

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Stocks Module for Inkycal Project Stocks Module for Inkycal Project
@@ -20,249 +20,252 @@ from inkycal.custom import write, internet_available
from PIL import Image from PIL import Image
try: try:
import yfinance as yf import yfinance as yf
except ImportError: except ImportError:
print('yfinance is not installed! Please install with:') print('yfinance is not installed! Please install with:')
print('pip3 install yfinance') print('pip3 install yfinance')
try: try:
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.image as mpimg import matplotlib.image as mpimg
except ImportError: except ImportError:
print('matplotlib is not installed! Please install with:') print('matplotlib is not installed! Please install with:')
print('pip3 install matplotlib') print('pip3 install matplotlib')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Stocks(inkycal_module): class Stocks(inkycal_module):
name = "Stocks - Displays stock market infos from Yahoo finance"
name = "Stocks - Displays stock market infos from Yahoo finance" # required parameters
requires = {
# required parameters "tickers": {
requires = {
"tickers": { "label": "You can display any information by using "
"the respective symbols that are used by Yahoo! Finance. "
"label": "You can display any information by using " "Separate multiple symbols with a comma sign e.g. "
"the respective symbols that are used by Yahoo! Finance. " "TSLA, U, NVDA, EURUSD=X"
"Separate multiple symbols with a comma sign e.g. " }
"TSLA, U, NVDA, EURUSD=X"
}
} }
def __init__(self, config): def __init__(self, config):
super().__init__(config) super().__init__(config)
config = config['config'] config = config['config']
# If tickers is a string from web-ui, convert to a list, else use # If tickers is a string from web-ui, convert to a list, else use
# tickers as-is i.e. for tests # tickers as-is i.e. for tests
if config['tickers'] and isinstance(config['tickers'], str): if config['tickers'] and isinstance(config['tickers'], str):
self.tickers = config['tickers'].replace(" ", "").split(',') #returns list self.tickers = config['tickers'].replace(" ", "").split(',') # returns list
else: else:
self.tickers = config['tickers'] self.tickers = config['tickers']
# give an OK message # give an OK message
print(f'{__name__} loaded') print(f'{__name__} loaded')
def generate_image(self): def generate_image(self):
"""Generate image for this module""" """Generate image for this module"""
# Define new image size with respect to padding # Define new image size with respect to padding
im_width = int(self.width - (2 * self.padding_left)) im_width = int(self.width - (2 * self.padding_left))
im_height = int(self.height - (2 * self.padding_top)) im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height im_size = im_width, im_height
logger.info(f'image size: {im_width} x {im_height} px') logger.info(f'image size: {im_width} x {im_height} px')
# Create an image for black pixels and one for coloured pixels (required) # Create an image for black pixels and one for coloured pixels (required)
im_black = Image.new('RGB', size = im_size, color = 'white') im_black = Image.new('RGB', size=im_size, color='white')
im_colour = Image.new('RGB', size = im_size, color = 'white') im_colour = Image.new('RGB', size=im_size, color='white')
# Create tmp path # Create tmp path
tmpPath = '/tmp/inkycal_stocks/' tmpPath = '/tmp/inkycal_stocks/'
try: try:
os.mkdir(tmpPath) os.mkdir(tmpPath)
except OSError: except OSError:
print (f"Creation of tmp directory {tmpPath} failed") print(f"Creation of tmp directory {tmpPath} failed")
else: else:
print (f"Successfully created tmp directory {tmpPath} ") print(f"Successfully created tmp directory {tmpPath} ")
# Check if internet is available # Check if internet is available
if internet_available() == True: if internet_available() == True:
logger.info('Connection test passed') logger.info('Connection test passed')
else: else:
raise Exception('Network could not be reached :/') raise Exception('Network could not be reached :/')
# Set some parameters for formatting feeds # Set some parameters for formatting feeds
line_spacing = 1 line_spacing = 1
line_height = self.font.getsize('hg')[1] + line_spacing line_height = self.font.getsize('hg')[1] + line_spacing
line_width = im_width line_width = im_width
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing)) max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
logger.debug(f"max_lines: {max_lines}") logger.debug(f"max_lines: {max_lines}")
# Calculate padding from top so the lines look centralised # Calculate padding from top so the lines look centralised
spacing_top = int( im_height % line_height / 2 ) spacing_top = int(im_height % line_height / 2)
# Calculate line_positions # Calculate line_positions
line_positions = [ line_positions = [
(0, spacing_top + _ * line_height ) for _ in range(max_lines)] (0, spacing_top + _ * line_height) for _ in range(max_lines)]
logger.debug(f'line positions: {line_positions}') logger.debug(f'line positions: {line_positions}')
parsed_tickers = [] parsed_tickers = []
parsed_tickers_colour = [] parsed_tickers_colour = []
chartSpace = Image.new('RGBA', (im_width, im_height), "white") chartSpace = Image.new('RGBA', (im_width, im_height), "white")
chartSpace_colour = Image.new('RGBA', (im_width, im_height), "white") chartSpace_colour = Image.new('RGBA', (im_width, im_height), "white")
tickerCount = range(len(self.tickers)) tickerCount = range(len(self.tickers))
for _ in tickerCount: for _ in tickerCount:
ticker = self.tickers[_] ticker = self.tickers[_]
logger.info(f'preparing data for {ticker}...') logger.info(f'preparing data for {ticker}...')
yfTicker = yf.Ticker(ticker) yfTicker = yf.Ticker(ticker)
try: try:
stockInfo = yfTicker.info stockInfo = yfTicker.info
except Exception as exceptionMessage: except Exception as exceptionMessage:
logger.warning(f"Failed to get '{ticker}' ticker info: {exceptionMessage}") logger.warning(f"Failed to get '{ticker}' ticker info: {exceptionMessage}")
try: try:
stockName = stockInfo['shortName'] stockName = stockInfo['shortName']
except Exception: except Exception:
stockName = ticker stockName = ticker
logger.warning(f"Failed to get '{stockName}' ticker name! Using " logger.warning(f"Failed to get '{stockName}' ticker name! Using "
"the ticker symbol as name instead.") "the ticker symbol as name instead.")
try: try:
stockCurrency = stockInfo['currency'] stockCurrency = stockInfo['currency']
if stockCurrency == 'USD': if stockCurrency == 'USD':
stockCurrency = '$' stockCurrency = '$'
elif stockCurrency == 'EUR': elif stockCurrency == 'EUR':
stockCurrency = '' stockCurrency = ''
except Exception: except Exception:
stockCurrency = '' stockCurrency = ''
logger.warning(f"Failed to get ticker currency!") logger.warning(f"Failed to get ticker currency!")
try:
precision = stockInfo['priceHint']
except Exception:
precision = 2
logger.warning(f"Failed to get '{stockName}' ticker price hint! Using "
"default precision of 2 instead.")
stockHistory = yfTicker.history("30d") try:
stockHistoryLen = len(stockHistory) precision = stockInfo['priceHint']
logger.info(f'fetched {stockHistoryLen} datapoints ...') except Exception:
previousQuote = (stockHistory.tail(2)['Close'].iloc[0]) precision = 2
currentQuote = (stockHistory.tail(1)['Close'].iloc[0]) logger.warning(f"Failed to get '{stockName}' ticker price hint! Using "
currentHigh = (stockHistory.tail(1)['High'].iloc[0]) "default precision of 2 instead.")
currentLow = (stockHistory.tail(1)['Low'].iloc[0])
currentOpen = (stockHistory.tail(1)['Open'].iloc[0])
currentGain = currentQuote-previousQuote
currentGainPercentage = (1-currentQuote/previousQuote)*-100
firstQuote = stockHistory.tail(stockHistoryLen)['Close'].iloc[0]
logger.info(f'firstQuote {firstQuote} ...')
def floatStr(precision, number):
return "%0.*f" % (precision, number)
def percentageStr(number):
return '({:+.2f}%)'.format(number)
def gainStr(precision, number):
return "%+.*f" % (precision, number)
stockNameLine = '{} ({})'.format(stockName, stockCurrency) stockHistory = yfTicker.history("30d")
stockCurrentValueLine = '{} {} {}'.format( stockHistoryLen = len(stockHistory)
floatStr(precision, currentQuote), gainStr(precision, currentGain), percentageStr(currentGainPercentage)) logger.info(f'fetched {stockHistoryLen} datapoints ...')
stockDayValueLine = '1d OHL: {}/{}/{}'.format( previousQuote = (stockHistory.tail(2)['Close'].iloc[0])
floatStr(precision, currentOpen), floatStr(precision, currentHigh), floatStr(precision, currentLow)) currentQuote = (stockHistory.tail(1)['Close'].iloc[0])
maxQuote = max(stockHistory.High) currentHigh = (stockHistory.tail(1)['High'].iloc[0])
minQuote = min(stockHistory.Low) currentLow = (stockHistory.tail(1)['Low'].iloc[0])
logger.info(f'high {maxQuote} low {minQuote} ...') currentOpen = (stockHistory.tail(1)['Open'].iloc[0])
stockMonthValueLine = '{}d OHL: {}/{}/{}'.format( currentGain = currentQuote - previousQuote
stockHistoryLen,floatStr(precision, firstQuote),floatStr(precision, maxQuote),floatStr(precision, minQuote)) currentGainPercentage = (1 - currentQuote / previousQuote) * -100
firstQuote = stockHistory.tail(stockHistoryLen)['Close'].iloc[0]
logger.info(f'firstQuote {firstQuote} ...')
logger.info(stockNameLine) def floatStr(precision, number):
logger.info(stockCurrentValueLine) return "%0.*f" % (precision, number)
logger.info(stockDayValueLine)
logger.info(stockMonthValueLine)
parsed_tickers.append(stockNameLine)
parsed_tickers.append(stockCurrentValueLine)
parsed_tickers.append(stockDayValueLine)
parsed_tickers.append(stockMonthValueLine)
parsed_tickers_colour.append("") def percentageStr(number):
if currentGain < 0: return '({:+.2f}%)'.format(number)
parsed_tickers_colour.append(stockCurrentValueLine)
else:
parsed_tickers_colour.append("")
if currentOpen > currentQuote:
parsed_tickers_colour.append(stockDayValueLine)
else:
parsed_tickers_colour.append("")
if firstQuote > currentQuote:
parsed_tickers_colour.append(stockMonthValueLine)
else:
parsed_tickers_colour.append("")
if (_ < len(tickerCount)): def gainStr(precision, number):
parsed_tickers.append("") return "%+.*f" % (precision, number)
parsed_tickers_colour.append("")
logger.info(f'creating chart data...') stockNameLine = '{} ({})'.format(stockName, stockCurrency)
chartData = stockHistory.reset_index() stockCurrentValueLine = '{} {} {}'.format(
chartCloseData = chartData.loc[:,'Close'] floatStr(precision, currentQuote), gainStr(precision, currentGain),
chartTimeData = chartData.loc[:,'Date'] percentageStr(currentGainPercentage))
stockDayValueLine = '1d OHL: {}/{}/{}'.format(
floatStr(precision, currentOpen), floatStr(precision, currentHigh), floatStr(precision, currentLow))
maxQuote = max(stockHistory.High)
minQuote = min(stockHistory.Low)
logger.info(f'high {maxQuote} low {minQuote} ...')
stockMonthValueLine = '{}d OHL: {}/{}/{}'.format(
stockHistoryLen, floatStr(precision, firstQuote), floatStr(precision, maxQuote),
floatStr(precision, minQuote))
logger.info(f'creating chart plot...') logger.info(stockNameLine)
fig, ax = plt.subplots() # Create a figure containing a single axes. logger.info(stockCurrentValueLine)
ax.plot(chartTimeData, chartCloseData, linewidth=8) # Plot some data on the axes. logger.info(stockDayValueLine)
ax.set_xticklabels([]) logger.info(stockMonthValueLine)
ax.set_yticklabels([]) parsed_tickers.append(stockNameLine)
chartPath = tmpPath+ticker+'.png' parsed_tickers.append(stockCurrentValueLine)
logger.info(f'saving chart image to {chartPath}...') parsed_tickers.append(stockDayValueLine)
plt.savefig(chartPath) parsed_tickers.append(stockMonthValueLine)
logger.info(f'chartSpace is...{im_width} {im_height}') parsed_tickers_colour.append("")
logger.info(f'open chart ...{chartPath}') if currentGain < 0:
chartImage = Image.open(chartPath) parsed_tickers_colour.append(stockCurrentValueLine)
chartImage.thumbnail((im_width/4,line_height*4), Image.BICUBIC) else:
parsed_tickers_colour.append("")
if currentOpen > currentQuote:
parsed_tickers_colour.append(stockDayValueLine)
else:
parsed_tickers_colour.append("")
if firstQuote > currentQuote:
parsed_tickers_colour.append(stockMonthValueLine)
else:
parsed_tickers_colour.append("")
chartPasteX = im_width-(chartImage.width) if (_ < len(tickerCount)):
chartPasteY = line_height*5*_ parsed_tickers.append("")
logger.info(f'pasting chart image with index {_} to...{chartPasteX} {chartPasteY}') parsed_tickers_colour.append("")
if firstQuote > currentQuote: logger.info(f'creating chart data...')
chartSpace_colour.paste(chartImage, (chartPasteX, chartPasteY)) chartData = stockHistory.reset_index()
else: chartCloseData = chartData.loc[:, 'Close']
chartSpace.paste(chartImage, (chartPasteX, chartPasteY)) chartTimeData = chartData.loc[:, 'Date']
im_black.paste(chartSpace) logger.info(f'creating chart plot...')
im_colour.paste(chartSpace_colour) fig, ax = plt.subplots() # Create a figure containing a single axes.
ax.plot(chartTimeData, chartCloseData, linewidth=8) # Plot some data on the axes.
ax.set_xticklabels([])
ax.set_yticklabels([])
chartPath = tmpPath + ticker + '.png'
logger.info(f'saving chart image to {chartPath}...')
plt.savefig(chartPath)
# Write/Draw something on the black image logger.info(f'chartSpace is...{im_width} {im_height}')
for _ in range(len(parsed_tickers)): logger.info(f'open chart ...{chartPath}')
if _+1 > max_lines: chartImage = Image.open(chartPath)
logger.error('Ran out of lines for parsed_ticker_colour') chartImage.thumbnail((im_width / 4, line_height * 4), Image.BICUBIC)
break
write(im_black, line_positions[_], (line_width, line_height),
parsed_tickers[_], font = self.font, alignment= 'left')
# Write/Draw something on the colour image chartPasteX = im_width - (chartImage.width)
for _ in range(len(parsed_tickers_colour)): chartPasteY = line_height * 5 * _
if _+1 > max_lines: logger.info(f'pasting chart image with index {_} to...{chartPasteX} {chartPasteY}')
logger.error('Ran out of lines for parsed_tickers_colour')
break if firstQuote > currentQuote:
write(im_colour, line_positions[_], (line_width, line_height), chartSpace_colour.paste(chartImage, (chartPasteX, chartPasteY))
parsed_tickers_colour[_], font = self.font, alignment= 'left') else:
chartSpace.paste(chartImage, (chartPasteX, chartPasteY))
im_black.paste(chartSpace)
im_colour.paste(chartSpace_colour)
# Write/Draw something on the black image
for _ in range(len(parsed_tickers)):
if _ + 1 > max_lines:
logger.error('Ran out of lines for parsed_ticker_colour')
break
write(im_black, line_positions[_], (line_width, line_height),
parsed_tickers[_], font=self.font, alignment='left')
# Write/Draw something on the colour image
for _ in range(len(parsed_tickers_colour)):
if _ + 1 > max_lines:
logger.error('Ran out of lines for parsed_tickers_colour')
break
write(im_colour, line_positions[_], (line_width, line_height),
parsed_tickers_colour[_], font=self.font, alignment='left')
# Save image of black and colour channel in image-folder
return im_black, im_colour
# Save image of black and colour channel in image-folder
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print('running module in standalone/debug mode') print('running module in standalone/debug mode')

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
todoist module for Inky-Calendar Project todoist module for Inky-Calendar Project
@@ -10,195 +9,196 @@ from inkycal.modules.template import inkycal_module
from inkycal.custom import * from inkycal.custom import *
try: try:
import todoist import todoist
except ImportError: except ImportError:
print('todoist is not installed! Please install with:') print('todoist is not installed! Please install with:')
print('pip3 install todoist-python') print('pip3 install todoist-python')
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Todoist(inkycal_module): class Todoist(inkycal_module):
"""Todoist api class """Todoist api class
parses todo's from api-key parses todo's from api-key
""" """
name = "Todoist API - show your todos from todoist" name = "Todoist API - show your todos from todoist"
requires = { requires = {
'api_key': { 'api_key': {
"label":"Please enter your Todoist API-key", "label": "Please enter your Todoist API-key",
}, },
}
optional = {
'project_filter': {
"label":"Show Todos only from following project (separated by a comma). Leave empty to show "+
"todos from all projects",
} }
}
def __init__(self, config): optional = {
"""Initialize inkycal_rss module""" 'project_filter': {
"label": "Show Todos only from following project (separated by a comma). Leave empty to show " +
"todos from all projects",
}
}
super().__init__(config) def __init__(self, config):
"""Initialize inkycal_rss module"""
config = config['config'] super().__init__(config)
# Check if all required parameters are present config = config['config']
for param in self.requires:
if not param in config:
raise Exception(f'config is missing {param}')
# module specific parameters # Check if all required parameters are present
self.api_key = config['api_key'] for param in self.requires:
if not param in config:
raise Exception(f'config is missing {param}')
# if project filter is set, initialize it # module specific parameters
if config['project_filter'] and isinstance(config['project_filter'], str): self.api_key = config['api_key']
self.project_filter = config['project_filter'].split(',')
else:
self.project_filter = config['project_filter']
self._api = todoist.TodoistAPI(config['api_key']) # if project filter is set, initialize it
self._api.sync() if config['project_filter'] and isinstance(config['project_filter'], str):
self.project_filter = config['project_filter'].split(',')
# give an OK message
print(f'{filename} loaded')
def _validate(self):
"""Validate module-specific parameters"""
if not isinstance(self.api_key, str):
print('api_key has to be a string: "Yourtopsecretkey123" ')
def generate_image(self):
"""Generate image for this module"""
# Define new image size with respect to padding
im_width = int(self.width - (2 * self.padding_left))
im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
logger.info(f'Image size: {im_size}')
# Create an image for black pixels and one for coloured pixels
im_black = Image.new('RGB', size = im_size, color = 'white')
im_colour = Image.new('RGB', size = im_size, color = 'white')
# Check if internet is available
if internet_available() == True:
logger.info('Connection test passed')
self._api.sync()
else:
raise Exception('Network could not be reached :/')
# Set some parameters for formatting todos
line_spacing = 1
line_height = self.font.getsize('hg')[1] + line_spacing
line_width = im_width
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
# Calculate padding from top so the lines look centralised
spacing_top = int( im_height % line_height / 2 )
# Calculate line_positions
line_positions = [
(0, spacing_top + _ * line_height ) for _ in range(max_lines)]
# Get all projects by name and id
all_projects = {project['id']: project['name']
for project in self._api.projects.all()}
logger.debug(f"all_projects: {all_projects}")
# Filter entries in all_projects if filter was given
if self.project_filter:
for project_id in list(all_projects):
if all_projects[project_id] not in self.project_filter:
del all_projects[project_id]
logger.debug(f"all_project: {all_projects}")
# If filter was activated and no roject was found with that name,
# raise an exception to avoid showing a blank image
if all_projects == {}:
logger.error('No project found from project filter!')
logger.error('Please double check spellings in project_filter')
raise Exception('No matching project found in filter. Please '
'double check spellings in project_filter or leave'
'empty')
# Create single-use generator to filter undone and non-deleted tasks
tasks = (task.data for task in self._api.state['items'] if
task['checked'] == 0 and task['is_deleted']==0)
# Simplify the tasks for faster processing
simplified = [
{
'name':task['content'],
'due':task['due']['string'] if task['due'] != None else "",
'priority':task['priority'],
'project':all_projects[ task['project_id' ] ] if task['project_id'] in all_projects else "deleted"
}
for task in tasks]
# remove groups that have been deleted
simplified = [task for task in simplified if task['project'] != "deleted"]
logger.debug(f'simplified: {simplified}')
# Get maximum width of project names for selected font
project_width = int(max([
self.font.getsize(task['project'])[0] for task in simplified ]) * 1.1)
# Get maximum width of project dues for selected font
due_width = int(max([
self.font.getsize(task['due'])[0] for task in simplified ]) * 1.1)
# Group tasks by project name
grouped = {name: [] for id_, name in all_projects.items()}
for task in simplified:
if task['project'] in grouped:
grouped[task['project']].append(task)
logger.debug(f"grouped: {grouped}")
# Add the parsed todos on the image
cursor = 0
for name, todos in grouped.items():
if todos:
for todo in todos:
if cursor < len(line_positions):
line_x, line_y = line_positions[cursor]
# Add todo project name
write(
im_colour, line_positions[cursor],
(project_width, line_height),
todo['project'], font=self.font, alignment='left')
# Add todo due if not empty
if todo['due'] != "":
write(
im_black,
(line_x + project_width, line_y),
(due_width, line_height),
todo['due'], font=self.font, alignment='left')
# Add todo name
write(
im_black,
(line_x+project_width+due_width, line_y),
(im_width-project_width-due_width, line_height),
todo['name'], font=self.font, alignment='left')
cursor += 1
else: else:
logger.error('More todos than available lines') self.project_filter = config['project_filter']
break
self._api = todoist.TodoistAPI(config['api_key'])
self._api.sync()
# give an OK message
print(f'{filename} loaded')
def _validate(self):
"""Validate module-specific parameters"""
if not isinstance(self.api_key, str):
print('api_key has to be a string: "Yourtopsecretkey123" ')
def generate_image(self):
"""Generate image for this module"""
# Define new image size with respect to padding
im_width = int(self.width - (2 * self.padding_left))
im_height = int(self.height - (2 * self.padding_top))
im_size = im_width, im_height
logger.info(f'Image size: {im_size}')
# Create an image for black pixels and one for coloured pixels
im_black = Image.new('RGB', size=im_size, color='white')
im_colour = Image.new('RGB', size=im_size, color='white')
# Check if internet is available
if internet_available() == True:
logger.info('Connection test passed')
self._api.sync()
else:
raise Exception('Network could not be reached :/')
# Set some parameters for formatting todos
line_spacing = 1
line_height = self.font.getsize('hg')[1] + line_spacing
line_width = im_width
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
# Calculate padding from top so the lines look centralised
spacing_top = int(im_height % line_height / 2)
# Calculate line_positions
line_positions = [
(0, spacing_top + _ * line_height) for _ in range(max_lines)]
# Get all projects by name and id
all_projects = {project['id']: project['name']
for project in self._api.projects.all()}
logger.debug(f"all_projects: {all_projects}")
# Filter entries in all_projects if filter was given
if self.project_filter:
for project_id in list(all_projects):
if all_projects[project_id] not in self.project_filter:
del all_projects[project_id]
logger.debug(f"all_project: {all_projects}")
# If filter was activated and no roject was found with that name,
# raise an exception to avoid showing a blank image
if all_projects == {}:
logger.error('No project found from project filter!')
logger.error('Please double check spellings in project_filter')
raise Exception('No matching project found in filter. Please '
'double check spellings in project_filter or leave'
'empty')
# Create single-use generator to filter undone and non-deleted tasks
tasks = (task.data for task in self._api.state['items'] if
task['checked'] == 0 and task['is_deleted'] == 0)
# Simplify the tasks for faster processing
simplified = [
{
'name': task['content'],
'due': task['due']['string'] if task['due'] != None else "",
'priority': task['priority'],
'project': all_projects[task['project_id']] if task['project_id'] in all_projects else "deleted"
}
for task in tasks]
# remove groups that have been deleted
simplified = [task for task in simplified if task['project'] != "deleted"]
logger.debug(f'simplified: {simplified}')
# Get maximum width of project names for selected font
project_width = int(max([
self.font.getsize(task['project'])[0] for task in simplified]) * 1.1)
# Get maximum width of project dues for selected font
due_width = int(max([
self.font.getsize(task['due'])[0] for task in simplified]) * 1.1)
# Group tasks by project name
grouped = {name: [] for id_, name in all_projects.items()}
for task in simplified:
if task['project'] in grouped:
grouped[task['project']].append(task)
logger.debug(f"grouped: {grouped}")
# Add the parsed todos on the image
cursor = 0
for name, todos in grouped.items():
if todos:
for todo in todos:
if cursor < len(line_positions):
line_x, line_y = line_positions[cursor]
# Add todo project name
write(
im_colour, line_positions[cursor],
(project_width, line_height),
todo['project'], font=self.font, alignment='left')
# Add todo due if not empty
if todo['due'] != "":
write(
im_black,
(line_x + project_width, line_y),
(due_width, line_height),
todo['due'], font=self.font, alignment='left')
# Add todo name
write(
im_black,
(line_x + project_width + due_width, line_y),
(im_width - project_width - due_width, line_height),
todo['name'], font=self.font, alignment='left')
cursor += 1
else:
logger.error('More todos than available lines')
break
# return the images ready for the display
return im_black, im_colour
# return the images ready for the display
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone/debug mode') print(f'running {filename} in standalone/debug mode')

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Weather module for Inky-Calendar software. Weather module for Inky-Calendar software.
Copyright by aceisace Copyright by aceisace
@@ -13,510 +13,502 @@ import arrow
from locale import getdefaultlocale as sys_locale from locale import getdefaultlocale as sys_locale
try: try:
from pyowm.owm import OWM from pyowm.owm import OWM
except ImportError: except ImportError:
print('pyowm is not installed! Please install with:') print('pyowm is not installed! Please install with:')
print('pip3 install pyowm') print('pip3 install pyowm')
filename = os.path.basename(__file__).split('.py')[0] filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename) logger = logging.getLogger(filename)
class Weather(inkycal_module): class Weather(inkycal_module):
"""Weather class """Weather class
parses weather details from openweathermap parses weather details from openweathermap
""" """
name = "Weather (openweathermap) - Get weather forecasts from openweathermap" name = "Weather (openweathermap) - Get weather forecasts from openweathermap"
requires = { requires = {
"api_key" : { "api_key": {
"label":"Please enter openweathermap api-key. You can create one for free on openweathermap", "label": "Please enter openweathermap api-key. You can create one for free on openweathermap",
}, },
"location": { "location": {
"label":"Please enter your location in the following format: City, Country-Code. "+ "label": "Please enter your location in the following format: City, Country-Code. " +
"You can also enter the location ID found in the url "+ "You can also enter the location ID found in the url " +
"e.g. https://openweathermap.org/city/4893171 -> ID is 4893171" "e.g. https://openweathermap.org/city/4893171 -> ID is 4893171"
} }
} }
optional = { optional = {
"round_temperature": { "round_temperature": {
"label":"Round temperature to the nearest degree?", "label": "Round temperature to the nearest degree?",
"options": [True, False], "options": [True, False],
}, },
"round_windspeed": { "round_windspeed": {
"label":"Round windspeed?", "label": "Round windspeed?",
"options": [True, False], "options": [True, False],
}, },
"forecast_interval": { "forecast_interval": {
"label":"Please select the forecast interval", "label": "Please select the forecast interval",
"options": ["daily", "hourly"], "options": ["daily", "hourly"],
}, },
"units": { "units": {
"label": "Which units should be used?", "label": "Which units should be used?",
"options": ["metric", "imperial"], "options": ["metric", "imperial"],
}, },
"hour_format": { "hour_format": {
"label": "Which hour format do you prefer?", "label": "Which hour format do you prefer?",
"options": [24, 12], "options": [24, 12],
}, },
"use_beaufort": { "use_beaufort": {
"label": "Use beaufort scale for windspeed?", "label": "Use beaufort scale for windspeed?",
"options": [True, False], "options": [True, False],
}, },
} }
def __init__(self, config): def __init__(self, config):
"""Initialize inkycal_weather module""" """Initialize inkycal_weather module"""
super().__init__(config) super().__init__(config)
config = config['config'] config = config['config']
# Check if all required parameters are present # Check if all required parameters are present
for param in self.requires: for param in self.requires:
if not param in config: if not param in config:
raise Exception(f'config is missing {param}') raise Exception(f'config is missing {param}')
# required parameters # required parameters
self.api_key = config['api_key'] self.api_key = config['api_key']
self.location = config['location'] self.location = config['location']
# optional parameters # optional parameters
self.round_temperature = config['round_temperature'] self.round_temperature = config['round_temperature']
self.round_windspeed = config['round_windspeed'] self.round_windspeed = config['round_windspeed']
self.forecast_interval = config['forecast_interval'] self.forecast_interval = config['forecast_interval']
self.units = config['units'] self.units = config['units']
self.hour_format = int(config['hour_format']) self.hour_format = int(config['hour_format'])
self.use_beaufort = config['use_beaufort'] self.use_beaufort = config['use_beaufort']
# additional configuration # additional configuration
self.owm = OWM(self.api_key).weather_manager() self.owm = OWM(self.api_key).weather_manager()
self.timezone = get_system_tz() self.timezone = get_system_tz()
self.locale = config['language'] self.locale = config['language']
self.weatherfont = ImageFont.truetype( self.weatherfont = ImageFont.truetype(
fonts['weathericons-regular-webfont'], size = self.fontsize) fonts['weathericons-regular-webfont'], size=self.fontsize)
# give an OK message # give an OK message
print(f"{filename} loaded") print(f"{filename} loaded")
def generate_image(self):
def generate_image(self): """Generate image for this module"""
"""Generate image for this module"""
# Define new image size with respect to padding
# Define new image size with respect to padding im_width = int(self.width - (2 * self.padding_left))
im_width = int(self.width - (2 * self.padding_left)) im_height = int(self.height - (2 * self.padding_top))
im_height = int(self.height - (2 * self.padding_top)) im_size = im_width, im_height
im_size = im_width, im_height logger.info(f'Image size: {im_size}')
logger.info(f'Image size: {im_size}')
# Create an image for black pixels and one for coloured pixels
# Create an image for black pixels and one for coloured pixels im_black = Image.new('RGB', size=im_size, color='white')
im_black = Image.new('RGB', size = im_size, color = 'white') im_colour = Image.new('RGB', size=im_size, color='white')
im_colour = Image.new('RGB', size = im_size, color = 'white')
# Check if internet is available
# Check if internet is available if internet_available() == True:
if internet_available() == True: logger.info('Connection test passed')
logger.info('Connection test passed') else:
else: logger.exception('Network could not be reached :(')
logger.exception('Network could not be reached :(') raise
raise
def get_moon_phase():
def get_moon_phase(): """Calculate the current (approximate) moon phase"""
"""Calculate the current (approximate) moon phase"""
dec = decimal.Decimal
dec = decimal.Decimal diff = now - arrow.get(2001, 1, 1)
diff = now - arrow.get(2001, 1, 1) days = dec(diff.days) + (dec(diff.seconds) / dec(86400))
days = dec(diff.days) + (dec(diff.seconds) / dec(86400)) lunations = dec("0.20439731") + (days * dec("0.03386319269"))
lunations = dec("0.20439731") + (days * dec("0.03386319269")) position = lunations % dec(1)
position = lunations % dec(1) index = math.floor((position * dec(8)) + dec("0.5"))
index = math.floor((position * dec(8)) + dec("0.5")) return {0: '\uf095', 1: '\uf099', 2: '\uf09c', 3: '\uf0a0',
return {0: '\uf095',1: '\uf099',2: '\uf09c',3: '\uf0a0', 4: '\uf0a3', 5: '\uf0a7', 6: '\uf0aa', 7: '\uf0ae'}[int(index) & 7]
4: '\uf0a3',5: '\uf0a7',6: '\uf0aa',7: '\uf0ae' }[int(index) & 7]
def is_negative(temp):
"""Check if temp is below freezing point of water (0°C/30°F)
def is_negative(temp): returns True if temp below freezing point, else False"""
"""Check if temp is below freezing point of water (0°C/30°F) answer = False
returns True if temp below freezing point, else False"""
answer = False if temp_unit == 'celsius' and round(float(temp.split('°')[0])) <= 0:
answer = True
if temp_unit == 'celsius' and round(float(temp.split('°')[0])) <= 0: elif temp_unit == 'fahrenheit' and round(float(temp.split('°')[0])) <= 0:
answer = True answer = True
elif temp_unit == 'fahrenheit' and round(float(temp.split('°')[0])) <= 0: return answer
answer = True
return answer # Lookup-table for weather icons and weather codes
weathericons = {
# Lookup-table for weather icons and weather codes '01d': '\uf00d', '02d': '\uf002', '03d': '\uf013',
weathericons = { '04d': '\uf012', '09d': '\uf01a ', '10d': '\uf019',
'01d': '\uf00d', '02d': '\uf002', '03d': '\uf013', '11d': '\uf01e', '13d': '\uf01b', '50d': '\uf014',
'04d': '\uf012', '09d': '\uf01a ', '10d': '\uf019', '01n': '\uf02e', '02n': '\uf013', '03n': '\uf013',
'11d': '\uf01e', '13d': '\uf01b', '50d': '\uf014', '04n': '\uf013', '09n': '\uf037', '10n': '\uf036',
'01n': '\uf02e', '02n': '\uf013', '03n': '\uf013', '11n': '\uf03b', '13n': '\uf038', '50n': '\uf023'
'04n': '\uf013', '09n': '\uf037', '10n': '\uf036', }
'11n': '\uf03b', '13n': '\uf038', '50n': '\uf023'
} def draw_icon(image, xy, box_size, icon, rotation=None):
"""Custom function to add icons of weather font on image
image = on which image should the text be added?
def draw_icon(image, xy, box_size, icon, rotation = None): xy = xy-coordinates as tuple -> (x,y)
"""Custom function to add icons of weather font on image box_size = size of text-box -> (width,height)
image = on which image should the text be added? icon = icon-unicode, looks this up in weathericons dictionary
xy = xy-coordinates as tuple -> (x,y) """
box_size = size of text-box -> (width,height) x, y = xy
icon = icon-unicode, looks this up in weathericons dictionary box_width, box_height = box_size
""" text = icon
x,y = xy font = self.weatherfont
box_width, box_height = box_size
text = icon # Increase fontsize to fit specified height and width of text box
font = self.weatherfont size = 8
font = ImageFont.truetype(font.path, size)
# Increase fontsize to fit specified height and width of text box text_width, text_height = font.getsize(text)
size = 8
font = ImageFont.truetype(font.path, size) while (text_width < int(box_width * 0.9) and
text_width, text_height = font.getsize(text) text_height < int(box_height * 0.9)):
size += 1
while (text_width < int(box_width * 0.9) and font = ImageFont.truetype(font.path, size)
text_height < int(box_height * 0.9)): text_width, text_height = font.getsize(text)
size += 1
font = ImageFont.truetype(font.path, size) text_width, text_height = font.getsize(text)
text_width, text_height = font.getsize(text)
# Align text to desired position
text_width, text_height = font.getsize(text) x = int((box_width / 2) - (text_width / 2))
y = int((box_height / 2) - (text_height / 2))
# Align text to desired position
x = int((box_width / 2) - (text_width / 2)) # Draw the text in the text-box
y = int((box_height / 2) - (text_height / 2)) draw = ImageDraw.Draw(image)
space = Image.new('RGBA', (box_width, box_height))
# Draw the text in the text-box ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
draw = ImageDraw.Draw(image)
space = Image.new('RGBA', (box_width, box_height)) if rotation != None:
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) space.rotate(rotation, expand=True)
if rotation != None: # Update only region with text (add text with transparent background)
space.rotate(rotation, expand = True) image.paste(space, xy, space)
# Update only region with text (add text with transparent background) # column1 column2 column3 column4 column5 column6 column7
image.paste(space, xy, space) # |----------|----------|----------|----------|----------|----------|----------|
# | time | temperat.| moonphase| forecast1| forecast2| forecast3| forecast4|
# | current |----------|----------|----------|----------|----------|----------|
# | weather | humidity | sunrise | icon1 | icon2 | icon3 | icon4 |
# column1 column2 column3 column4 column5 column6 column7 # | icon |----------|----------|----------|----------|----------|----------|
# |----------|----------|----------|----------|----------|----------|----------| # | | windspeed| sunset | temperat.| temperat.| temperat.| temperat.|
# | time | temperat.| moonphase| forecast1| forecast2| forecast3| forecast4| # |----------|----------|----------|----------|----------|----------|----------|
# | current |----------|----------|----------|----------|----------|----------|
# | weather | humidity | sunrise | icon1 | icon2 | icon3 | icon4 | # Calculate size rows and columns
# | icon |----------|----------|----------|----------|----------|----------| col_width = im_width // 7
# | | windspeed| sunset | temperat.| temperat.| temperat.| temperat.|
# |----------|----------|----------|----------|----------|----------|----------| # Ratio width height
image_ratio = im_width / im_height
# Calculate size rows and columns if image_ratio >= 4:
col_width = im_width // 7 row_height = im_height // 3
else:
# Ratio width height logger.info('Please consider decreasing the height.')
image_ratio = im_width / im_height row_height = int((im_height * (1 - im_height / im_width)) / 3)
if image_ratio >= 4: logger.debug(f"row_height: {row_height} | col_width: {col_width}")
row_height = im_height // 3
else: # Calculate spacings for better centering
logger.info('Please consider decreasing the height.') spacing_top = int((im_width % col_width) / 2)
row_height = int( (im_height* (1-im_height/im_width)) / 3 ) spacing_left = int((im_height % row_height) / 2)
logger.debug(f"row_height: {row_height} | col_width: {col_width}") # Define sizes for weather icons
icon_small = int(col_width / 3)
# Calculate spacings for better centering icon_medium = icon_small * 2
spacing_top = int( (im_width % col_width) / 2 ) icon_large = icon_small * 3
spacing_left = int( (im_height % row_height) / 2 )
# Calculate the x-axis position of each col
# Define sizes for weather icons col1 = spacing_top
icon_small = int(col_width / 3) col2 = col1 + col_width
icon_medium = icon_small * 2 col3 = col2 + col_width
icon_large = icon_small * 3 col4 = col3 + col_width
col5 = col4 + col_width
# Calculate the x-axis position of each col col6 = col5 + col_width
col1 = spacing_top col7 = col6 + col_width
col2 = col1 + col_width
col3 = col2 + col_width # Calculate the y-axis position of each row
col4 = col3 + col_width line_gap = int((im_height - spacing_top - 3 * row_height) // 4)
col5 = col4 + col_width
col6 = col5 + col_width row1 = line_gap
col7 = col6 + col_width row2 = row1 + line_gap + row_height
row3 = row2 + line_gap + row_height
# Calculate the y-axis position of each row
line_gap = int((im_height - spacing_top - 3*row_height) // 4) # Draw lines on each row and border
############################################################################
row1 = line_gap ## draw = ImageDraw.Draw(im_black)
row2 = row1 + line_gap + row_height ## draw.line((0, 0, im_width, 0), fill='red')
row3 = row2+ line_gap + row_height ## draw.line((0, im_height-1, im_width, im_height-1), fill='red')
## draw.line((0, row1, im_width, row1), fill='black')
# Draw lines on each row and border ## draw.line((0, row1+row_height, im_width, row1+row_height), fill='black')
############################################################################ ## draw.line((0, row2, im_width, row2), fill='black')
## draw = ImageDraw.Draw(im_black) ## draw.line((0, row2+row_height, im_width, row2+row_height), fill='black')
## draw.line((0, 0, im_width, 0), fill='red') ## draw.line((0, row3, im_width, row3), fill='black')
## draw.line((0, im_height-1, im_width, im_height-1), fill='red') ## draw.line((0, row3+row_height, im_width, row3+row_height), fill='black')
## draw.line((0, row1, im_width, row1), fill='black') ############################################################################
## draw.line((0, row1+row_height, im_width, row1+row_height), fill='black')
## draw.line((0, row2, im_width, row2), fill='black') # Positions for current weather details
## draw.line((0, row2+row_height, im_width, row2+row_height), fill='black') weather_icon_pos = (col1, 0)
## draw.line((0, row3, im_width, row3), fill='black') temperature_icon_pos = (col2, row1)
## draw.line((0, row3+row_height, im_width, row3+row_height), fill='black') temperature_pos = (col2 + icon_small, row1)
############################################################################ humidity_icon_pos = (col2, row2)
humidity_pos = (col2 + icon_small, row2)
windspeed_icon_pos = (col2, row3)
# Positions for current weather details windspeed_pos = (col2 + icon_small, row3)
weather_icon_pos = (col1, 0)
temperature_icon_pos = (col2, row1) # Positions for sunrise, sunset, moonphase
temperature_pos = (col2+icon_small, row1) moonphase_pos = (col3, row1)
humidity_icon_pos = (col2, row2) sunrise_icon_pos = (col3, row2)
humidity_pos = (col2+icon_small, row2) sunrise_time_pos = (col3 + icon_small, row2)
windspeed_icon_pos = (col2, row3) sunset_icon_pos = (col3, row3)
windspeed_pos = (col2+icon_small, row3) sunset_time_pos = (col3 + icon_small, row3)
# Positions for sunrise, sunset, moonphase # Positions for forecast 1
moonphase_pos = (col3, row1) stamp_fc1 = (col4, row1)
sunrise_icon_pos = (col3, row2) icon_fc1 = (col4, row1 + row_height)
sunrise_time_pos = (col3+icon_small, row2) temp_fc1 = (col4, row3)
sunset_icon_pos = (col3, row3)
sunset_time_pos = (col3+ icon_small, row3) # Positions for forecast 2
stamp_fc2 = (col5, row1)
# Positions for forecast 1 icon_fc2 = (col5, row1 + row_height)
stamp_fc1 = (col4, row1) temp_fc2 = (col5, row3)
icon_fc1 = (col4, row1+row_height)
temp_fc1 = (col4, row3) # Positions for forecast 3
stamp_fc3 = (col6, row1)
# Positions for forecast 2 icon_fc3 = (col6, row1 + row_height)
stamp_fc2 = (col5, row1) temp_fc3 = (col6, row3)
icon_fc2 = (col5, row1+row_height)
temp_fc2 = (col5, row3) # Positions for forecast 4
stamp_fc4 = (col7, row1)
# Positions for forecast 3 icon_fc4 = (col7, row1 + row_height)
stamp_fc3 = (col6, row1) temp_fc4 = (col7, row3)
icon_fc3 = (col6, row1+row_height)
temp_fc3 = (col6, row3) # Create current-weather and weather-forecast objects
if self.location.isdigit():
# Positions for forecast 4 logging.debug('looking up location by ID')
stamp_fc4 = (col7, row1) weather = self.owm.weather_at_id(int(self.location)).weather
icon_fc4 = (col7, row1+row_height) forecast = self.owm.forecast_at_id(int(self.location), '3h')
temp_fc4 = (col7, row3) else:
logging.debug('looking up location by string')
# Create current-weather and weather-forecast objects weather = self.owm.weather_at_place(self.location).weather
if self.location.isdigit(): forecast = self.owm.forecast_at_place(self.location, '3h')
logging.debug('looking up location by ID')
weather = self.owm.weather_at_id(int(self.location)).weather # Set decimals
forecast = self.owm.forecast_at_id(int(self.location), '3h') dec_temp = None if self.round_temperature == True else 1
else: dec_wind = None if self.round_windspeed == True else 1
logging.debug('looking up location by string')
weather = self.owm.weather_at_place(self.location).weather # Set correct temperature units
forecast = self.owm.forecast_at_place(self.location, '3h') if self.units == 'metric':
temp_unit = 'celsius'
# Set decimals elif self.units == 'imperial':
dec_temp = None if self.round_temperature == True else 1 temp_unit = 'fahrenheit'
dec_wind = None if self.round_windspeed == True else 1
logging.debug(f'temperature unit: {temp_unit}')
# Set correct temperature units logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}')
if self.units == 'metric':
temp_unit = 'celsius' # Get current time
elif self.units == 'imperial': now = arrow.utcnow()
temp_unit = 'fahrenheit'
if self.forecast_interval == 'hourly':
logging.debug(f'temperature unit: {temp_unit}')
logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}') logger.debug("getting hourly forecasts")
# Get current time # Forecasts are provided for every 3rd full hour
now = arrow.utcnow() # find out how many hours there are until the next 3rd full hour
if (now.hour % 3) != 0:
if self.forecast_interval == 'hourly': hour_gap = 3 - (now.hour % 3)
else:
logger.debug("getting hourly forecasts") hour_gap = 3
# Forecasts are provided for every 3rd full hour # Create timings for hourly forcasts
# find out how many hours there are until the next 3rd full hour forecast_timings = [now.shift(hours=+ hour_gap + _).floor('hour')
if (now.hour % 3) != 0: for _ in range(0, 12, 3)]
hour_gap = 3 - (now.hour % 3)
else: # Create forecast objects for given timings
hour_gap = 3 forecasts = [forecast.get_weather_at(forecast_time.datetime) for
forecast_time in forecast_timings]
# Create timings for hourly forcasts
forecast_timings = [now.shift(hours = + hour_gap + _).floor('hour') # Add forecast-data to fc_data dictionary
for _ in range(0,12,3)] fc_data = {}
for forecast in forecasts:
# Create forecast objects for given timings temp = '{}°'.format(round(
forecasts = [forecast.get_weather_at(forecast_time.datetime) for forecast.temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
forecast_time in forecast_timings]
icon = forecast.weather_icon_name
# Add forecast-data to fc_data dictionary fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
fc_data = {} 'temp': temp,
for forecast in forecasts: 'icon': icon,
temp = '{}°'.format(round( 'stamp': forecast_timings[forecasts.index(forecast)].to(
forecast.temperature(unit=temp_unit)['temp'], ndigits=dec_temp)) get_system_tz()).format('H.00' if self.hour_format == 24 else 'h a')
}
icon = forecast.weather_icon_name
fc_data['fc'+str(forecasts.index(forecast)+1)] = { elif self.forecast_interval == 'daily':
'temp':temp,
'icon':icon, logger.debug("getting daily forecasts")
'stamp': forecast_timings[forecasts.index(forecast)].to(
get_system_tz()).format('H.00' if self.hour_format == 24 else 'h a') def calculate_forecast(days_from_today):
} """Get temperature range and most frequent icon code for forecast
days_from_today should be int from 1-4: e.g. 2 -> 2 days from today
elif self.forecast_interval == 'daily': """
logger.debug("getting daily forecasts") # Create a list containing time-objects for every 3rd hour of the day
time_range = list(arrow.Arrow.range('hour',
now.shift(days=days_from_today).floor('day'),
def calculate_forecast(days_from_today): now.shift(days=days_from_today).ceil('day')
"""Get temperature range and most frequent icon code for forecast ))[::3]
days_from_today should be int from 1-4: e.g. 2 -> 2 days from today
""" # Get forecasts for each time-object
forecasts = [forecast.get_weather_at(_.datetime) for _ in time_range]
# Create a list containing time-objects for every 3rd hour of the day
time_range = list(arrow.Arrow.range('hour', # Get all temperatures for this day
now.shift(days=days_from_today).floor('day'), daily_temp = [round(_.temperature(unit=temp_unit)['temp'],
now.shift(days=days_from_today).ceil('day') ndigits=dec_temp) for _ in forecasts]
))[::3] # Calculate min. and max. temp for this day
temp_range = f'{max(daily_temp)}°/{min(daily_temp)}°'
# Get forecasts for each time-object
forecasts = [forecast.get_weather_at(_.datetime) for _ in time_range] # Get all weather icon codes for this day
daily_icons = [_.weather_icon_name for _ in forecasts]
# Get all temperatures for this day # Find most common element from all weather icon codes
daily_temp = [round(_.temperature(unit=temp_unit)['temp'], status = max(set(daily_icons), key=daily_icons.count)
ndigits=dec_temp) for _ in forecasts]
# Calculate min. and max. temp for this day weekday = now.shift(days=days_from_today).format('ddd', locale=
temp_range = f'{max(daily_temp)}°/{min(daily_temp)}°' self.locale)
return {'temp': temp_range, 'icon': status, 'stamp': weekday}
# Get all weather icon codes for this day forecasts = [calculate_forecast(days) for days in range(1, 5)]
daily_icons = [_.weather_icon_name for _ in forecasts]
# Find most common element from all weather icon codes fc_data = {}
status = max(set(daily_icons), key=daily_icons.count) for forecast in forecasts:
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
weekday = now.shift(days=days_from_today).format('ddd', locale= 'temp': forecast['temp'],
self.locale) 'icon': forecast['icon'],
return {'temp':temp_range, 'icon':status, 'stamp': weekday} 'stamp': forecast['stamp']
}
forecasts = [calculate_forecast(days) for days in range (1,5)]
for key, val in fc_data.items():
fc_data = {} logger.debug((key, val))
for forecast in forecasts:
fc_data['fc'+str(forecasts.index(forecast)+1)] = { # Get some current weather details
'temp':forecast['temp'], temperature = '{}°'.format(round(
'icon':forecast['icon'], weather.temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
'stamp': forecast['stamp']
} weather_icon = weather.weather_icon_name
humidity = str(weather.humidity)
sunrise_raw = arrow.get(weather.sunrise_time()).to(self.timezone)
sunset_raw = arrow.get(weather.sunset_time()).to(self.timezone)
for key,val in fc_data.items(): logger.debug(f'weather_icon: {weather_icon}')
logger.debug((key,val))
# Get some current weather details if self.hour_format == 12:
temperature = '{}°'.format(round( logger.debug('using 12 hour format for sunrise/sunset')
weather.temperature(unit=temp_unit)['temp'], ndigits=dec_temp)) sunrise = sunrise_raw.format('h:mm a')
sunset = sunset_raw.format('h:mm a')
weather_icon = weather.weather_icon_name elif self.hour_format == 24:
humidity = str(weather.humidity) logger.debug('using 24 hour format for sunrise/sunset')
sunrise_raw = arrow.get(weather.sunrise_time()).to(self.timezone) sunrise = sunrise_raw.format('H:mm')
sunset_raw = arrow.get(weather.sunset_time()).to(self.timezone) sunset = sunset_raw.format('H:mm')
logger.debug(f'weather_icon: {weather_icon}') # Format the windspeed to user preference
if self.use_beaufort == True:
logger.debug("using beaufort for wind")
wind = str(weather.wind(unit='beaufort')['speed'])
elif self.use_beaufort == False:
if self.units == 'metric':
logging.debug('getting windspeed in metric unit')
wind = str(weather.wind(unit='meters_sec')['speed']) + 'm/s'
if self.hour_format == 12: elif self.units == 'imperial':
logger.debug('using 12 hour format for sunrise/sunset') logging.debug('getting windspeed in imperial unit')
sunrise = sunrise_raw.format('h:mm a') wind = str(weather.wind(unit='miles_hour')['speed']) + 'miles/h'
sunset = sunset_raw.format('h:mm a')
elif self.hour_format == 24: dec = decimal.Decimal
logger.debug('using 24 hour format for sunrise/sunset') moonphase = get_moon_phase()
sunrise = sunrise_raw.format('H:mm')
sunset = sunset_raw.format('H:mm')
# Format the windspeed to user preference # Fill weather details in col 1 (current weather icon)
if self.use_beaufort == True: draw_icon(im_colour, weather_icon_pos, (col_width, im_height),
logger.debug("using beaufort for wind") weathericons[weather_icon])
wind = str(weather.wind(unit='beaufort')['speed'])
elif self.use_beaufort == False: # Fill weather details in col 2 (temp, humidity, wind)
draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height),
'\uf053')
if self.units == 'metric': if is_negative(temperature):
logging.debug('getting windspeed in metric unit') write(im_black, temperature_pos, (col_width - icon_small, row_height),
wind = str(weather.wind(unit='meters_sec')['speed']) + 'm/s' temperature, font=self.font)
else:
write(im_black, temperature_pos, (col_width - icon_small, row_height),
temperature, font=self.font)
elif self.units == 'imperial': draw_icon(im_colour, humidity_icon_pos, (icon_small, row_height),
logging.debug('getting windspeed in imperial unit') '\uf07a')
wind = str(weather.wind(unit='miles_hour')['speed']) + 'miles/h'
dec = decimal.Decimal write(im_black, humidity_pos, (col_width - icon_small, row_height),
moonphase = get_moon_phase() humidity + '%', font=self.font)
# Fill weather details in col 1 (current weather icon) draw_icon(im_colour, windspeed_icon_pos, (icon_small, icon_small),
draw_icon(im_colour, weather_icon_pos, (col_width, im_height), '\uf050')
weathericons[weather_icon])
# Fill weather details in col 2 (temp, humidity, wind) write(im_black, windspeed_pos, (col_width - icon_small, row_height),
draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height), wind, font=self.font)
'\uf053')
if is_negative(temperature): # Fill weather details in col 3 (moonphase, sunrise, sunset)
write(im_black, temperature_pos, (col_width-icon_small, row_height), draw_icon(im_colour, moonphase_pos, (col_width, row_height), moonphase)
temperature, font = self.font)
else:
write(im_black, temperature_pos, (col_width-icon_small, row_height),
temperature, font = self.font)
draw_icon(im_colour, humidity_icon_pos, (icon_small, row_height), draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051')
'\uf07a') write(im_black, sunrise_time_pos, (col_width - icon_small, row_height),
sunrise, font=self.font)
write(im_black, humidity_pos, (col_width-icon_small, row_height), draw_icon(im_colour, sunset_icon_pos, (icon_small, icon_small), '\uf052')
humidity+'%', font = self.font) write(im_black, sunset_time_pos, (col_width - icon_small, row_height), sunset,
font=self.font)
draw_icon(im_colour, windspeed_icon_pos, (icon_small, icon_small), # Add the forecast data to the correct places
'\uf050') for pos in range(1, len(fc_data) + 1):
stamp = fc_data[f'fc{pos}']['stamp']
write(im_black, windspeed_pos, (col_width-icon_small, row_height), icon = weathericons[fc_data[f'fc{pos}']['icon']]
wind, font=self.font) temp = fc_data[f'fc{pos}']['temp']
# Fill weather details in col 3 (moonphase, sunrise, sunset) write(im_black, eval(f'stamp_fc{pos}'), (col_width, row_height),
draw_icon(im_colour, moonphase_pos, (col_width, row_height), moonphase) stamp, font=self.font)
draw_icon(im_colour, eval(f'icon_fc{pos}'), (col_width, row_height + line_gap * 2),
icon)
write(im_black, eval(f'temp_fc{pos}'), (col_width, row_height),
temp, font=self.font)
draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051') border_h = row3 + row_height
write(im_black, sunrise_time_pos, (col_width-icon_small, row_height), border_w = col_width - 3 # leave 3 pixels gap
sunrise, font = self.font)
draw_icon(im_colour, sunset_icon_pos, (icon_small, icon_small), '\uf052') # Add borders around each sub-section
write(im_black, sunset_time_pos, (col_width-icon_small, row_height), sunset, draw_border(im_black, (col1, row1), (col_width * 3 - 3, border_h),
font = self.font) shrinkage=(0, 0))
# Add the forecast data to the correct places for _ in range(4, 8):
for pos in range(1, len(fc_data)+1): draw_border(im_black, (eval(f'col{_}'), row1), (border_w, border_h),
stamp = fc_data[f'fc{pos}']['stamp'] shrinkage=(0, 0))
icon = weathericons[fc_data[f'fc{pos}']['icon']] # return the images ready for the display
temp = fc_data[f'fc{pos}']['temp'] return im_black, im_colour
write(im_black, eval(f'stamp_fc{pos}'), (col_width, row_height),
stamp, font = self.font)
draw_icon(im_colour, eval(f'icon_fc{pos}'), (col_width, row_height+line_gap*2),
icon)
write(im_black, eval(f'temp_fc{pos}'), (col_width, row_height),
temp, font = self.font)
border_h = row3 + row_height
border_w = col_width - 3 #leave 3 pixels gap
# Add borders around each sub-section
draw_border(im_black, (col1, row1), (col_width*3 - 3, border_h),
shrinkage=(0,0))
for _ in range(4,8):
draw_border(im_black, (eval(f'col{_}'), row1), (border_w, border_h),
shrinkage=(0,0))
# return the images ready for the display
return im_black, im_colour
if __name__ == '__main__': if __name__ == '__main__':
print(f'running {filename} in standalone mode') print(f'running {filename} in standalone mode')

View File

@@ -1,92 +1,92 @@
#!python3
import abc import abc
from inkycal.custom import * from inkycal.custom import *
class inkycal_module(metaclass=abc.ABCMeta): class inkycal_module(metaclass=abc.ABCMeta):
"""Generic base class for inkycal modules""" """Generic base class for inkycal modules"""
@classmethod @classmethod
def __subclasshook__(cls, subclass): def __subclasshook__(cls, subclass):
return (hasattr(subclass, 'generate_image') and return (hasattr(subclass, 'generate_image') and
callable(subclass.generate_image) or callable(subclass.generate_image) or
NotImplemented) NotImplemented)
def __init__(self, config): def __init__(self, config):
"""Initialize module with given config""" """Initialize module with given config"""
# Initializes base module # Initializes base module
# sets properties shared amongst all sections # sets properties shared amongst all sections
self.config = conf = config['config'] self.config = conf = config['config']
self.width, self.height = conf['size'] self.width, self.height = conf['size']
self.padding_left = self.padding_right = conf["padding_x"] self.padding_left = self.padding_right = conf["padding_x"]
self.padding_top = self.padding_bottom = conf['padding_y'] self.padding_top = self.padding_bottom = conf['padding_y']
self.fontsize = conf["fontsize"] self.fontsize = conf["fontsize"]
self.font = ImageFont.truetype( self.font = ImageFont.truetype(
fonts['NotoSansUI-Regular'], size = self.fontsize) fonts['NotoSansUI-Regular'], size=self.fontsize)
def set(self, help=False, **kwargs): def set(self, help=False, **kwargs):
"""Set attributes of class, e.g. class.set(key=value) """Set attributes of class, e.g. class.set(key=value)
see that can be changed by setting help to True see that can be changed by setting help to True
""" """
lst = dir(self).copy() lst = dir(self).copy()
options = [_ for _ in lst if not _.startswith('_')] options = [_ for _ in lst if not _.startswith('_')]
if 'logger' in options: options.remove('logger') if 'logger' in options: options.remove('logger')
if help == True: if help == True:
print('The following can be configured:') print('The following can be configured:')
print(options) print(options)
for key, value in kwargs.items(): for key, value in kwargs.items():
if key in options: if key in options:
if key == 'fontsize': if key == 'fontsize':
self.font = ImageFont.truetype(self.font.path, value) self.font = ImageFont.truetype(self.font.path, value)
self.fontsize = value self.fontsize = value
else: else:
setattr(self, key, value) setattr(self, key, value)
print(f"set '{key}' to '{value}'") print(f"set '{key}' to '{value}'")
else: else:
print(f'{key} does not exist') print(f'{key} does not exist')
pass pass
# Check if validation has been implemented # Check if validation has been implemented
try: try:
self._validate() self._validate()
except AttributeError: except AttributeError:
print('no validation implemented') print('no validation implemented')
@abc.abstractmethod @abc.abstractmethod
def generate_image(self): def generate_image(self):
# Generate image for this module with specified parameters # Generate image for this module with specified parameters
raise NotImplementedError( raise NotImplementedError(
'The developers were too lazy to implement this function') 'The developers were too lazy to implement this function')
@classmethod @classmethod
def get_config(cls): def get_config(cls):
# Do not change # Do not change
# Get the config of this module for the web-ui # Get the config of this module for the web-ui
try: try:
if hasattr(cls, 'requires'):
for each in cls.requires:
if not "label" in cls.requires[each]:
raise Exception(f"no label found for {each}")
if hasattr(cls, 'optional'):
for each in cls.optional:
if not "label" in cls.optional[each]:
raise Exception(f"no label found for {each}")
conf = {
"name": cls.__name__,
"name_str": cls.name,
"requires": cls.requires if hasattr(cls, 'requires') else {},
"optional": cls.optional if hasattr(cls, 'optional') else {},
}
return conf
except:
raise Exception(
'Ohoh, something went wrong while trying to get the config of this module')
if hasattr(cls, 'requires'):
for each in cls.requires:
if not "label" in cls.requires[each]:
raise Exception(f"no label found for {each}")
if hasattr(cls, 'optional'):
for each in cls.optional:
if not "label" in cls.optional[each]:
raise Exception(f"no label found for {each}")
conf = {
"name": cls.__name__,
"name_str": cls.name,
"requires": cls.requires if hasattr(cls, 'requires') else {},
"optional": cls.optional if hasattr(cls, 'optional') else {},
}
return conf
except:
raise Exception(
'Ohoh, something went wrong while trying to get the config of this module')

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
iCalendar parser test (ical_parser) iCalendar parser test (ical_parser)
@@ -14,45 +13,45 @@ from urllib.request import urlopen
from inkycal.modules.ical_parser import iCalendar from inkycal.modules.ical_parser import iCalendar
from helper_functions import * from helper_functions import *
ical = iCalendar() ical = iCalendar()
test_ical = 'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics' test_ical = 'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics'
class ical_parser_test(unittest.TestCase): class ical_parser_test(unittest.TestCase):
def test_load_url(self): def test_load_url(self):
print('testing loading via URL...', end="") print('testing loading via URL...', end="")
ical.load_url(test_ical) ical.load_url(test_ical)
print('OK') print('OK')
def test_get_events(self): def test_get_events(self):
print('testing parsing of events...', end="") print('testing parsing of events...', end="")
ical.get_events(arrow.now(), arrow.now().shift(weeks=30)) ical.get_events(arrow.now(), arrow.now().shift(weeks=30))
print('OK') print('OK')
def test_sorting(self): def test_sorting(self):
print('testing sorting of events...', end="") print('testing sorting of events...', end="")
ical.sort() ical.sort()
print('OK') print('OK')
def test_show_events(self): def test_show_events(self):
print('testing if events can be shown...', end="") print('testing if events can be shown...', end="")
ical.show_events() ical.show_events()
print('OK') print('OK')
def test_laod_from_file(self):
print('testing loading from file...', end="")
dummy = str(urlopen(test_ical).read().decode())
with open('dummy.ical', mode="w") as file:
file.write(dummy)
ical.load_from_file('dummy.ical')
print('OK')
os.remove('dummy.ical')
def test_laod_from_file(self):
print('testing loading from file...', end="")
dummy = str(urlopen(test_ical).read().decode())
with open('dummy.ical', mode="w") as file:
file.write(dummy)
ical.load_from_file('dummy.ical')
print('OK')
os.remove('dummy.ical')
if __name__ == '__main__': if __name__ == '__main__':
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
logger = logging.getLogger() unittest.main()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
unittest.main()

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Agenda test (inkycal_agenda) Agenda test (inkycal_agenda)
Copyright by aceisace Copyright by aceisace
@@ -7,80 +7,80 @@ Copyright by aceisace
import unittest import unittest
from inkycal.modules import Agenda as Module from inkycal.modules import Agenda as Module
from helper_functions import * from helper_functions import *
environment = get_environment() environment = get_environment()
# Set to True to preview images. Only works on Raspberry Pi OS with Desktop # Set to True to preview images. Only works on Raspberry Pi OS with Desktop
use_preview = False use_preview = False
sample_url = "https://www.officeholidays.com/ics-fed/usa" sample_url = "https://www.officeholidays.com/ics-fed/usa"
tests = [ tests = [
{ {
"name": "Agenda", "name": "Agenda",
"config": { "config": {
"size": [400, 200], "size": [400, 200],
"ical_urls": sample_url, "ical_urls": sample_url,
"ical_files": None, "ical_files": None,
"date_format": "ddd D MMM", "date_format": "ddd D MMM",
"time_format": "HH:mm", "time_format": "HH:mm",
"padding_x": 10, "padding_x": 10,
"padding_y": 10, "padding_y": 10,
"fontsize": 12, "fontsize": 12,
"language": "en" "language": "en"
} }
}, },
{ {
"name": "Agenda", "name": "Agenda",
"config": { "config": {
"size": [500, 800], "size": [500, 800],
"ical_urls": sample_url, "ical_urls": sample_url,
"ical_files": None, "ical_files": None,
"date_format": "ddd D MMM", "date_format": "ddd D MMM",
"time_format": "HH:mm", "time_format": "HH:mm",
"padding_x": 10, "padding_x": 10,
"padding_y": 10, "padding_y": 10,
"fontsize": 12, "fontsize": 12,
"language": "en" "language": "en"
} }
}, },
{ {
"position": 1, "position": 1,
"name": "Agenda", "name": "Agenda",
"config": { "config": {
"size": [300, 800], "size": [300, 800],
"ical_urls": sample_url, "ical_urls": sample_url,
"ical_files": None, "ical_files": None,
"date_format": "ddd D MMM", "date_format": "ddd D MMM",
"time_format": "HH:mm", "time_format": "HH:mm",
"padding_x": 10, "padding_x": 10,
"padding_y": 10, "padding_y": 10,
"fontsize": 12, "fontsize": 12,
"language": "en" "language": "en"
} }
}, },
] ]
class module_test(unittest.TestCase): class module_test(unittest.TestCase):
def test_get_config(self): def test_get_config(self):
print('getting data for web-ui...', end = "") print('getting data for web-ui...', end="")
Module.get_config() Module.get_config()
print('OK') print('OK')
def test_generate_image(self):
for test in tests:
print(f'test {tests.index(test) + 1} generating image..')
module = Module(test)
im_black, im_colour = module.generate_image()
print('OK')
if use_preview == True and environment == 'Raspberry':
preview(merge(im_black, im_colour))
def test_generate_image(self):
for test in tests:
print(f'test {tests.index(test)+1} generating image..')
module = Module(test)
im_black, im_colour = module.generate_image()
print('OK')
if use_preview == True and environment == 'Raspberry':
preview(merge(im_black, im_colour))
if __name__ == '__main__': if __name__ == '__main__':
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
logger = logging.getLogger() unittest.main()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
unittest.main()

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Calendar test (inkycal_calendar) Calendar test (inkycal_calendar)
@@ -9,84 +8,85 @@ Copyright by aceisace
import unittest import unittest
from inkycal.modules import Calendar as Module from inkycal.modules import Calendar as Module
from helper_functions import * from helper_functions import *
environment = get_environment() environment = get_environment()
# Set to True to preview images. Only works on Raspberry Pi OS with Desktop # Set to True to preview images. Only works on Raspberry Pi OS with Desktop
use_preview = False use_preview = False
sample_url = "https://www.officeholidays.com/ics-fed/usa" sample_url = "https://www.officeholidays.com/ics-fed/usa"
tests = [ tests = [
{ {
"name": "Calendar", "name": "Calendar",
"config": { "config": {
"size": [500, 500], "size": [500, 500],
"week_starts_on": "Monday", "week_starts_on": "Monday",
"show_events": True, "show_events": True,
"ical_urls": sample_url, "ical_urls": sample_url,
"ical_files": None, "ical_files": None,
"date_format": "D MMM", "time_format": "HH:mm", "date_format": "D MMM", "time_format": "HH:mm",
"padding_x": 10,"padding_y": 10,"fontsize": 12,"language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Calendar", "name": "Calendar",
"config": { "config": {
"size": [400, 800], "size": [400, 800],
"week_starts_on": "Sunday", "week_starts_on": "Sunday",
"show_events": True, "show_events": True,
"ical_urls": sample_url, "ical_urls": sample_url,
"ical_files": None, "ical_files": None,
"date_format": "D MMM", "time_format": "HH:mm", "date_format": "D MMM", "time_format": "HH:mm",
"padding_x": 10,"padding_y": 10,"fontsize": 12,"language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Calendar", "name": "Calendar",
"config": { "config": {
"size": [400, 800], "size": [400, 800],
"week_starts_on": "Monday", "week_starts_on": "Monday",
"show_events": False, "show_events": False,
"ical_urls": sample_url, "ical_urls": sample_url,
"ical_files": None, "ical_files": None,
"date_format": "D MMM", "time_format": "HH:mm", "date_format": "D MMM", "time_format": "HH:mm",
"padding_x": 10,"padding_y": 10,"fontsize": 12,"language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Calendar", "name": "Calendar",
"config": { "config": {
"size": [400, 800], "size": [400, 800],
"week_starts_on": "Monday", "week_starts_on": "Monday",
"show_events": True, "show_events": True,
"ical_urls": None, "ical_urls": None,
"ical_files": None, "ical_files": None,
"date_format": "D MMM", "time_format": "HH:mm", "date_format": "D MMM", "time_format": "HH:mm",
"padding_x": 10,"padding_y": 10,"fontsize": 12,"language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
] ]
class module_test(unittest.TestCase):
def test_get_config(self):
print('getting data for web-ui...', end = "")
Module.get_config()
print('OK')
def test_generate_image(self): class module_test(unittest.TestCase):
for test in tests: def test_get_config(self):
print(f'test {tests.index(test)+1} generating image..', end="") print('getting data for web-ui...', end="")
module = Module(test) Module.get_config()
im_black, im_colour = module.generate_image() print('OK')
print('OK')
if use_preview == True and environment == 'Raspberry': def test_generate_image(self):
preview(merge(im_black, im_colour)) for test in tests:
print(f'test {tests.index(test) + 1} generating image..', end="")
module = Module(test)
im_black, im_colour = module.generate_image()
print('OK')
if use_preview == True and environment == 'Raspberry':
preview(merge(im_black, im_colour))
if __name__ == '__main__': if __name__ == '__main__':
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
logger = logging.getLogger() unittest.main()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
unittest.main()

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Feeds test (inykcal_feeds) Feeds test (inykcal_feeds)
@@ -9,61 +8,62 @@ Copyright by aceisace
import unittest import unittest
from inkycal.modules import Feeds as Module from inkycal.modules import Feeds as Module
from helper_functions import * from helper_functions import *
environment = get_environment() environment = get_environment()
# Set to True to preview images. Only works on Raspberry Pi OS with Desktop # Set to True to preview images. Only works on Raspberry Pi OS with Desktop
use_preview = False use_preview = False
tests = [ tests = [
{ {
"name": "Feeds", "name": "Feeds",
"config": { "config": {
"size": [400,200], "size": [400, 200],
"feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#", "feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#",
"shuffle_feeds": True, "shuffle_feeds": True,
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Feeds", "name": "Feeds",
"config": { "config": {
"size": [400,100], "size": [400, 100],
"feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#", "feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#",
"shuffle_feeds": False, "shuffle_feeds": False,
"padding_x": 10, "padding_y": 10, "fontsize": 14, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 14, "language": "en"
} }
}, },
{ {
"name": "Feeds", "name": "Feeds",
"config": { "config": {
"size": [400,100], "size": [400, 100],
"feed_urls": "https://www.anekdot.ru/rss/export_top.xml", "feed_urls": "https://www.anekdot.ru/rss/export_top.xml",
"shuffle_feeds": False, "shuffle_feeds": False,
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
] ]
class module_test(unittest.TestCase):
def test_get_config(self):
print('getting data for web-ui...', end = "")
Module.get_config()
print('OK')
def test_generate_image(self): class module_test(unittest.TestCase):
for test in tests: def test_get_config(self):
print(f'test {tests.index(test)+1} generating image..') print('getting data for web-ui...', end="")
module = Module(test) Module.get_config()
im_black, im_colour = module.generate_image() print('OK')
print('OK')
if use_preview == True and environment == 'Raspberry': def test_generate_image(self):
preview(merge(im_black, im_colour)) for test in tests:
print(f'test {tests.index(test) + 1} generating image..')
module = Module(test)
im_black, im_colour = module.generate_image()
print('OK')
if use_preview == True and environment == 'Raspberry':
preview(merge(im_black, im_colour))
if __name__ == '__main__': if __name__ == '__main__':
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
logger = logging.getLogger() unittest.main()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
unittest.main()

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Image test (inkycal_image) Image test (inkycal_image)
@@ -10,113 +9,114 @@ import unittest
from inkycal.modules import Inkyimage as Module from inkycal.modules import Inkyimage as Module
from inkycal.custom import top_level from inkycal.custom import top_level
from helper_functions import * from helper_functions import *
environment = get_environment() environment = get_environment()
# Set to True to preview images. Only works on Raspberry Pi OS with Desktop # Set to True to preview images. Only works on Raspberry Pi OS with Desktop
use_preview = False use_preview = False
test_path = f'{top_level}/Gallery/coffee.png' test_path = f'{top_level}/Gallery/coffee.png'
tests = [ tests = [
{ {
"name": "Inkyimage", "name": "Inkyimage",
"config": { "config": {
"size": [400,200], "size": [400, 200],
"path": test_path, "path": test_path,
"palette": "bwr", "palette": "bwr",
"autoflip": True, "autoflip": True,
"orientation": "vertical", "orientation": "vertical",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Inkyimage", "name": "Inkyimage",
"config": { "config": {
"size": [800,500], "size": [800, 500],
"path": test_path, "path": test_path,
"palette": "bwy", "palette": "bwy",
"autoflip": True, "autoflip": True,
"orientation": "vertical", "orientation": "vertical",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Inkyimage", "name": "Inkyimage",
"config": { "config": {
"size": [400,100], "size": [400, 100],
"path": test_path, "path": test_path,
"palette": "bw", "palette": "bw",
"autoflip": False, "autoflip": False,
"orientation": "vertical", "orientation": "vertical",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Inkyimage", "name": "Inkyimage",
"config": { "config": {
"size": [400,100], "size": [400, 100],
"path": test_path, "path": test_path,
"palette": "bwr", "palette": "bwr",
"autoflip": True, "autoflip": True,
"orientation": "vertical", "orientation": "vertical",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Inkyimage", "name": "Inkyimage",
"config": { "config": {
"size": [400,100], "size": [400, 100],
"path": test_path, "path": test_path,
"palette": "bwy", "palette": "bwy",
"autoflip": True, "autoflip": True,
"orientation": "horizontal", "orientation": "horizontal",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Inkyimage", "name": "Inkyimage",
"config": { "config": {
"size": [500, 800], "size": [500, 800],
"path": test_path, "path": test_path,
"palette": "bw", "palette": "bw",
"autoflip": True, "autoflip": True,
"orientation": "vertical", "orientation": "vertical",
"padding_x": 0, "padding_y": 0, "fontsize": 12, "language": "en" "padding_x": 0, "padding_y": 0, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Inkyimage", "name": "Inkyimage",
"config": { "config": {
"size": [500, 800], "size": [500, 800],
"path": test_path, "path": test_path,
"palette": "bwr", "palette": "bwr",
"autoflip": True, "autoflip": True,
"orientation": "vertical", "orientation": "vertical",
"padding_x": 20, "padding_y": 20, "fontsize": 12, "language": "en" "padding_x": 20, "padding_y": 20, "fontsize": 12, "language": "en"
} }
}, },
] ]
class module_test(unittest.TestCase):
def test_get_config(self):
print('getting data for web-ui...', end = "")
Module.get_config()
print('OK')
def test_generate_image(self): class module_test(unittest.TestCase):
for test in tests: def test_get_config(self):
print(f'test {tests.index(test)+1} generating image..') print('getting data for web-ui...', end="")
module = Module(test) Module.get_config()
im_black, im_colour = module.generate_image() print('OK')
print('OK')
if use_preview == True and environment == 'Raspberry': def test_generate_image(self):
preview(merge(im_black, im_colour)) for test in tests:
print(f'test {tests.index(test) + 1} generating image..')
module = Module(test)
im_black, im_colour = module.generate_image()
print('OK')
if use_preview == True and environment == 'Raspberry':
preview(merge(im_black, im_colour))
if __name__ == '__main__': if __name__ == '__main__':
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
logger = logging.getLogger() unittest.main()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
unittest.main()

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Jokes test (inkycal_jokes) Jokes test (inkycal_jokes)

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Slideshow test (inkycal_slideshow) Slideshow test (inkycal_slideshow)

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Stocks test (inkycal_stocks) Stocks test (inkycal_stocks)
@@ -9,82 +8,83 @@ Copyright by aceisace
import unittest import unittest
from inkycal.modules import Stocks as Module from inkycal.modules import Stocks as Module
from helper_functions import * from helper_functions import *
environment = get_environment() environment = get_environment()
# Set to True to preview images. Only works on Raspberry Pi OS with Desktop # Set to True to preview images. Only works on Raspberry Pi OS with Desktop
use_preview = False use_preview = False
tests = [ tests = [
{ {
"name": "Stocks", "name": "Stocks",
"config": { "config": {
"size": [528, 30], "size": [528, 30],
"tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Stocks", "name": "Stocks",
"config": { "config": {
"size": [528, 50], "size": [528, 50],
"tickers": [], "tickers": [],
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Stocks", "name": "Stocks",
"config": { "config": {
"size": [528, 200], "size": [528, 200],
"tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Stocks", "name": "Stocks",
"config": { "config": {
"size": [528, 800], "size": [528, 800],
"tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Stocks", "name": "Stocks",
"config": { "config": {
"size": [528, 100], "size": [528, 100],
"tickers": "TSLA,AMD,NVDA,^DJI,BTC-USD,EURUSD=X", "tickers": "TSLA,AMD,NVDA,^DJI,BTC-USD,EURUSD=X",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
{ {
"name": "Stocks", "name": "Stocks",
"config": { "config": {
"size": [528, 400], "size": [528, 400],
"tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
"padding_x": 10, "padding_y": 10, "fontsize": 14, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 14, "language": "en"
} }
}, },
] ]
class module_test(unittest.TestCase):
def test_get_config(self):
print('getting data for web-ui...', end = "")
Module.get_config()
print('OK')
def test_generate_image(self): class module_test(unittest.TestCase):
for test in tests: def test_get_config(self):
print(f'test {tests.index(test)+1} generating image..') print('getting data for web-ui...', end="")
module = Module(test) Module.get_config()
im_black, im_colour = module.generate_image() print('OK')
print('OK')
if use_preview == True and environment == 'Raspberry': def test_generate_image(self):
preview(merge(im_black, im_colour)) for test in tests:
print(f'test {tests.index(test) + 1} generating image..')
module = Module(test)
im_black, im_colour = module.generate_image()
print('OK')
if use_preview == True and environment == 'Raspberry':
preview(merge(im_black, im_colour))
if __name__ == '__main__': if __name__ == '__main__':
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
logger = logging.getLogger() unittest.main()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
unittest.main()

View File

@@ -1,2 +0,0 @@
import unittest
from inkycal.modules import Todoist as Module

View File

@@ -1,3 +1,5 @@
#!python3
import unittest import unittest
from inkycal.modules import Todoist as Module from inkycal.modules import Todoist as Module
from helper_functions import * from helper_functions import *

View File

@@ -1,3 +1,5 @@
#!python3
import unittest import unittest
from inkycal.modules import Weather as Module from inkycal.modules import Weather as Module
from helper_functions import * from helper_functions import *

View File

@@ -1,5 +1,4 @@
#!/usr/bin/python3 #!python3
# -*- coding: utf-8 -*-
""" """
Main test (main) Main test (main)

View File

@@ -1,5 +1,4 @@
#!python3 #!python3
# -*- coding: utf-8 -*-
from setuptools import setup from setuptools import setup
from os import path from os import path