Folder restructuring

Reorganised folders and files
This commit is contained in:
Ace 2020-05-07 20:16:55 +02:00
parent 47a327cddc
commit fd9fcbcc8d
40 changed files with 625 additions and 1789 deletions

5
.gitignore.txt Normal file
View File

@ -0,0 +1,5 @@
*.pyc
__pycache__/
_build
_static
_templates

View File

@ -1,27 +0,0 @@
#!/bin/bash
# Script for updating the Inky-Calendar software. This will automatically
# transfer the user's own details with the placeholders in the settings.py file
# To-do: Delete the old settings.py file after all operations are done
in="/home/pi/settings.py.old"
out="/home/pi/Inky-Calendar/Calendar/settings.py"
# replace template iCalendar URLs with user-defined URLs
sed -n -e "/^ical_urls/r $in" -i -e "/^ical_urls/d" $out
sed -n -e "/^rss_feeds/r $in" -i -e "/^rss_feeds/d" $out
sed -n -e "/^update_interval/r $in" -i -e "/^update_interval/d" $out
sed -n -e "/^api_key/r $in" -i -e "/^api_key/d" $out
sed -n -e "/^location/r $in" -i -e "/^location/d" $out
sed -n -e "/^week_starts_on/r $in" -i -e "/^week_starts_on/d" $out
sed -n -e "/^calibration_hours/r $in" -i -e "/^calibration_hours/d" $out
sed -n -e "/^display_colours/r $in" -i -e "/^display_colours/d" $out
sed -n -e "/^language/r $in" -i -e "/^language/d" $out
sed -n -e "/^units/r $in" -i -e "/^units/d" $out
sed -n -e "/^hours/r $in" -i -e "/^hours/d" $out
sed -n -e "/^top_section/r $in" -i -e "/^top_section/d" $out
sed -n -e "/^middle_section/r $in" -i -e "/^middle_section/d" $out
sed -n -e "/^bottom_section/r $in" -i -e "/^bottom_section/d" $out
echo -e 'All operations done'

45
inkycal/Inkycal.py Normal file
View File

@ -0,0 +1,45 @@
from importlib import import_module
from inkycal.configuration.settings_parser import inkycal_settings as settings
from inkycal.display.layout import inkycal_layout as layout
##modules = settings.which_modules()
##for module in modules:
## if module == 'inkycal_rss':
## module = import_module('inkycal.modules.'+module)
## #import_module('modules.'+module)
##print(module)
settings_file = '/home/pi/Desktop/settings.json'
class inkycal:
def __init__(self, settings_file_path):
"""Load settings file from path"""
# Load settings file
self.settings = settings(settings_file_path)
self.model = self.settings.model
def create_canvas(self):
"""Create a canvas with same size as the specified model"""
self.layout = layout(model=self.model)
def create_custom_canvas(self, width=None, height=None,
supports_colour=False):
"""Create a custom canvas by specifying height and width"""
self.layout = layout(model=model, width=width, height=height,
supports_colour=supports_colour)
def create_sections(self):
"""Create sections with default sizes"""
self.layout.create_sections()
def create_custom_sections(self, top_section=0.10, middle_section=0.65,
bottom_section=0.25):
"""Create custom-sized sections in the canvas"""
self.layout.create_sections(top_section=top_section,
middle_section=middle_section,
bottom_section=bottom_section)

0
inkycal/__init__.py Normal file
View File

View File

@ -0,0 +1,2 @@
from .settings_parser import inkycal_settings
print('loaded settings')

View File

@ -0,0 +1,42 @@
{
"language": "en",
"units": "metric",
"hours": 24,
"model": "epd_7_in_5_v2_colour",
"update_interval": 60,
"calibration_hours": [
0,
12,
18
],
"display_orientation": "normal",
"panels": [
{
"location": "top",
"type": "inkycal_weather",
"config": {
"api_key": "topsecret",
"location": "Stuttgart, DE"
}
},
{
"location": "middle",
"type": "inkycal_calendar",
"config": {
"week_starts_on": "Monday",
"ical_urls": [
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
]
}
},
{
"location": "bottom",
"type": "inkycal_rss",
"config": {
"rss_urls": [
"http://feeds.bbci.co.uk/news/world/rss.xml#"
]
}
}
]
}

View File

@ -0,0 +1,137 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Json settings parser. Currently in alpha!
Copyright by aceisace
"""
import json
from os import chdir #Ad-hoc
# TODO:
# Check of jsmin can/should be used to parse jsonc settings file
# Remove check of fixed settings file location. Ask user to specify path
# to settings file
from os import path
class inkycal_settings:
"""Load and validate settings from the settings file"""
__supported_languages = ['en', 'de', 'ru', 'it', 'es', 'fr', 'el', 'sv', 'nl',
'pl', 'ua', 'nb', 'vi', 'zh_tw', 'zh-cn', 'ja', 'ko']
__supported_units = ['metric', 'imperial']
__supported_hours = [12, 24]
__supported_display_orientation = ['normal', 'upside_down']
__supported_models = [
'epd_7_in_5_v2_colour', 'epd_7_in_5_v2',
'epd_7_in_5_colour', 'epd_7_in_5',
'epd_5_in_83_colour','epd_5_in_83',
'epd_4_in_2_colour', 'epd_4_in_2'
]
def __init__(self, settings_file_path):
"""Load settings from path (folder or settings.json file)"""
try:
if settings_file_path.endswith('settings.json'):
folder = settings_file_path.split('/settings.json')[0]
else:
folder = settings_file_path
chdir(folder)
with open("settings.json") as file:
self.raw_settings = json.load(file)
except FileNotFoundError:
print('No settings file found in specified location')
try:
self.language = self.raw_settings['language']
if self.language not in self.__supported_languages or type(self.language) != str:
print('Unsupported language: {}!. Switching to english'.format(language))
self.language = 'en'
self.units = self.raw_settings['units']
if self.units not in self.__supported_units or type(self.units) != str:
print('Units ({}) not supported, using metric units.'.format(units))
self.units = 'metric'
self.hours = self.raw_settings['hours']
if self.hours not in self.__supported_hours or type(self.hours) != int:
print('Selected hours: {} not supported, using 24-hours'.format(hours))
self.hours = '24'
self.model = self.raw_settings['model']
if self.model not in self.__supported_models or type(self.model) != str:
print('Model: {} not supported. Please select a valid option'.format(model))
print('Switching to 7.5" ePaper black-white (v1) (fallback)')
self.model = 'epd_7_in_5'
self.calibration_hours = self.raw_settings['calibration_hours']
if not self.calibration_hours or type(self.calibration_hours) != list:
print('Invalid calibration hours: {}'.format(calibration_hours))
print('Using default option, 0am,12am,6pm')
self.calibration_hours = [0,12,18]
self.display_orientation = self.raw_settings['display_orientation']
if self.display_orientation not in self.__supported_display_orientation or type(
self.display_orientation) != str:
print('Invalid ({}) display orientation.'.format(display_orientation))
print('Switching to default orientation, normal-mode')
self.display_orientation = 'normal'
### Check if empty, If empty, set to none
for sections in self.raw_settings['panels']:
if sections['location'] == 'top':
self.top_section = sections['type']
self.top_section_config = sections['config']
elif sections['location'] == 'middle':
self.middle_section = sections['type']
self.middle_section_config = sections['config']
elif sections['location'] == 'bottom':
self.bottom_section = sections['type']
self.bottom_section_config = sections['config']
print('settings loaded')
except Exception as e:
print(e.reason)
def module_init(self, module_name):
"""Get all data from settings file by providing the module name"""
if module_name == self.top_section:
config = self.top_section_config
elif module_name == self.middle_section:
config = self.middle_section_config
elif module_name == self.bottom_section:
config = self.bottom_section_config
else:
print('Invalid module name!')
config = None
for module in self.raw_settings['panels']:
if module_name == module['type']:
location = module['location']
return config, location
def which_modules(self):
"""Returns a list of modules (from settings file) which should be loaded
on start"""
lst = [self.top_section, self.middle_section, self.bottom_section]
return lst
def main():
print('running settings parser as standalone...')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,2 @@
from .layout import inkycal_layout
print('imported layout class')

82
inkycal/display/layout.py Normal file
View File

@ -0,0 +1,82 @@
class inkycal_layout:
"""Page layout handling"""
def __init__(self, model=None, width=None, height=None,
supports_colour=False):
"""Initialize parameters for specified epaper model
Use model parameter to specify display OR
Crate a custom display with given width and height"""
self.background_colour = 'white' # Move to inkycal_rendering
self.text_colour = 'black' # Move to inkycal_rendering
if (model != None) and (width == None) and (height == None):
display_dimensions = {
'epd_7_in_5_v2_colour': (800, 400),
'epd_7_in_5_v2': (800, 400),
'epd_7_in_5_colour': (640, 384),
'epd_7_in_5': (640, 384),
'epd_5_in_83_colour': (600, 448),
'epd_5_in_83': (600, 448),
'epd_4_in_2_colour': (400, 300),
'epd_4_in_2': (400, 300),
}
self.display_height, self.display_width = display_dimensions[model]
if 'colour' in model:
self.three_colour_support = True
elif width and height:
self.display_height = width
self.display_width = height
self.supports_colour = supports_colour
else:
print("Can't create a layout without given sizes")
raise
self.__top_section_width = self.display_width
self.__middle_section_width = self.display_width
self.__bottom_section_width = self.display_width
self.create_sections()
def create_sections(self, top_section=0.10, middle_section=0.65,
bottom_section=0.25):
"""Allocate fixed percentage height for top and middle section
e.g. 0.2 = 20% (Leave empty for default values)
Set top/bottom_section to 0 to allocate more space for the middle section
"""
scale = lambda percentage: round(percentage * self.display_height)
if top_section == 0 or bottom_section == 0:
if top_section == 0:
self.__top_section_height = 0
if bottom_section == 0:
self.__bottom_section_height = 0
self.__middle_section_height = scale(1 - top_section - bottom_section)
else:
if top_section + middle_section + bottom_section > 1.0:
print('All percentages should add up to max 100%, not more!')
raise
self.__top_section_height = scale(top_section)
self.__middle_section_height = scale(middle_section)
self.__bottom_section_height = (self.display_height -
self.__top_section_height - self.__middle_section_height)
def get_section_size(self, section):
"""Enter top/middle/bottom to get the size of the section as a tuple:
(width, height)"""
if section not in ['top','middle','bottom']:
print('Invalid section: ', section)
raise
else:
if section == 'top':
size = (self.__top_section_width, self.__top_section_height)
elif section == 'middle':
size = (self.__middle_section_width, self.__middle_section_height)
elif section == 'bottom':
size = (self.__bottom_section_width, self.__bottom_section_height)
return size

View File

View File

View File

@ -0,0 +1,140 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
RSS module for Inky-Calendar Project
Copyright by aceisace
"""
from inkycal.render.functions import *
from random import shuffle
try:
import feedparser
except ImportError:
print('feedparser is not installed! Please install with:')
print('pipe install feedparser')
# Debug Data
size = (384, 160)
config = {'rss_urls': ['http://feeds.bbci.co.uk/news/world/rss.xml#']}
class inkycal_rss:
def __init__(self, section_size, section_config):
"""Initialize inkycal_rss module"""
self.name = os.path.basename(__file__).split('.py')[0]
self.config = section_config
self.width, self.height = section_size
self.background_colour = 'white'
self.font_colour = 'black'
self.fontsize = 12
self.font = ImageFont.truetype(
fonts['NotoSans-SemiCondensed'], size = self.fontsize)
self.padding_x = 0.02
self.padding_y = 0.05
print('{0} loaded'.format(self.name))
def set(self, **kwargs):
"""Manually set some parameters of this module"""
for key, value in kwargs.items():
if key in self.__dict__:
setattr(self, key, value)
else:
print('{0} does not exist'.format(key))
pass
def get(self, **kwargs):
"""Manually get some parameters of this module"""
for key, value in kwargs.items():
if key in self.__dict__:
getattr(self, key, value)
else:
print('{0} does not exist'.format(key))
pass
def get_options(self):
"""Get all options which can be changed"""
return self.__dict__
def generate_image(self):
"""Generate image for this module"""
# Define new image size with respect to padding
im_width = int(self.width - (self.width * 2 * self.padding_x))
im_height = int(self.height - (self.height * 2 * self.padding_y))
im_size = im_width, im_height
# Create an image for black pixels and one for coloured pixels
im_black = Image.new('RGB', size = im_size, color = self.background_colour)
im_colour = Image.new('RGB', size = im_size, color = 'white')
# Set some parameters for formatting rss feeds
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)]
if internet_available() == True:
print('Connection test passed')
else:
# write 'No network available :('
raise Exception('Network could not be reached :(')
try:
# Create list containing all rss-feeds from all rss-feed urls
parsed_feeds = []
for feeds in self.config['rss_urls']:
text = feedparser.parse(feeds)
for posts in text.entries:
parsed_feeds.append('{0}: {1}'.format(posts.title, posts.summary))
# print(parsed_feeds)
# Shuffle the list to prevent showing the same content
shuffle(parsed_feeds)
# Trim down the list to the max number of lines
del parsed_feeds[max_lines:]
# Wrap long text from feeds (line-breaking)
flatten = lambda z: [x for y in z for x in y]
filtered_feeds, counter = [], 0
for posts in parsed_feeds:
wrapped = text_wrap(posts, font = self.font, max_width = line_width)
counter += len(filtered_feeds) + len(wrapped)
if counter < max_lines:
filtered_feeds.append(wrapped)
filtered_feeds = flatten(filtered_feeds)
# Write rss-feeds on image
"""Write the correctly formatted text on the display"""
for _ in range(len(filtered_feeds)):
write(im_black, line_positions[_], (line_width, line_height),
filtered_feeds[_], font = self.font, alignment= 'left')
# Cleanup
del filtered_feeds, parsed_feeds, wrapped, counter, text
except Exception as e:
print('Error in {0}'.format(self.name))
print('Reason: ',e)
write(im_black, (0,0), (im_width, im_height), str(e), font = self.font)
# Save image of black and colour channel in image-folder
im_black.save(images+self.name+'.png')
im_colour.save(images+self.name+'_colour.png')
if __name__ == '__main__':
print('running {0} in standalone mode'.format(
os.path.basename(__file__).split('.py')[0]))

