Improved formatting
This commit is contained in:
@@ -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
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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 ###
|
||||||
|
@@ -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 ###
|
||||||
|
|
||||||
|
@@ -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 ###
|
||||||
|
@@ -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 ###
|
||||||
|
|
||||||
|
@@ -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 ###
|
||||||
|
|
||||||
|
@@ -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 ###
|
||||||
|
|
||||||
|
@@ -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 ###
|
||||||
|
|
||||||
|
@@ -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 ###
|
||||||
|
|
||||||
|
@@ -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 ###
|
||||||
|
@@ -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 ###
|
||||||
|
@@ -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 ###
|
||||||
|
@@ -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
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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()
|
|
||||||
|
@@ -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()
|
|
||||||
|
@@ -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()
|
|
||||||
|
@@ -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()
|
|
||||||
|
|
||||||
|
@@ -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()
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Jokes test (inkycal_jokes)
|
Jokes test (inkycal_jokes)
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Slideshow test (inkycal_slideshow)
|
Slideshow test (inkycal_slideshow)
|
||||||
|
@@ -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()
|
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
import unittest
|
|
||||||
from inkycal.modules import Todoist as Module
|
|
@@ -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 *
|
||||||
|
@@ -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 *
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Main test (main)
|
Main test (main)
|
||||||
|
Reference in New Issue
Block a user