View File

@ -0,0 +1 @@
from .fonts import inkycal_fonts

165
inkycal/render/functions.py Normal file
View File

@ -0,0 +1,165 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Inky-Calendar custom-functions for ease-of-use
Copyright by aceisace
"""
from PIL import Image, ImageDraw, ImageFont, ImageColor
from urllib.request import urlopen
import os
import time
##from glob import glob
##import importlib
##import subprocess as subp
##import numpy
##import arrow
##from pytz import timezone
##"""Set some display parameters"""
##driver = importlib.import_module('drivers.'+model)
# Get the path to the Inky-Calendar folder
top_level = os.path.dirname(
os.path.abspath(os.path.dirname(__file__))).split('/inkycal')[0]
# Get path of 'fonts' and 'images' folders within Inky-Calendar folder
fonts_location = top_level + '/fonts/'
images = top_level + '/images/'
# Get available fonts within fonts folder
fonts = {}
for path,dirs,files in os.walk(fonts_location):
for filename in files:
if filename.endswith('.otf'):
name = filename.split('.otf')[0]
fonts[name] = os.path.join(path, filename)
if filename.endswith('.ttf'):
name = filename.split('.ttf')[0]
fonts[name] = os.path.join(path, filename)
del name, filename, files
available_fonts = [key for key,values in fonts.items()]
def get_fonts():
"""Print all available fonts by name"""
for fonts in available_fonts:
print(fonts)
def write(image, xy, box_size, text, font=None, **kwargs):
"""Write text on specified image
image = on which image should the text be added?
xy = xy-coordinates as tuple -> (x,y)
box_size = size of text-box -> (width,height)
text = string (what to write)
font = which font to use
"""
allowed_kwargs = ['alignment', 'autofit', 'colour', 'rotation'
'fill_width', 'fill_height']
alignment='center'
autofit = False
fill_width = 1.0
fill_height = 0.8
colour = 'black'
rotation = None
for key, value in kwargs.items():
if key in allowed_kwargs:
setattr(write, key, value)
else:
print('{0} does not exist'.format(key))
pass
x,y = xy
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, 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, 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
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]
# Align text to desired position
if alignment == "" or "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)
if rotation != None:
space.rotate(rotation, expand = True)
# Update only region with text (add text with transparent background)
image.paste(space, xy, space)
def text_wrap(text, font=None, max_width = None):
"""Split long text (text-wrapping). Returns a list"""
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():
"""check if the internet is available"""
try:
urlopen('https://google.com',timeout=5)
return True
except URLError as err:
return False
def get_system_tz():
"""Get the timezone set by the system"""
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

View File

@ -1 +0,0 @@
This is just a dummy file.

View File

@ -1 +0,0 @@
#nothing in here. What did you expect?

View File

@ -1,173 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
v1.7.2
Main file of Inky-Calendar software. Creates dynamic images for each section,
assembles them and sends it to the E-Paper
Copyright by aceisace
"""
from __future__ import print_function
from configuration import *
import arrow
from time import sleep
import gc
"""Perepare for execution of main programm"""
calibration_countdown = 'initial'
skip_calibration = False
upside_down = False
image_cleanup()
try:
top_section_module = importlib.import_module(top_section)
except ValueError:
print('Something went wrong while importing the top-section module:', top_section)
pass
try:
middle_section_module = importlib.import_module(middle_section)
except ValueError:
print('Something went wrong while importing the middle_section module', middle_section)
pass
try:
bottom_section_module = importlib.import_module(bottom_section)
except ValueError:
print('Something went wrong while importing the bottom_section module', bottom_section)
pass
"""Check time and calibrate display if time """
while True:
now = arrow.now(tz=get_tz())
for _ in range(1):
image = Image.new('RGB', (display_width, display_height), background_colour)
if three_colour_support == True:
image_col = Image.new('RGB', (display_width, display_height), 'white')
"""------------------Add short info------------------"""
print('Current Date: {0} \nCurrent Time: {1}'.format(now.format(
'D MMM YYYY'), now.format('HH:mm')))
print('-----------Main programm started now----------')
"""------------------Calibration check----------------"""
if skip_calibration != True:
print('Calibration..', end = ' ')
if now.hour in calibration_hours:
if calibration_countdown == 'initial':
print('required. Performing calibration now.')
calibration_countdown = 0
calibrate_display(3)
else:
if calibration_countdown % (60 // int(update_interval)) == 0:
calibrate_display(3)
calibration_countdown = 0
else:
print('not required. Continuing...')
else:
print('Calibration skipped!. Please note that not calibrating e-paper',
'displays causes ghosting')
"""----------------------top-section-image-----------------------------"""
try:
top_section_module.main()
top_section_image = Image.open(image_path + top_section+'.png')
image.paste(top_section_image, (0, 0))
if three_colour_support == True:
top_section_image_col = Image.open(image_path + top_section+'_col.png')
image_col.paste(top_section_image_col, (0, 0))
except Exception as error:
print(error)
pass
"""----------------------middle-section-image---------------------------"""
try:
middle_section_module.main()
middle_section_image = Image.open(image_path + middle_section+'.png')
image.paste(middle_section_image, (0, middle_section_offset))
if three_colour_support == True:
middle_section_image_col = Image.open(image_path + middle_section+'_col.png')
image_col.paste(middle_section_image_col, (0, middle_section_offset))
except Exception as error:
print(error)
pass
"""----------------------bottom-section-image---------------------------"""
try:
bottom_section_module.main()
bottom_section_image = Image.open(image_path + bottom_section+'.png')
image.paste(bottom_section_image, (0, bottom_section_offset))
if three_colour_support == True:
bottom_section_image_col = Image.open(image_path + bottom_section+'_col.png')
image_col.paste(bottom_section_image_col, (0, bottom_section_offset))
except Exception as error:
print(error)
pass
"""---------------------------------------------------------------------"""
if upside_down == True:
image = image.rotate(180, expand=True)
if three_colour_support == True:
image_col = image_col.rotate(180, expand=True)
image = optimise_colours(image)
image.save(image_path + 'canvas.png')
if three_colour_support == True:
image_col = optimise_colours(image_col)
image_col.save(image_path+'canvas_col.png')
"""---------Refreshing E-Paper with newly created image-----------"""
epaper = driver.EPD()
print('Initialising E-Paper...', end = '')
epaper.init()
print('Done')
if three_colour_support == True:
print('Sending image data and refreshing display...', end='')
epaper.display(epaper.getbuffer(image), epaper.getbuffer(image_col))
print('Done')
else:
print('Sending image data and refreshing display...', end='')
epaper.display(epaper.getbuffer(image))
print('Done')
print('Sending E-Paper to deep sleep...', end = '')
epaper.sleep()
print('Done')
"""--------------Post processing after main loop-----------------"""
"""Collect some garbage to free up some resources"""
gc.collect()
"""Adjust calibration countdowns"""
if calibration_countdown == 'initial':
calibration_countdown = 0
calibration_countdown += 1
"""Calculate duration until next display refresh"""
for _ in range(1):
update_timings = [(60 - int(update_interval)*updates) for updates in
range(60//int(update_interval))][::-1]
for _ in update_timings:
if now.minute <= _:
minutes = _ - now.minute
break
refresh_countdown = minutes*60 + (60 - now.second)
print('{0} Minutes left until next refresh'.format(minutes))
del update_timings, minutes, image
image_cleanup()
sleep(refresh_countdown)

View File

@ -1,156 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Agenda module for Inky-Calendar Project
Copyright by aceisace
"""
from __future__ import print_function
from inkycal_icalendar import fetch_events
from configuration import*
show_events = True
print_events = False
style = 'D MMM YY HH:mm'
all_day_str = 'All day'
"""Add a border to increase readability"""
border_top = int(middle_section_height * 0.02)
border_left = int(middle_section_width * 0.02)
"""Choose font optimised for the agenda section"""
font = ImageFont.truetype(NotoSans+'Medium.ttf', agenda_fontsize)
line_height = int(font.getsize('hg')[1] * 1.2) + 1
line_width = int(middle_section_width - (border_left*2))
"""Set some positions for events, dates and times"""
date_col_width = int(line_width * 0.20)
time_col_width = int(line_width * 0.15)
event_col_width = int(line_width - date_col_width - time_col_width)
date_col_start = border_left
time_col_start = date_col_start + date_col_width
event_col_start = time_col_start + time_col_width
"""Find max number of lines that can fit in the middle section and allocate
a position for each line"""
max_lines = int(middle_section_height+bottom_section_height -
(border_top * 2))// line_height
line_pos = [(border_left, int(top_section_height + border_top + line * line_height))
for line in range(max_lines)]
def generate_image():
if middle_section == 'inkycal_agenda' and internet_available() == True:
try:
clear_image('middle_section')
if not bottom_section:
clear_image('bottom_section')
print('Agenda module: Generating image...', end = '')
now = arrow.now(get_tz())
today_start = arrow.get(now.year, now.month, now.day)
"""Create a list of dictionaries containing dates of the next days"""
agenda_events = [{'date':today_start.replace(days=+_),
'date_str': now.replace(days=+_).format('ddd D MMM',locale=language),
'type':'date'} for _ in range(max_lines)]
"""Copy the list from the icalendar module with some conditions"""
upcoming_events = fetch_events()
filtered_events = [events for events in upcoming_events if
events.end > now]
"""Set print_events_to True to print all events in this month"""
if print_events == True and filtered_events:
auto_line_width = max(len(_.name) for _ in filtered_events)
for events in filtered_events:
print('{0} {1} | {2} | {3} | All day ='.format(events.name,
' '* (auto_line_width - len(events.name)), events.begin.format(style),
events.end.format(style)), events.all_day)
"""Convert the event-timings from utc to the specified locale's time
and create a ready-to-display list for the agenda view"""
for events in filtered_events:
if not events.all_day:
agenda_events.append({'date': events.begin, 'time': events.begin.format(
'HH:mm' if hours == '24' else 'hh:mm a'), 'name':str(events.name),
'type':'timed_event'})
else:
if events.duration.days == 1:
agenda_events.append({'date': events.begin,'time': all_day_str,
'name': events.name,'type':'full_day_event'})
else:
for day in range(events.duration.days):
agenda_events.append({'date': events.begin.replace(days=+day),
'time': all_day_str,'name':events.name, 'type':'full_day_event'})
"""Sort events and dates in chronological order"""
agenda_events = sorted(agenda_events, key = lambda event: event['date'])
"""Crop the agenda_events in case it's too long"""
del agenda_events[max_lines:]
"""Display all events, dates and times on the display"""
if show_events == True:
previous_date = None
for events in range(len(agenda_events)):
if agenda_events[events]['type'] == 'date':
if previous_date == None or previous_date != agenda_events[events][
'date']:
write_text(date_col_width, line_height,
agenda_events[events]['date_str'], line_pos[events], font = font)
previous_date = agenda_events[events]['date']
if three_colour_support == True:
draw_col.line((date_col_start, line_pos[events][1],
line_width,line_pos[events][1]), fill = 'black')
else:
draw.line((date_col_start, line_pos[events][1],
line_width,line_pos[events][1]), fill = 'black')
elif agenda_events[events]['type'] == 'timed_event':
write_text(time_col_width, line_height, agenda_events[events]['time'],
(time_col_start, line_pos[events][1]), font = font)
write_text(event_col_width, line_height, (''+agenda_events[events][
'name']), (event_col_start, line_pos[events][1]),
alignment = 'left', font = font)
else:
write_text(time_col_width, line_height, agenda_events[events]['time'],
(time_col_start, line_pos[events][1]), font = font)
write_text(event_col_width, line_height, (''+agenda_events[events]['name']),
(event_col_start, line_pos[events][1]), alignment = 'left', font = font)
"""Crop the image to show only the middle section"""
agenda_image = image.crop((0,middle_section_offset,display_width, display_height))
agenda_image.save(image_path+'inkycal_agenda.png')
if three_colour_support == True:
agenda_image_col = image_col.crop((0,middle_section_offset,display_width, display_height))
agenda_image_col.save(image_path+'inkycal_agenda_col.png')
print('Done')
except Exception as e:
"""If something went wrong, print a Error message on the Terminal"""
print('Failed!')
print('Error in Agenda module!')
print('Reason: ',e)
clear_image('middle_section')
write_text(middle_section_width, middle_section_height, str(e),
(0, middle_section_offset), font = font)
calendar_image = crop_image(image, 'middle_section')
calendar_image.save(image_path+'inkycal_agenda.png')
pass
def main():
generate_image()
main()

View File

@ -1,232 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Calendar module for Inky-Calendar Project
Copyright by aceisace
"""
from __future__ import print_function
import calendar
from configuration import *
print_events = False
show_events = True
today_in_your_language = 'today'
tomorrow_in_your_language = 'tomorrow'
at_in_your_language = 'at'
event_icon = 'square' # dot #square
style = "DD MMM"
font = ImageFont.truetype(NotoSans+'.ttf', calendar_fontsize)
space_between_lines = 0
if show_events == True:
from inkycal_icalendar import fetch_events
"""Add a border to increase readability"""
border_top = int(middle_section_height * 0.02)
border_left = int(middle_section_width * 0.02)
main_area_height = middle_section_height-border_top*2
main_area_width = middle_section_width-border_left*2
line_height = font.getsize('hg')[1] + space_between_lines
line_width = middle_section_width - (border_left*2)
"""Calculate height for each sub-section"""
month_name_height = int(main_area_height*0.1)
weekdays_height = int(main_area_height*0.05)
calendar_height = int(main_area_height*0.6)
events_height = int(main_area_height*0.25)
"""Set rows and coloumns in the calendar section and calculate sizes"""
calendar_rows, calendar_coloumns = 6, 7
icon_width = main_area_width // calendar_coloumns
icon_height = calendar_height // calendar_rows
"""Calculate paddings for calendar section"""
x_padding_calendar = int((main_area_width % icon_width) / 2)
y_padding_calendar = int((main_area_height % calendar_rows) / 2)
"""Add coordinates for number icons inside the calendar section"""
grid_start_y = (middle_section_offset + border_top + month_name_height +
weekdays_height + y_padding_calendar)
grid_start_x = border_left + x_padding_calendar
grid = [(grid_start_x + icon_width*x, grid_start_y + icon_height*y)
for y in range(calendar_rows) for x in range(calendar_coloumns)]
weekday_pos = [(grid_start_x + icon_width*_, middle_section_offset +
month_name_height) for _ in range(calendar_coloumns)]
max_event_lines = (events_height - border_top) // (font.getsize('hg')[1]
+ space_between_lines)
event_lines = [(border_left,(bottom_section_offset - events_height)+
int(events_height/max_event_lines*_)) for _ in range(max_event_lines)]
def generate_image():
if middle_section == "inkycal_calendar" and internet_available() == True:
try:
clear_image('middle_section')
print('Calendar module: Generating image...', end = '')
now = arrow.now(tz = get_tz())
"""Set up the Calendar template based on personal preferences"""
if week_starts_on == "Monday":
calendar.setfirstweekday(calendar.MONDAY)
weekstart = now.replace(days = - now.weekday())
else:
calendar.setfirstweekday(calendar.SUNDAY)
weekstart = now.replace(days = - now.isoweekday())
"""Write the name of the current month at the correct position"""
write_text(main_area_width, month_name_height,
str(now.format('MMMM',locale=language)), (border_left,
middle_section_offset), autofit = True)
"""Set up weeknames in local language and add to main section"""
weekday_names = [weekstart.replace(days=+_).format('ddd',locale=language)
for _ in range(7)]
for _ in range(len(weekday_pos)):
write_text(icon_width, weekdays_height, weekday_names[_],
weekday_pos[_], autofit = True)
"""Create a calendar template and flatten (remove nestings)"""
flatten = lambda z: [x for y in z for x in y]
calendar_flat = flatten(calendar.monthcalendar(now.year, now.month))
"""Add the numbers on the correct positions"""
for i in range(len(calendar_flat)):
if calendar_flat[i] not in (0, int(now.day)):
write_text(icon_width, icon_height, str(calendar_flat[i]), grid[i])
"""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[calendar_flat.index(now.day)]
x_circle,y_circle = int(icon_width/2), int(icon_height/2)
radius = int(icon_width * 0.25)
text_width, text_height = default.getsize(str(now.day))
x_text = int((icon_width / 2) - (text_width / 2))
y_text = int((icon_height / 2) - (text_height / 1.7))
ImageDraw.Draw(icon).ellipse((x_circle-radius, y_circle-radius,
x_circle+radius, y_circle+radius), fill= 'black', outline=None)
ImageDraw.Draw(icon).text((x_text, y_text), str(now.day), fill='white',
font=bold)
if three_colour_support == True:
image_col.paste(icon, current_day_pos, icon)
else:
image.paste(icon, current_day_pos, icon)
"""Create some reference points for the current month"""
days_current_month = calendar.monthrange(now.year, now.month)[1]
month_start = now.floor('month')
month_end = now.ceil('month')
if show_events == True:
"""Filter events which begin before the end of this month"""
upcoming_events = fetch_events()
calendar_events = [events for events in upcoming_events if
month_start <= events.end <= month_end ]
"""Find days with events in the current month"""
days_with_events = []
for events in calendar_events:
if events.duration.days <= 1:
days_with_events.append(int(events.begin.format('D')))
else:
for day in range(events.duration.days):
days_with_events.append(
int(events.begin.replace(days=+i).format('D')))
days_with_events = set(days_with_events)
if event_icon == 'dot':
for days in days_with_events:
write_text(icon_width, int(icon_height * 0.2), '',
(grid[calendar_flat.index(days)][0],
int(grid[calendar_flat.index(days)][1] + icon_height*0.8)))
if event_icon == 'square':
square_size = int(icon_width * 0.6)
center_x = int((icon_width - square_size) / 2)
center_y = int((icon_height - square_size) / 2)
for days in days_with_events:
draw_square((int(grid[calendar_flat.index(days)][0]+center_x),
int(grid[calendar_flat.index(days)][1] + center_y )),
8, square_size , square_size, colour='black')
"""Add a small section showing events of today and tomorrow"""
event_list = []
after_two_days = now.replace(days=2).floor('day')
for event in calendar_events:
if event.begin.day == now.day and now < event.end:
if event.all_day:
event_list.append('{}: {}'.format(today_in_your_language, event.name))
else:
event_list.append('{0} {1} {2} : {3}'.format(today_in_your_language,
at_in_your_language, event.begin.format('HH:mm' if hours == '24' else
'hh:mm a'), event.name))
elif event.begin.day == now.replace(days=1).day:
if event.all_day:
event_list.append('{}: {}'.format(tomorrow_in_your_language, event.name))
else:
event_list.append('{0} {1} {2} : {3}'.format(tomorrow_in_your_language,
at_in_your_language, event.begin.format('HH:mm' if hours == '24' else
'hh:mm a'), event.name))
elif event.begin > after_two_days:
if event.all_day:
event_list.append('{}: {}'.format(event.begin.format('D MMM'), event.name))
else:
event_list.append('{0} {1} {2} : {3}'.format(event.begin.format('D MMM'),
at_in_your_language, event.begin.format('HH:mm' if hours == '24' else
'hh:mm a'), event.name))
del event_list[max_event_lines:]
if event_list:
for lines in event_list:
write_text(main_area_width, int(events_height/max_event_lines), lines,
event_lines[event_list.index(lines)], font=font, alignment='left')
else:
write_text(main_area_width, int(events_height/max_event_lines),
'No upcoming events.', event_lines[0], font=font, alignment='left')
"""Set print_events_to True to print all events in this month"""
style = 'DD MMM YY HH:mm'
if print_events == True and calendar_events:
line_width = max(len(_.name) for _ in calendar_events)
for events in calendar_events:
print('{0} {1} | {2} | {3} | All day ='.format(events.name,
' ' * (line_width - len(events.name)), events.begin.format(style),
events.end.format(style)), events.all_day)
calendar_image = crop_image(image, 'middle_section')
calendar_image.save(image_path+'inkycal_calendar.png')
if three_colour_support == True:
calendar_image_col = crop_image(image_col, 'middle_section')
calendar_image_col.save(image_path+'inkycal_calendar_col.png')
print('Done')
except Exception as e:
"""If something went wrong, print a Error message on the Terminal"""
print('Failed!')
print('Error in Calendar module!')
print('Reason: ',e)
clear_image('middle_section')
write_text(middle_section_width, middle_section_height, str(e),
(0, middle_section_offset), font = font)
calendar_image = crop_image(image, 'middle_section')
calendar_image.save(image_path+'inkycal_calendar.png')
pass
def main():
generate_image()
main()

View File

@ -1,95 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
iCalendar (parsing) module for Inky-Calendar Project
Copyright by aceisace
"""
from __future__ import print_function
from configuration import *
from settings import ical_urls
import arrow
from ics import Calendar
use_recurring_events = True
print_events = False
style = 'DD MMM YY HH:mm'
if use_recurring_events == True:
from dateutil.rrule import rrulestr, rruleset
import re
def fetch_events():
"""Set timelines for filtering upcoming events"""
timezone = get_tz()
now = arrow.now(tz=timezone)
beginning_of_month = now.replace(days= - now.day +1)
near_future = now.replace(days= 30)
further_future = now.replace(days=40)
"""Parse the iCalendars from the urls, fixing some known errors with ics"""
calendars = [Calendar(fix_ical(url)) for url in ical_urls]
"""Filter any upcoming events from all iCalendars and add them to a list"""
upcoming_events = [events for ical in calendars for events in ical.events
if beginning_of_month <= events.end <= further_future or
beginning_of_month <= events.begin <= near_future]
"""Try to parse recurring events. This is clearly experimental! """
if use_recurring_events == True:
for ical in calendars:
for events in ical.events:
event_str = str(events)
if re.search('RRULE:(.+?)\n', event_str):
if events.all_day and events.duration.days > 1:
events.end = events.end.replace(days=-2)
else:
events.end = events.end.to(timezone)
events.begin = events.begin.to(timezone)
try:
rule = re.search('RRULE:(.+?)\n', event_str).group(0)[:-2]
if re.search('UNTIL=(.+?);', rule) and not re.search('UNTIL=(.+?)Z;', rule):
rule = re.sub('UNTIL=(.+?);', 'UNTIL='+re.search('UNTIL=(.+?);', rule).group(0)[6:-1]+'T000000Z;', rule)
dates = rrulestr(rule, dtstart= events.begin.datetime).between(after= now.datetime, before = further_future.datetime)
if dates:
duration = events.duration
for date in dates:
cc = events.clone()
cc.end = arrow.get(date+duration)
cc.begin = arrow.get(date)
upcoming_events.append(cc)
#print("Added '{}' with new start at {}".format(cc.name, cc.begin.format('DD MMM YY')))
except Exception as e:
print('Something went wrong while parsing recurring events')
pass
"""Sort events according to their beginning date"""
def sort_dates(event):
return event.begin
upcoming_events.sort(key=sort_dates)
"""Multiday events are displayed incorrectly; fix that"""
for events in upcoming_events:
if events.all_day and events.duration.days > 1:
events.end = events.end.replace(days=-2)
events.make_all_day()
if not events.all_day:
events.end = events.end.to(timezone)
events.begin = events.begin.to(timezone)
""" The list upcoming_events should not be modified. If you need the data from
this one, copy the list or the contents to another one."""
#print(upcoming_events) # Print all events. Might look a bit messy
"""Print upcoming events in a more appealing way"""
if print_events == True and upcoming_events:
line_width = max(len(i.name) for i in upcoming_events)
for events in upcoming_events:
print('{0} {1} | {2} | {3} | All day ='.format(events.name,
' '* (line_width - len(events.name)), events.begin.format(style),
events.end.format(style)), events.all_day)
return upcoming_events

View File

@ -1,179 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Experimental image module for Inky-Calendar software
Displays an image on the E-Paper. Work in progress!
Copyright by aceisace
"""
from __future__ import print_function
from configuration import *
from os import path
from PIL import ImageOps
import requests
import numpy
"""----------------------------------------------------------------"""
#path = 'https://github.com/aceisace/Inky-Calendar/raw/master/Gallery/Inky-Calendar-logo.png'
#path ='/home/pi/Inky-Calendar/images/canvas.png'
path = inkycal_image_path
path_body = inkycal_image_path_body
mode = 'auto' # 'horizontal' # 'vertical' # 'auto'
upside_down = True # Flip image by 180 deg (upside-down)
alignment = 'center' # top_center, top_left, center_left, bottom_right etc.
colours = 'bwr' # bwr # bwy # bw
render = True # show image on E-Paper?
"""----------------------------------------------------------------"""
# First determine dimensions
if mode == 'horizontal':
display_width, display_height == display_height, display_width
if mode == 'vertical':
pass
# .. Then substitute possibly parameterized path
# TODO Get (assigned) panel dimensions instead of display dimensions
path = path.replace('{model}', model).replace('{width}',str(display_width)).replace('{height}',str(display_height))
"""Try to open the image if it exists and is an image file"""
try:
if 'http' in path:
if path_body is None:
# Plain GET
im = Image.open(requests.get(path, stream=True).raw)
else:
# POST request, passing path_body in the body
im = Image.open(requests.post(path, json=path_body, stream=True).raw)
else:
im = Image.open(path)
except FileNotFoundError:
print('Your file could not be found. Please check the path to your file.')
raise
except OSError:
print('Please check if the path points to an image file.')
raise
"""Turn image upside-down if specified"""
if upside_down == True:
im.rotate(180, expand = True)
if mode == 'auto':
if (im.width > im.height) and (display_width < display_height):
print('display vertical, image horizontal -> flipping image')
im = im.rotate(90, expand=True)
if (im.width < im.height) and (display_width > display_height):
print('display horizontal, image vertical -> flipping image')
im = im.rotate(90, expand=True)
def fit_width(image, width):
"""Resize an image to desired width"""
print('resizing width from', image.width, 'to', end = ' ')
wpercent = (display_width/float(image.width))
hsize = int((float(image.height)*float(wpercent)))
img = image.resize((width, hsize), Image.ANTIALIAS)
print(img.width)
return img
def fit_height(image, height):
"""Resize an image to desired height"""
print('resizing height from', image.height, 'to', end = ' ')
hpercent = (height / float(image.height))
wsize = int(float(image.width) * float(hpercent))
img = image.resize((wsize, height), Image.ANTIALIAS)
print(img.height)
return img
if im.width > display_width:
im = fit_width(im, display_width)
if im.height > display_height:
im = fit_height(im, display_height)
if alignment == 'center':
x,y = int((display_width-im.width)/2), int((display_height-im.height)/2)
elif alignment == 'center_right':
x, y = display_width-im.width, int((display_height-im.height)/2)
elif alignment == 'center_left':
x, y = 0, int((display_height-im.height)/2)
elif alignment == 'top_center':
x, y = int((display_width-im.width)/2), 0
elif alignment == 'top_right':
x, y = display_width-im.width, 0
elif alignment == 'top_left':
x, y = 0, 0
elif alignment == 'bottom_center':
x, y = int((display_width-im.width)/2), display_height-im.height
elif alignment == 'bottom_right':
x, y = display_width-im.width, display_height-im.height
elif alignment == 'bottom_left':
x, y = display_width-im.width, display_height-im.height
if len(im.getbands()) == 4:
print('removing transparency')
bg = Image.new('RGBA', (im.width, im.height), 'white')
im = Image.alpha_composite(bg, im)
image.paste(im, (x,y))
im = image
if colours == 'bw':
"""For black-white images, use monochrome dithering"""
black = im.convert('1', dither=True)
elif colours == 'bwr':
"""For black-white-red images, create corresponding palette"""
pal = [255,255,255, 0,0,0, 255,0,0, 255,255,255]
elif colours == 'bwy':
"""For black-white-yellow images, create corresponding palette"""
pal = [255,255,255, 0,0,0, 255,255,0, 255,255,255]
"""Map each pixel of the opened image to the Palette"""
if colours != 'bw':
palette_im = Image.new('P', (3,1))
palette_im.putpalette(pal * 64)
quantized_im = im.quantize(palette=palette_im)
quantized_im.convert('RGB')
"""Create buffer for coloured pixels"""
buffer1 = numpy.array(quantized_im.convert('RGB'))
r1,g1,b1 = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2]
"""Create buffer for black pixels"""
buffer2 = numpy.array(quantized_im.convert('RGB'))
r2,g2,b2 = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2]
if colours == 'bwr':
"""Create image for only red pixels"""
buffer2[numpy.logical_and(r2 == 0, b2 == 0)] = [255,255,255] # black->white
buffer2[numpy.logical_and(r2 == 255, b2 == 0)] = [0,0,0] #red->black
colour = Image.fromarray(buffer2)
"""Create image for only black pixels"""
buffer1[numpy.logical_and(r1 == 255, b1 == 0)] = [255,255,255]
black = Image.fromarray(buffer1)
if colours == 'bwy':
"""Create image for only yellow pixels"""
buffer2[numpy.logical_and(r2 == 0, b2 == 0)] = [255,255,255] # black->white
buffer2[numpy.logical_and(g2 == 255, b2 == 0)] = [0,0,0] #yellow -> black
colour = Image.fromarray(buffer2)
"""Create image for only black pixels"""
buffer1[numpy.logical_and(g1 == 255, b1 == 0)] = [255,255,255]
black = Image.fromarray(buffer1)
if render == True:
epaper = driver.EPD()
print('Initialising E-Paper...', end = '')
epaper.init()
print('Done')
print('Sending image data and refreshing display...', end='')
if three_colour_support == True:
epaper.display(epaper.getbuffer(black), epaper.getbuffer(colour))
else:
epaper.display(epaper.getbuffer(black))
print('Done')
print('Sending E-Paper to deep sleep...', end = '')
epaper.sleep()
print('Done')

View File

@ -1,95 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
RSS module for Inky-Calendar software.
Copyright by aceisace
"""
from __future__ import print_function
import feedparser
from random import shuffle
from configuration import *
"""Add a border to increase readability"""
border_top = int(bottom_section_height * 0.05)
border_left = int(bottom_section_width * 0.02)
"""Choose font optimised for the weather section"""
font = ImageFont.truetype(NotoSans+'.ttf', rss_fontsize)
space_between_lines = 1
line_height = font.getsize('hg')[1] + space_between_lines
line_width = bottom_section_width - (border_left*2)
"""Find out how many lines can fit at max in the bottom section"""
max_lines = (bottom_section_height - (border_top*2)) // (font.getsize('hg')[1]
+ space_between_lines)
"""Calculate the height padding so the lines look centralised"""
y_padding = int( (bottom_section_height % line_height) / 2 )
"""Create a list containing positions of each line"""
line_positions = [(border_left, bottom_section_offset +
border_top + y_padding + _*line_height ) for _ in range(max_lines)]
def generate_image():
if bottom_section == "inkycal_rss" and rss_feeds != [] and internet_available() == True:
try:
clear_image('bottom_section')
print('RSS module: Connectivity check passed. Generating image...',
end = '')
"""Parse the RSS-feed titles & summaries and save them to a list"""
parsed_feeds = []
for feeds in rss_feeds:
text = feedparser.parse(feeds)
for posts in text.entries:
parsed_feeds.append('{0}: {1}'.format(posts.title, posts.summary))
"""Shuffle the list, then crop it to the max number of lines"""
shuffle(parsed_feeds)
del parsed_feeds[max_lines:]
"""Check the lenght of each feed. Wrap the text if it doesn't fit on one line"""
flatten = lambda z: [x for y in z for x in y]
filtered_feeds, counter = [], 0
for posts in parsed_feeds:
wrapped = text_wrap(posts, font = font, line_width = line_width)
counter += len(filtered_feeds) + len(wrapped)
if counter < max_lines:
filtered_feeds.append(wrapped)
filtered_feeds = flatten(filtered_feeds)
"""Write the correctly formatted text on the display"""
for _ in range(len(filtered_feeds)):
write_text(line_width, line_height, filtered_feeds[_],
line_positions[_], font = font, alignment= 'left')
del filtered_feeds, parsed_feeds
rss_image = crop_image(image, 'bottom_section')
rss_image.save(image_path+'inkycal_rss.png')
if three_colour_support == True:
rss_image_col = crop_image(image_col, 'bottom_section')
rss_image_col.save(image_path+'inkycal_rss_col.png')
print('Done')
except Exception as e:
"""If something went wrong, print a Error message on the Terminal"""
print('Failed!')
print('Error in RSS module!')
print('Reason: ',e)
clear_image('bottom_section')
write_text(bottom_section_width, bottom_section_height, str(e),
(0, bottom_section_offset), font = font)
rss = crop_image(image, 'bottom_section')
rss.save(image_path+'inkycal_rss.png')
pass
def main():
generate_image()
main()

View File

@ -1,362 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Weather module for Inky-Calendar software.
The lunar phase calculation is from Sean B. Palmer, inamidst.com.
Thank You Palmer for the awesome code!
Copyright by aceisace
"""
from __future__ import print_function
import pyowm
from configuration import *
import math, decimal
dec = decimal.Decimal
"""Optional parameters"""
round_temperature = True
round_windspeed = True
use_beaufort = True
show_wind_direction = False
use_wind_direction_icon = False
now_str = 'now'
"""Set the optional parameters"""
decimal_places_temperature = None if round_temperature == True else 1
decimal_places_windspeed = None if round_windspeed == True else 1
print('Initialising weather...', end=' ')
owm = pyowm.OWM(api_key, language=language)
print('Done')
"""Icon-code to unicode dictionary for weather-font"""
weathericons = {
'01d': '\uf00d', '02d': '\uf002', '03d': '\uf013',
'04d': '\uf012', '09d': '\uf01a', '10d': '\uf019',
'11d': '\uf01e', '13d': '\uf01b', '50d': '\uf014',
'01n': '\uf02e', '02n': '\uf013', '03n': '\uf013',
'04n': '\uf013', '09n': '\uf037', '10n': '\uf036',
'11n': '\uf03b', '13n': '\uf038', '50n': '\uf023'
}
"""Add a border to increase readability"""
border_top = int(top_section_height * 0.05)
border_left = int(top_section_width * 0.02)
"""Calculate size for each weather sub-section"""
row_height = (top_section_height-(border_top*2)) // 3
coloumn_width = (top_section_width-(border_left*2)) // 7
"""Calculate paddings"""
x_padding = int( (top_section_width % coloumn_width) / 2 )
y_padding = int( (top_section_height % row_height) / 2 )
"""Allocate sizes for weather icons"""
icon_small = row_height
icon_medium = row_height * 2
"""Calculate the x-axis position of each coloumn"""
coloumn1 = x_padding
coloumn2 = coloumn1 + coloumn_width
coloumn3 = coloumn2 + coloumn_width
coloumn4 = coloumn3 + coloumn_width
coloumn5 = coloumn4 + coloumn_width
coloumn6 = coloumn5 + coloumn_width
coloumn7 = coloumn6 + coloumn_width
"""Calculate the y-axis position of each row"""
row1 = y_padding
row2 = row1 + row_height
row3 = row2 + row_height
"""Allocate positions for current weather details"""
text_now_pos = (coloumn1, row1)
weather_icon_now_pos = (coloumn1, row2)
temperature_icon_now_pos = (coloumn2, row1)
temperature_now_pos = (coloumn2+icon_small, row1)
humidity_icon_now_pos = (coloumn2, row2)
humidity_now_pos = (coloumn2+icon_small, row2)
windspeed_icon_now_pos = (coloumn2, row3)
windspeed_now_pos = (coloumn2+icon_small, row3)
moon_phase_now_pos = (coloumn3, row1)
sunrise_icon_now_pos = (coloumn3, row2)
sunrise_time_now_pos = (coloumn3+icon_small, row2)
sunset_icon_now_pos = (coloumn3, row3)
sunset_time_now_pos = (coloumn3+ icon_small, row3)
"""Allocate positions for weather forecast after 3 hours"""
text_fc1_pos = (coloumn4, row1)
icon_fc1_pos = (coloumn4, row2)
temperature_fc1_pos = (coloumn4, row3)
"""Allocate positions for weather forecast after 6 hours"""
text_fc2_pos = (coloumn5, row1)
icon_fc2_pos = (coloumn5, row2)
temperature_fc2_pos = (coloumn5, row3)
"""Allocate positions for weather forecast after 9 hours"""
text_fc3_pos = (coloumn6, row1)
icon_fc3_pos = (coloumn6, row2)
temperature_fc3_pos = (coloumn6, row3)
"""Allocate positions for weather forecast after 12 hours"""
text_fc4_pos = (coloumn7, row1)
icon_fc4_pos = (coloumn7, row2)
temperature_fc4_pos = (coloumn7, row3)
"""Windspeed (m/s) to beaufort (index of list) conversion"""
windspeed_to_beaufort = [0.02, 1.5, 3.3, 5.4, 7.9, 10.7, 13.8, 17.1, 20.7,
24.4, 28.4, 32.6, 100]
def to_units(kelvin):
"""Function to convert tempertures from kelvin to celcius or fahrenheit"""
degrees_celsius = round(kelvin - 273.15, ndigits = decimal_places_temperature)
fahrenheit = round((kelvin - 273.15) * 9/5 + 32,
ndigits = decimal_places_temperature)
if units == 'metric':
conversion = str(degrees_celsius) + '°C'
if units == 'imperial':
conversion = str(fahrenheit) + 'F'
return conversion
def red_temp(negative_temperature):
if three_colour_support == True and negative_temperature[0] == '-' and units == 'metric':
colour = 'red'
else:
colour = 'black'
return colour
"""Function to convert time objects to specified format 12/24 hours"""
"""Simple means just the hour and if 12 hours, am/pm as well"""
def to_hours(datetime_object, simple = False):
if hours == '24':
if simple == True:
converted_time = datetime_object.format('H') + '.00'
else:
converted_time = datetime_object.format('HH:mm')
else:
if simple == True:
converted_time = datetime_object.format('H a')
else:
converted_time = datetime_object.format('hh:mm')
return str(converted_time)
"""Choose font optimised for the weather section"""
fontsize = 8
font = ImageFont.truetype(NotoSans+'Medium.ttf', fontsize)
fill_height = 0.8
while font.getsize('hg')[1] <= (row_height * fill_height):
fontsize += 1
font = ImageFont.truetype(NotoSans+'.ttf', fontsize)
def generate_image():
"""Connect to Openweathermap API and fetch weather data"""
if top_section == "inkycal_weather" and api_key != "" and owm.is_API_online() is True:
try:
clear_image('top_section')
print('Weather module: Connectivity check passed, Generating image...',
end = '')
current_weather_setup = owm.weather_at_place(location)
weather = current_weather_setup.get_weather()
"""Set-up and get weather forecast data"""
forecast = owm.three_hours_forecast(location)
"""Round the hour to the nearest multiple of 3"""
now = arrow.utcnow()
if (now.hour % 3) != 0:
hour_gap = 3 - (now.hour % 3)
else:
hour_gap = 3
"""Prepare timings for forecasts"""
fc1 = now.replace(hours = + hour_gap).floor('hour')
fc2 = now.replace(hours = + hour_gap + 3).floor('hour')
fc3 = now.replace(hours = + hour_gap + 6).floor('hour')
fc4 = now.replace(hours = + hour_gap + 9).floor('hour')
"""Prepare forecast objects for the specified timings"""
forecast_fc1 = forecast.get_weather_at(fc1.datetime)
forecast_fc2 = forecast.get_weather_at(fc2.datetime)
forecast_fc3 = forecast.get_weather_at(fc3.datetime)
forecast_fc4 = forecast.get_weather_at(fc4.datetime)
"""Get the current temperature and forcasts temperatures"""
temperature_now = to_units(weather.get_temperature()['temp'])
temperature_fc1 = to_units(forecast_fc1.get_temperature()['temp'])
temperature_fc2 = to_units(forecast_fc2.get_temperature()['temp'])
temperature_fc3 = to_units(forecast_fc3.get_temperature()['temp'])
temperature_fc4 = to_units(forecast_fc4.get_temperature()['temp'])
"""Get current and forecast weather icon names"""
weather_icon_now = weather.get_weather_icon_name()
weather_icon_fc1 = forecast_fc1.get_weather_icon_name()
weather_icon_fc2 = forecast_fc2.get_weather_icon_name()
weather_icon_fc3 = forecast_fc3.get_weather_icon_name()
weather_icon_fc4 = forecast_fc4.get_weather_icon_name()
"""Parse current weather details"""
sunrise_time_now = arrow.get(weather.get_sunrise_time()).to(get_tz())
sunset_time_now = arrow.get(weather.get_sunset_time()).to(get_tz())
humidity_now = str(weather.get_humidity())
cloudstatus_now = str(weather.get_clouds())
weather_description_now = str(weather.get_detailed_status())
windspeed_now = weather.get_wind(unit='meters_sec')['speed']
wind_degrees = forecast_fc1.get_wind()['deg']
wind_direction = ["N","NE","E","SE","S","SW","W","NW"][round(
wind_degrees/45) % 8]
if use_beaufort == True:
wind = str([windspeed_to_beaufort.index(_) for _ in
windspeed_to_beaufort if windspeed_now < _][0])
else:
meters_sec = round(windspeed_now, ndigits = windspeed_decimal_places)
miles_per_hour = round(windspeed_now * 2,23694,
ndigits = windspeed_decimal_places)
if units == 'metric':
wind = str(meters_sec) + 'm/s'
if units == 'imperial':
wind = str(miles_per_hour) + 'mph'
if show_wind_direction == True:
wind += '({0})'.format(wind_direction)
"""Calculate the moon phase"""
def get_moon_phase():
diff = now - arrow.get(2001, 1, 1)
days = dec(diff.days) + (dec(diff.seconds) / dec(86400))
lunations = dec("0.20439731") + (days * dec("0.03386319269"))
position = lunations % dec(1)
index = math.floor((position * dec(8)) + dec("0.5"))
return {0: '\uf095',1: '\uf099',2: '\uf09c',3: '\uf0a0',
4: '\uf0a3',5: '\uf0a7',6: '\uf0aa',7: '\uf0ae' }[int(index) & 7]
moonphase = get_moon_phase()
"""Add weather details in column 1"""
write_text(coloumn_width, row_height, now_str, text_now_pos, font = font)
write_text(icon_medium, icon_medium, weathericons[weather_icon_now],
weather_icon_now_pos, font = w_font, fill_width = 0.9)
"""Add weather details in column 2"""
write_text(icon_small, icon_small, '\uf053', temperature_icon_now_pos,
font = w_font, fill_height = 0.9)
write_text(icon_small, icon_small, '\uf07a', humidity_icon_now_pos,
font = w_font, fill_height = 0.9)
if use_wind_direction_icon == False:
write_text(icon_small, icon_small, '\uf050', windspeed_icon_now_pos,
font = w_font, fill_height = 0.9)
else:
write_text(icon_small, icon_small, '\uf0b1', windspeed_icon_now_pos,
font = w_font, fill_height = 0.9, rotation = -wind_degrees)
write_text(coloumn_width-icon_small, row_height, temperature_now,
temperature_now_pos, font = font, colour= red_temp(temperature_now))
write_text(coloumn_width-icon_small, row_height, humidity_now+'%',
humidity_now_pos, font = font)
write_text(coloumn_width-icon_small, row_height, wind,
windspeed_now_pos, font = font, autofit = True)
"""Add weather details in column 3"""
write_text(coloumn_width, row_height, moonphase , moon_phase_now_pos,
font = w_font, fill_height = 0.9)
write_text(icon_small, icon_small, '\uf051', sunrise_icon_now_pos,
font = w_font, fill_height = 0.9)
write_text(icon_small, icon_small, '\uf052', sunset_icon_now_pos,
font = w_font, fill_height = 0.9)
write_text(coloumn_width-icon_small, row_height,
to_hours(sunrise_time_now), sunrise_time_now_pos, font = font,
fill_width = 0.9)
write_text(coloumn_width-icon_small, row_height,
to_hours(sunset_time_now), sunset_time_now_pos, font = font,
fill_width = 0.9)
"""Add weather details in column 4 (forecast 1)"""
write_text(coloumn_width, row_height, to_hours(fc1, simple=True),
text_fc1_pos, font = font)
write_text(coloumn_width, row_height, weathericons[weather_icon_fc1],
icon_fc1_pos, font = w_font, fill_height = 1.0)
write_text(coloumn_width, row_height, temperature_fc1,
temperature_fc1_pos, font = font, colour = red_temp(
temperature_fc1))
"""Add weather details in column 5 (forecast 2)"""
write_text(coloumn_width, row_height, to_hours(fc2, simple=True),
text_fc2_pos, font = font)
write_text(coloumn_width, row_height, weathericons[weather_icon_fc2],
icon_fc2_pos, font = w_font, fill_height = 1.0)
write_text(coloumn_width, row_height, temperature_fc2,
temperature_fc2_pos, font = font, colour = red_temp(
temperature_fc2))
"""Add weather details in column 6 (forecast 3)"""
write_text(coloumn_width, row_height, to_hours(fc3, simple=True),
text_fc3_pos, font = font)
write_text(coloumn_width, row_height, weathericons[weather_icon_fc3],
icon_fc3_pos, font = w_font, fill_height = 1.0)
write_text(coloumn_width, row_height, temperature_fc3,
temperature_fc3_pos, font = font, colour = red_temp(
temperature_fc3))
"""Add weather details in coloumn 7 (forecast 4)"""
write_text(coloumn_width, row_height, to_hours(fc4, simple=True),
text_fc4_pos, font = font)
write_text(coloumn_width, row_height, weathericons[weather_icon_fc4],
icon_fc4_pos, font = w_font, fill_height = 1.0)
write_text(coloumn_width, row_height, temperature_fc4,
temperature_fc4_pos, font = font, colour = red_temp(
temperature_fc4))
"""Add vertical lines between forecast sections"""
draw = ImageDraw.Draw(image)
line_start_y = int(top_section_height*0.1)
line_end_y = int(top_section_height*0.9)
draw.line((coloumn4, line_start_y, coloumn4, line_end_y), fill='black')
draw.line((coloumn5, line_start_y, coloumn5, line_end_y), fill='black')
draw.line((coloumn6, line_start_y, coloumn6, line_end_y), fill='black')
draw.line((coloumn7, line_start_y, coloumn7, line_end_y), fill='black')
if three_colour_support == True:
draw_col.line((0, top_section_height-border_top, top_section_width-
border_left, top_section_height-border_top), fill='black', width=3)
else:
draw.line((0, top_section_height-border_top, top_section_width-
border_left, top_section_height-border_top), fill='black', width=3)
weather_image = crop_image(image, 'top_section')
weather_image.save(image_path+'inkycal_weather.png')
if three_colour_support == True:
weather_image_col = crop_image(image_col, 'top_section')
weather_image_col.save(image_path+'inkycal_weather_col.png')
print('Done')
except Exception as e:
"""If something went wrong, print a Error message on the Terminal"""
print('Failed!')
print('Error in weather module!')
print('Reason: ',e)
clear_image('top_section')
write_text(top_section_width, top_section_height, str(e),
(0, 0), font = font)
weather_image = crop_image(image, 'top_section')
weather_image.save(image_path+'inkycal_weather.png')
pass
def main():
generate_image()
main()

View File

@ -1 +0,0 @@
v1.7.2

View File

@ -1,5 +1,6 @@
pyowm==2.10.0
Pillow==6.2.0
ics==0.4
Pillow==7.1.1
ics==0.7
feedparser==5.2.1
pytz==2019.3
python-dateutil==2.6.1
numpy==1.18.2

View File

@ -1,306 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Advanced configuration options for Inky-Calendar software.
Contains some useful functions for correctly rendering text,
calibrating (E-Paper display), checking internet connectivity
Copyright by aceisace
"""
from PIL import Image, ImageDraw, ImageFont, ImageColor
import numpy
import arrow
from urllib.request import urlopen
from settings import *
from pytz import timezone
import os
from glob import glob
import importlib
import subprocess as subp
"""Set the image background colour and text colour"""
background_colour = 'white'
text_colour = 'black'
"""Set some display parameters"""
driver = importlib.import_module('drivers.'+model)
display_height, display_width = driver.EPD_WIDTH, driver.EPD_HEIGHT
"""Check if the display supports 3 colours"""
if 'colour' in model:
three_colour_support = True
else:
three_colour_support = False
"""Create 3 sections of the display, based on percentage"""
top_section_width = middle_section_width = bottom_section_width = display_width
if top_section and bottom_section:
top_section_height = int(display_height*0.11)
bottom_section_height = int(display_height*0.24)
elif top_section and not bottom_section:
top_section_height = int(display_height*0.11)
bottom_section_height = 0
elif bottom_section and not top_section:
top_section_height = 0
bottom_section_height = int(display_height*0.24)
elif not top_section and not bottom_section:
top_section_height = bottom_section_height = 0
middle_section_height = int(display_height - top_section_height -
bottom_section_height)
"""Find out the y-axis position of each section"""
top_section_offset = 0
middle_section_offset = top_section_height
bottom_section_offset = display_height - bottom_section_height
"""Get the relative path of the Inky-Calendar folder"""
path = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
if path != "" and path[-1] != "/":
path += "/"
while not path.endswith('/Inky-Calendar/'):
path = ''.join(list(path)[:-1])
"""Select path for saving temporary image files"""
image_path = path + 'images/'
"""Fonts handling"""
fontpath = path+'fonts/'
NotoSansCJK = fontpath+'NotoSansCJK/NotoSansCJKsc-'
NotoSans = fontpath+'NotoSans/NotoSans-SemiCondensed'
weatherfont = fontpath+'WeatherFont/weathericons-regular-webfont.ttf'
"""Fontsizes"""
default_fontsize = 18
agenda_fontsize = 14
calendar_fontsize = 14
rss_fontsize = 14
weather_fontsize = 12
"""Automatically select correct fonts to support set language"""
if language in ['ja','zh','zh_tw','ko']:
default = ImageFont.truetype(NotoSansCJK+'Regular.otf', default_fontsize)
semi = ImageFont.truetype(NotoSansCJK+'Medium.otf', default_fontsize)
bold = ImageFont.truetype(NotoSansCJK+'Bold.otf', default_fontsize)
else:
default = ImageFont.truetype(NotoSans+'.ttf', default_fontsize)
semi = ImageFont.truetype(NotoSans+'Medium.ttf', default_fontsize)
bold = ImageFont.truetype(NotoSans+'SemiBold.ttf', default_fontsize)
w_font = ImageFont.truetype(weatherfont, weather_fontsize)
"""Create a blank image for black pixels and a colour image for coloured pixels"""
image = Image.new('RGB', (display_width, display_height), background_colour)
image_col = Image.new('RGB', (display_width, display_height), 'white')
draw = ImageDraw.Draw(image)
draw_col = ImageDraw.Draw(image_col)
"""Custom function to add text on an image"""
def write_text(space_width, space_height, text, tuple,
font=default, alignment='middle', autofit = False, fill_width = 1.0,
fill_height = 0.8, colour = text_colour, rotation = None):
"""tuple refers to (x,y) position on display"""
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(space_width * fill_width) and text_height < int(space_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]
while (text_width, text_height) > (space_width, space_height):
text=text[0:-1]
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
if alignment is "" or "middle" or None:
x = int((space_width / 2) - (text_width / 2))
if alignment is 'left':
x = 0
if font != w_font:
y = int((space_height / 2) - (text_height / 1.7))
else:
y = y = int((space_height / 2) - (text_height / 2))
space = Image.new('RGBA', (space_width, space_height))
ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font)
if rotation != None:
space.rotate(rotation, expand = True)
if colour == 'black' or 'white':
image.paste(space, tuple, space)
else:
image_col.paste(space, tuple, space)
def clear_image(section, colour = background_colour):
"""Clear the image"""
width, height = eval(section+'_width'), eval(section+'_height')
position = (0, eval(section+'_offset'))
box = Image.new('RGB', (width, height), colour)
image.paste(box, position)
if three_colour_support == True:
image_col.paste(box, position)
def crop_image(input_image, section):
"""Crop an input image to the desired section"""
x1, y1 = 0, eval(section+'_offset')
x2, y2 = eval(section+'_width'), y1 + eval(section+'_height')
image = input_image.crop((x1,y1,x2,y2))
return image
def text_wrap(text, font=default, line_width = display_width):
"""Split long text into smaller lists"""
counter, padding = 0, 40
lines = []
if font.getsize(text)[0] < line_width:
lines.append(text)
else:
for i in range(1, len(text.split())+1):
line = ' '.join(text.split()[counter:i])
if not font.getsize(line)[0] < line_width - padding:
lines.append(line)
line, counter = '', i
if i == len(text.split()) and line != '':
lines.append(line)
return lines
def draw_square(tuple, radius, width, height, colour=text_colour, line_width=1):
"""Draws a square with round corners at position (x,y) from tuple"""
x, y, diameter = tuple[0], tuple[1], radius*2
line_length = width - diameter
p1, p2 = (x+radius, y), (x+radius+line_length, y)
p3, p4 = (x+width, y+radius), (x+width, y+radius+line_length)
p5, p6 = (p2[0], y+height), (p1[0], y+height)
p7, p8 = (x, p4[1]), (x,p3[1])
c1, c2 = (x,y), (x+diameter, y+diameter)
c3, c4 = ((x+width)-diameter, y), (x+width, y+diameter)
c5, c6 = ((x+width)-diameter, (y+height)-diameter), (x+width, y+height)
c7, c8 = (x, (y+height)-diameter), (x+diameter, y+height)
if three_colour_support == True:
draw_col.line( (p1, p2) , fill=colour, width = line_width)
draw_col.line( (p3, p4) , fill=colour, width = line_width)
draw_col.line( (p5, p6) , fill=colour, width = line_width)
draw_col.line( (p7, p8) , fill=colour, width = line_width)
draw_col.arc( (c1, c2) , 180, 270, fill=colour, width=line_width)
draw_col.arc( (c3, c4) , 270, 360, fill=colour, width=line_width)
draw_col.arc( (c5, c6) , 0, 90, fill=colour, width=line_width)
draw_col.arc( (c7, c8) , 90, 180, fill=colour, width=line_width)
else:
draw.line( (p1, p2) , fill=colour, width = line_width)
draw.line( (p3, p4) , fill=colour, width = line_width)
draw.line( (p5, p6) , fill=colour, width = line_width)
draw.line( (p7, p8) , fill=colour, width = line_width)
draw.arc( (c1, c2) , 180, 270, fill=colour, width=line_width)
draw.arc( (c3, c4) , 270, 360, fill=colour, width=line_width)
draw.arc( (c5, c6) , 0, 90, fill=colour, width=line_width)
draw.arc( (c7, c8) , 90, 180, fill=colour, width=line_width)
def internet_available():
"""check if the internet is available"""
try:
urlopen('https://google.com',timeout=5)
return True
except URLError as err:
return False
def get_tz():
"""Get the system timezone"""
with open('/etc/timezone','r') as file:
lines = file.readlines()
system_tz = lines[0].rstrip()
local_tz = timezone(system_tz)
return local_tz
def fix_ical(ical_url):
"""Use iCalendars in compatability mode (without alarms)"""
ical = str(urlopen(ical_url).read().decode())
beginAlarmIndex = 0
while beginAlarmIndex >= 0:
beginAlarmIndex = ical.find('BEGIN:VALARM')
if beginAlarmIndex >= 0:
endAlarmIndex = ical.find('END:VALARM')
ical = ical[:beginAlarmIndex] + ical[endAlarmIndex+12:]
return ical
def image_cleanup():
"""Delete all files in the image folder"""
print('Cleanup of previous images...', end = '')
for temp_files in glob(image_path+'*'):
os.remove(temp_files)
print('Done')
def optimise_colours(image, threshold=220):
buffer = numpy.array(image.convert('RGB'))
red, green = buffer[:, :, 0], buffer[:, :, 1]
buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0,0,0] #grey->black
image = Image.fromarray(buffer)
return image
def calibrate_display(no_of_cycles):
"""How many times should each colour be calibrated? Default is 3"""
epaper = driver.EPD()
epaper.init()
white = Image.new('1', (display_width, display_height), 'white')
black = Image.new('1', (display_width, display_height), 'black')
print('----------Started calibration of E-Paper display----------')
if 'colour' in model:
for _ in range(no_of_cycles):
print('Calibrating...', end= ' ')
print('black...', end= ' ')
epaper.display(epaper.getbuffer(black), epaper.getbuffer(white))
print('colour...', end = ' ')
epaper.display(epaper.getbuffer(white), epaper.getbuffer(black))
print('white...')
epaper.display(epaper.getbuffer(white), epaper.getbuffer(white))
print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles))
else:
for _ in range(no_of_cycles):
print('Calibrating...', end= ' ')
print('black...', end = ' ')
epaper.display(epaper.getbuffer(black))
print('white...')
epaper.display(epaper.getbuffer(white)),
print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles))
print('-----------Calibration complete----------')
epaper.sleep()
def check_for_updates():
with open(path+'release.txt','r') as file:
lines = file.readlines()
installed_release = lines[0].rstrip()
temp = subp.check_output(['curl','-s','https://github.com/aceisace/Inky-Calendar/releases/latest'])
latest_release_url = str(temp).split('"')[1]
latest_release = latest_release_url.split('/tag/')[1]
def get_id(version):
if not version.startswith('v'):
print('incorrect release format!')
v = ''.join(version.split('v')[1].split('.'))
if len(v) == 2:
v += '0'
return int(v)
if get_id(installed_release) < get_id(latest_release):
print('New update available!. Please update to the latest version')
print('current release:', installed_release, 'new version:', latest_release)
else:
print('You are using the latest version of the Inky-Calendar software:', end = ' ')
print(installed_release)

View File

@ -1 +0,0 @@
#nothing in here. What did you expect?

View File

@ -1,91 +0,0 @@
{
"language" : "en", // "en", "de", "fr", "jp" etc.
"units" : "metric", // "metric", "imperial"
"hours" : 24, // 24, 12
"model" : "epd_7_in_5_v2_colour", // For supported E-paper models, see below
"update_interval" : 60, // 10, 15, 20, 30, 60
"calibration_hours" : [0,12,18], // Do not change unlesss you know what you are doing
//For now three panels can be defined for three unique locations: 'top', 'middle' and 'bottom'
"panels" : [
{
"location" : "top",
"type" : "inkycal_weather",
"config" : {
"api_key" : "", //Your openweathermap API-KEY -> "api-key"
"location" : "Stuttgart, DE" //"City name, Country code"
}
},
{
"location" : "middle",
"type" : "inkycal_calendar", // "inkycal_calendar" and "inkycal_agenda" have the same parameters, but are displayed differently
"config" : {
"week_starts_on" : "Monday", //"Sunday", "Monday"...
"ical_urls" : [
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics",
"https://www.calendarlabs.com/ical-calendar/ics/101/Netherlands_Holidays.ics"
]
}
},
{
"location" : "bottom",
"type" : "inkycal_rss",
"config" : {
"rss_feeds" : [
"http://feeds.bbci.co.uk/news/world/rss.xml#",
"https://github.com/aceisace/Inky-Calendar/releases.atom"
]
}
},
{
"location" : "middle",
"type" : "inkycal_image",
"config" : {
/*
The url or file path to obtain the image from.
The following parameters within accolades ({}) will be substituted:
- model
- width
- height
Samples
The inkycal logo:
inkycal_image_path = 'https://github.com/aceisace/Inky-Calendar/raw/master/Gallery/Inky-Calendar-logo.png'
A dynamic image with a demo-calendar
inkycal_image_path = 'https://inkycal.robertsirre.nl/panel/test/{model}/image?width={width}&height={height}'
Dynamic image with configurable calendars (see https://inkycal.robertsirre.nl/ and parameter inkycal_image_path_body)
inkycal_image_path = 'https://inkycal.robertsirre.nl/panel/calendar/{model}?width={width}&height={height}'
inkycal_image_path ='/home/pi/Inky-Calendar/images/canvas.png'
*/
"image_path" : "https://github.com/aceisace/Inky-Calendar/raw/master/Gallery/Inky-Calendar-logo.png",
/*
Optional: inkycal_image_path_body
Allows obtaining complexer configure images.
When `inkycal_image_path` starts with `http` and `inkycal_image_path_body` is specified, the image is obtained using POST instead of GET.
NOTE: structure of the body depends on the web-based image service
*/
// inkycal_image_path_body = [
// 'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics',
// 'https://www.calendarlabs.com/ical-calendar/ics/101/Netherlands_Holidays.ics'
// ]
}
}
]
}
/*
Supported E-Paper models"""
epd_7_in_5_v2_colour # 7.5" high-res black-white-red/yellow
epd_7_in_5_v2 # 7.5" high-res black-white
epd_7_in_5_colour # 7.5" black-white-red/yellow
epd_7_in_5 # 7.5" black-white
epd_5_in_83_colour # 5.83" black-white-red/yellow
epd_5_in_83 # 5.83" black-white
epd_4_in_2_colour # 4.2" black-white-red/yellow
epd_4_in_2 # 4.2" black-white
*/

View File

@ -1,66 +0,0 @@
ical_urls = ["https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"] #iCal URLs
rss_feeds = ["http://feeds.bbci.co.uk/news/world/rss.xml#"] # Use any RSS feed
update_interval = "60" # "10" # "15" # "20" # "30" # "60"
api_key = "" # Your openweathermap API-KEY -> "api-key"
location = "Stuttgart, DE" # "City name, Country code"
week_starts_on = "Monday" # 'Sunday' # Monday
calibration_hours = [0,12,18] # Do not change unlesss you know what you are doing
model = "epd_7_in_5_v2_colour" # Choose the E-Paper model (see below)
language = "en" # "en" # "de" # "fr" # "jp" etc.
units = "metric" # "metric" # "imperial"
hours = "24" # "24" # "12"
top_section = "inkycal_weather" # "inkycal_weather"
middle_section = "inkycal_calendar" # "inkycal_agenda" #"inkycal_calendar"
bottom_section = "inkycal_rss" # "inkycal_rss"
"""Adding multiple iCalendar URLs or RSS feed URLs"""
# Single URL:
# ical_urls/rss_feeds = ["url1"]
# Multiple URLs:
# ical_urls/rss_feeds = ["url1", "url2", "url3"]
# URLs should have this sign (") on both side -> "url1"
# If more than one URL is used, separate each one with a comma -> "url1", "url2"
########################
# inkycal_image config:
#
# inkycal_image_path
# The url or file path to obtain the image from.
# The following parameters within accolades ({}) will be substituted:
# - model
# - width
# - height
#
# Samples :
# The inkycal logo:
# inkycal_image_path = 'https://github.com/aceisace/Inky-Calendar/raw/master/Gallery/Inky-Calendar-logo.png'
#
# A dynamic image with a demo-calendar
# inkycal_image_path = 'https://inkycal.robertsirre.nl/panel/test/{model}/image?width={width}&height={height}'
#
# Dynamic image with configurable calendars (see https://inkycal.robertsirre.nl/ and parameter inkycal_image_path_body)
# inkycal_image_path = 'https://inkycal.robertsirre.nl/panel/calendar/{model}?width={width}&height={height}'
inkycal_image_path ='/home/pi/Inky-Calendar/images/canvas.png'
# Optional: inkycal_image_path_body
# Allows obtaining complexer configure images.
# When inkycal_image_path starts with `http` and inkycal_image_path_body is specified, the image is obtained using POST instead of GET.
# NOTE: structure of the body depends on the web-based image service
# inkycal_image_path_body = [
# 'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics',
# 'https://www.calendarlabs.com/ical-calendar/ics/101/Netherlands_Holidays.ics'
# ]
########################
"""Supported E-Paper models"""
# epd_7_in_5_v2_colour # 7.5" high-res black-white-red/yellow
# epd_7_in_5_v2 # 7.5" high-res black-white
# epd_7_in_5_colour # 7.5" black-white-red/yellow
# epd_7_in_5 # 7.5" black-white
# epd_5_in_83_colour # 5.83" black-white-red/yellow
# epd_5_in_83 # 5.83" black-white
# epd_4_in_2_colour # 4.2" black-white-red/yellow
# epd_4_in_2 # 4.2" black-white