Work in progress for release v1.7/1.8

This is a refactoring of the entire Inky-Calendar software and is work in progress. The reason for uploading is to test if everything works fine. Please do not attempt to use/install this software as it can potentially break your system. If you have any improvement ideas, you're most welcome to mention them in the Issues section. Thanks!
This commit is contained in:
Ace 2019-10-21 07:54:19 +02:00 committed by GitHub
parent 93426b3dea
commit e3a4997fdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1118 additions and 0 deletions

View File

@ -0,0 +1,92 @@
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,11 @@
This package is part of the noto project. Visit
google.com/get/noto for more information.
Built on 2017-10-24 from the following noto repository:
-----
Repo: noto-fonts
Tag: v2017-10-24-phase3-second-cleanup
Date: 2017-10-24 12:10:34 GMT
Commit: 8ef14e6c606a7a0ef3943b9ca01fd49445620d79
Remove some files that aren't for release.

View File

@ -0,0 +1,92 @@
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,11 @@
This package is part of the noto project. Visit
google.com/get/noto for more information.
Built on 2017-10-24 from the following noto repository:
-----
Repo: noto-cjk
Tag: v2017-06-01-serif-cjk-1-1
Date: 2017-09-20 09:49:40 GMT
Commit: 32a5844539f2e348ed36b44e990f9b06d7fb89fe
Update serif CJK to 1.1.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,41 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Calibration module for the Black-White and Black-White-Red E-Paper display
Calibration refers to flushing all pixels in a single colour to prevent
ghosting.
"""
from __future__ import print_function
import time
from settings import display_colours
from image_data import black, white, red
def calibration():
"""Function for Calibration"""
import e_paper_drivers
epd = e_paper_drivers.EPD()
print('_________Calibration for E-Paper started_________'+'\n')
for i in range(2):
epd.init()
print('Calibrating black...')
epd.display_frame(epd.get_frame_buffer(black))
if display_colours == "bwr":
print('calibrating red...')
epd.display_frame(epd.get_frame_buffer(red))
print('Calibrating white...')
epd.display_frame(epd.get_frame_buffer(white))
epd.sleep()
print('Cycle', str(i+1)+'/2', 'complete'+'\n')
print('Calibration complete')
def main():
"""Added timer"""
start = time.time()
calibration()
end = time.time()
print('Calibration complete in', int(end - start), 'seconds')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,171 @@
"""
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
from urllib.request import urlopen
from settings import language
from pytz import timezone
import numpy as np
import os
"""Set the display height and width (in pixels)"""
display_height, display_width = 640, 384
"""Create 3 sections of the display, based on percentage"""
top_section_width = middle_section_width = bottom_section_width = display_width
top_section_height = int(display_height*0.10)
middle_section_height = int(display_height*0.65)
bottom_section_height = int(display_height - middle_section_height -
top_section_height)
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])
"""Fonts handling"""
fontpath = path+'fonts/'
NotoSansCJK = fontpath+'NotoSansCJK/NotoSansCJKsc-'
NotoSans = fontpath+'NotoSans/NotoSans-SemiCondensed'
weatherfont = fontpath+'WeatherFont/weathericons-regular-webfont.ttf'
if language in ['ja','zh','zh_tw','ko']:
default = ImageFont.truetype(NotoSansCJK+'Light.otf', 18)
semi = ImageFont.truetype(NotoSansCJK+'DemiLight.otf', 18)
bold = ImageFont.truetype(NotoSansCJK+'Regular.otf', 18)
month_font = ImageFont.truetype(NotoSansCJK+'DemiLight.otf', 40)
else:
default = ImageFont.truetype(NotoSans+'Light.ttf', 18)
semi = ImageFont.truetype(NotoSans+'.ttf', 18)
bold = ImageFont.truetype(NotoSans+'Medium.ttf', 18)
month_font = ImageFont.truetype(NotoSans+'Light.ttf', 40)
w_font = ImageFont.truetype(weatherfont, 10)
x_padding = int((display_width % 10) // 2)
line_height = default.getsize('hg')[1]
line_width = display_width- x_padding
image = Image.new('RGB', (display_width, display_height), 'white')
#def main():
def write_text(box_width, box_height, text, tuple,
font=default, alignment='middle', adapt_fontsize = False):
text_width, text_height = font.getsize(text)
if adapt_fontsize == True:
size = 10
while text_width < box_width and text_height < box_height:
size += 1
font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getsize(text)
while (text_width, text_height) > (box_width, box_height):
text=text[0:-1]
text_width, text_height = font.getsize(text)
if alignment is "" or "middle" or None:
x = int((box_width / 2) - (text_width / 2))
if alignment is 'left':
x = 0
y = int((box_height / 2) - (text_height / 1.7))
space = Image.new('RGB', (box_width, box_height), color='white')
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
image.paste(space, tuple)
"""Custom function to display longer text into multiple lines (wrapping)"""
def text_wrap(text, font=default, line_width = display_width):
counter, padding = 0, 60
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
"""Check if internet is available by trying to reach google"""
def internet_available():
try:
urlopen('https://google.com',timeout=5)
return True
except URLError as err:
return False
'''Get system timezone and set timezone accordingly'''
def get_tz():
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):
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 reduce_colours(image):
buffer = np.array(image)
r,g,b = buffer[:,:,0], buffer[:,:,1], buffer[:,:,2]
if display_colours == "bwr":
buffer[np.logical_and(r > 245, g > 245)] = [255,255,255] #white
buffer[np.logical_and(r > 245, g < 245)] = [255,0,0] #red
buffer[np.logical_and(r != 255, r == g )] = [0,0,0] #black
else:
buffer[np.logical_and(r > 245, g > 245)] = [255,255,255] #white
buffer[g < 255] = [0,0,0] #black
image = Image.fromarray(buffer).rotate(270, expand=True)
return image
def calibrate(cycles):
"""Function for Calibration"""
import e_paper_drivers
epd = e_paper_drivers.EPD()
print('----------Started calibration of E-Paper display----------')
for i in range(cycles):
epd.init()
print('Calibrating black...')
epd.display_frame(epd.get_frame_buffer(black))
if display_colours == "bwr":
print('calibrating red...')
epd.display_frame(epd.get_frame_buffer(red))
print('Calibrating white...')
epd.display_frame(epd.get_frame_buffer(white))
epd.sleep()
print('Cycle {0} of {1} complete'.format(i, cycle))
print('-----------Calibration complete----------')
#if __name__ == '__main__':
#main()

View File

@ -0,0 +1,87 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Calendar module for Inky-Calendar Project
Copyright by aceisace
"""
from __future__ import print_function
from inkycal_icalendar import upcoming_events
from configuration import *
from settings import *
import arrow
"""Find max number of lines that can fit in the middle section and allocate
a position for each line"""
lines = middle_section_height // line_height
line_pos = {}
for i in range(lines):
y = top_section_height + i * line_height
line_pos['pos'+str(i+1)] = (x_padding, y)
"""Create a list of dictionaries containing dates of the next days"""
now = arrow.now()
agenda_list = [{'date':now.replace(days=+i),
'date_str':now.replace(days=+i).format('ddd D MMM YY',locale=language),
'type':'date'} for i in range(lines)]
"""Copy the list from the icalendar module"""
filtered_events = upcoming_events.copy()
"""Print events with some styling"""
"""
style = 'D MMM YY HH:mm'
if filtered_events:
line_width = max(len(i.name) for i in filtered_events)
for events in filtered_events:
print('{0} {1} | {2} | {3} |'.format(events.name,
' '* (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:
events.end = events.end.to(get_tz())
events.begin = events.begin.to(get_tz())
if hours == '24':
agenda_list.append({'date': events.begin,
'title':events.begin.format('HH:mm')+' '+ str(events.name),
'type':'timed_event'})
if hours == '12':
agenda_list.append({'date': events.begin,
'title':events.begin.format('hh:mm a')+' '+str(events.name),
'type':'timed_event'})
else:
if events.duration.days == 1:
agenda_list.append({'date': events.begin,'title':events.name, 'type':'full_day_event'})
else:
for days in range(events.duration.days):
agenda_list.append({'date': events.begin.replace(days=+i),'title':events.name, 'type':'full_day_event'})
"""Sort events and dates in chronological order"""
agenda_list = sorted(agenda_list, key = lambda i: i['date'])
"""Crop the agenda_list in case it's too long"""
if len(agenda_list) > len(line_pos):
del agenda_list[len(line_pos):]
"""Display all events and dates on the display"""
for i in range(len(agenda_list)):
if agenda_list[i]['type'] == 'date':
write_text(line_width, line_height, agenda_list[i]['date_str'],
line_pos['pos'+str(i+1)], alignment = 'left')
elif agenda_list[i]['type'] is 'timed_event':
write_text(line_width, line_height, agenda_list[i]['title'],
line_pos['pos'+str(i+1)], alignment = 'left')
else:
write_text(line_width, line_height, agenda_list[i]['title'],
line_pos['pos'+str(i+1)])
"""Crop the image to show only the middle section"""
image.crop((0, top_section_height, display_width,
display_height-bottom_section_height)).save('agenda.png')

View File

@ -0,0 +1,98 @@
#!/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 *
from settings import *
import datetime
from PIL import Image, ImageDraw
"""Define some parameters for the grid"""
grid_width, grid_height = display_width, 324
grid_rows = 6
grid_coloums = 7
padding_left = int((display_width % grid_coloums) / 2)
padding_up = int((grid_height % grid_rows) / 2)
icon_width = grid_width // grid_coloums
icon_height = grid_height // grid_rows
weekdays_height = 22
#def main():
this = datetime.datetime.now()
"""Add grid-coordinates in the grid dictionary for a later lookup"""
grid = {}
counter = 0
for row in range(grid_rows):
y = middle_section_offset - grid_height + row*icon_height
for col in range(grid_coloums):
x = padding_left + col*icon_width
counter += 1
grid['pos'+str(counter)] = (x,y)
"""Set the Calendar to start on the day specified by the settings file """
if week_starts_on is "" or "Monday":
calendar.setfirstweekday(calendar.MONDAY)
else:
calendar.setfirstweekday(calendar.SUNDAY)
"""Create a scrolling calendar"""
cal = calendar.monthcalendar(this.year, this.month)
current_row = [cal.index(i) for i in cal if this.day in i][0]
if current_row > 1:
del cal[:current_row-1]
if len(cal) < grid_rows:
next_month = this + datetime.timedelta(days=30)
cal_next_month = calendar.monthcalendar(next_month.year, next_month.month)
cal.extend(cal_next_month[:grid_rows - len(cal)]
"""
flatten = lambda z: [x for y in z for x in y]
cal = flatten(cal)
cal_next_month = flatten(cal_next_month)
cal.extend(cal_next_month)
num_font= ImageFont.truetype(NotoSansCJK+'Light.otf', 30)
"""
#draw = ImageDraw.Draw(image) #
"""
counter = 0
for i in range(len(cal)):
counter += 1
if cal[i] != 0 and counter <= grid_rows*grid_coloums:
write_text(icon_width, icon_height, str(cal[i]), grid['pos'+str(counter)],
font = num_font)
##if this.day == cal[i]:
##pos = grid['pos'+str(counter)]
#x = pos[0] + int(icon_width/2)
#y = pos[1] + int(icon_height/2)
#r = int(icon_width * 0.75#coords = (x-r, y-r, x+r, y+r)
#draw.ellipse(coords, fill= 0, outline='black',
#width=3)
image.crop((0, top_section_height, display_width,
display_height-bottom_section_height)).save('cal.png')
#if __name__ == '__main__':
# main()
"""

View File

@ -0,0 +1,53 @@
#!/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
print_events = True
"""Set timelines for filtering upcoming events"""
now = arrow.now(tz=get_tz())
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 = []
upcoming_events += [events for ical in calendars for events in ical.events
if now <= events.end <= further_future or now <= events.begin <= near_future]
"""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)
""" 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:
style = 'DD MMM YY HH:mm' #D MMM YY HH:mm
if upcoming_events:
line_width = max(len(i.name) for i in upcoming_events)
for events in upcoming_events:
print('{0} {1} | {2} | {3} |'.format(events.name,
' '* (line_width - len(events.name)), events.begin.format(style),
events.end.format(style)), events.all_day)

View File

@ -0,0 +1,49 @@
"""Add rss-feeds at the bottom section of the Calendar"""
import feedparser
from random import shuffle
from settings import *
from configuration import *
"""Find out how many lines can fit at max in the bottom section"""
lines = bottom_section_height // line_height
"""Create and fill a dictionary of the positions of each line"""
line_pos = {}
for i in range(lines):
y = bottom_section_offset + i * line_height
line_pos['pos' + str(i+1)] = (x_padding, y)
if bottom_section == "RSS" and rss_feeds != []:
"""Parse the RSS-feed titles & summaries and save them to a list"""
rss_feed = []
for feeds in rss_feeds:
text = feedparser.parse(feeds)
for posts in text.entries:
rss_feed.append('{0}: {1}'.format(posts.title, posts.summary))
del rss_feed[lines:]
shuffle(rss_feed)
"""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, counter = [], 0
for posts in rss_feed:
wrapped = text_wrap(posts)
counter += len(filtered) + len(wrapped)
if counter < lines:
filtered.append(wrapped)
filtered = flatten(filtered)
## for i in lines: # Show line lenght and content of each line
## print(i, ' ' * (line-len(i)),'| height: ',default.getsize(i)[1])
"""Write the correctly formatted text on the display"""
for i in range(len(filtered)):
write_text(display_width, default.getsize('hg')[1],
' '+filtered[i], line_pos['pos'+str(i+1)], alignment= 'left')
image.crop((0,bottom_section_offset, display_width, display_height)).save(
'rss.png')
del filtered, rss_feed

View File

@ -0,0 +1,161 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Weather module for Inky-Calendar software. In development...
To-do:
- make locations of icons and text dynamic
Copyright by aceisace
"""
from __future__ import print_function
import pyowm
from settings import *
from configuration import *
from PIL import Image, ImageDraw, ImageFont
import arrow
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'
}
"""Split top_section into to 2 rows"""
section_height = top_section_height // 2
section_width = (top_section_width - top_section_height) // 3
"""Allocate icon sizes"""
icon_small = section_height
icon_large = top_section_height
"""Split top section into 4 coloums"""
section1 = 0
section2 = icon_large + (top_section_width - icon_large) // 3 * 0
section3 = icon_large + (top_section_width - icon_large) // 3 * 1
section4 = icon_large + (top_section_width - icon_large) // 3 * 2
"""Allocate positions for icons"""
weather_icon_pos = (section1, 0)
wind_icon_pos = (section2, 0)
sun_icon_pos = (section3, 0)
temperature_icon_pos = (section4, 0)
weather_description_pos = (section2, section_height)
humidity_icon_pos = (section4, section_height)
"""Allocate positions for text"""
next_to = lambda x: (x[0]+ icon_small, x[1])
icon_offset = section_width - icon_small
wind_pos = next_to(wind_icon_pos)
temperature_pos = next_to(temperature_icon_pos)
sun_pos = next_to(sun_icon_pos)
humidity_pos = next_to(humidity_icon_pos)
weather_pos = (section2, section_height)
#def main():
"""Connect to Openweathermap API and fetch weather data"""
if top_section == "Weather" and api_key != "" and owm.is_API_online() is True:
try:
print("Fetching weather data from openweathermap...",end = ' ')
observation = owm.weather_at_place(location)
print("Done")
weather = observation.get_weather()
weathericon = weather.get_weather_icon_name()
Humidity = str(weather.get_humidity())
cloudstatus = str(weather.get_clouds())
weather_description = (str(weather.get_detailed_status()))
"""Add the icons at the correct positions"""
print('Adding weather info and icons to the image...', end = ' ')
write_text(icon_small, icon_small, '\uf055', temperature_icon_pos,
font = w_font, adapt_fontsize = True) # Temperature icon
write_text(icon_large, icon_large, weathericons[weathericon],
weather_icon_pos, font = w_font, adapt_fontsize = True) # Weather icon
write_text(icon_small, icon_small, '\uf07a', humidity_icon_pos, font = w_font,
adapt_fontsize = True) #Humidity icon
write_text(icon_small,icon_small, '\uf050', wind_icon_pos, font = w_font,
adapt_fontsize = True) #Wind icon
"""Format and write the temperature and windspeed"""
if units == "metric":
Temperature = str(int(weather.get_temperature(unit='celsius')['temp']))
windspeed = str(int(weather.get_wind()['speed']))
write_text(icon_offset, section_height, Temperature+'°C', temperature_pos)
write_text(icon_offset,section_height, windspeed+" km/h", wind_pos)
else:
Temperature = str(int(weather.get_temperature('fahrenheit')['temp']))
windspeed = str(int(weather.get_wind()['speed']*0.621))
write_text(icon_offset, section_height, Temperature+' F', temperature_pos)
write_text(icon_offset,section_height, windspeed+" mph", wind_pos)
"""write the humidity at the given position"""
write_text(icon_offset, section_height, Humidity+'%', humidity_pos)
now = arrow.now(tz=get_tz())
sunrise = arrow.get(weather.get_sunrise_time()).to(get_tz())
sunset = arrow.get(weather.get_sunset_time()).to(get_tz())
"""Add the sunrise/sunset icon and display the time"""
if (now <= sunrise and now <= sunset) or (now >= sunrise and now >= sunset):
write_text(icon_small, icon_small, '\uf051', sun_icon_pos, font = w_font,
adapt_fontsize = True)
if hours == "24":
write_text(icon_offset, section_height, sunrise.format('H:mm'), sun_pos)
else:
write_text(icon_offset, section_height, sunrise.format('h:mm'), sun_pos)
else:
write_text(icon_small, '\uf052', sun_icon_pos, font = w_font,
adapt_fontsize = True)
if hours == "24":
write_text(icon_offset, section_height, sunset.format('H:mm'), sun_pos)
else:
write_text(icon_offset, section_height, sunset.format('h:mm'), sun_pos)
"""Add a short weather description"""
write_text(section2+section3-icon_offset, section_height,
weather_description, weather_pos)
print('Done'+'\n')
"""Show the fetched weather data"""
print("Today's weather report: The current Temperature is {0}°C. The "
"relative humidity is {1} %. The current windspeed is {2} km/h. "
"The sunrise today was at {3}. The sunset is at {4}. The weather can "
"be described with: {5}".format(Temperature, Humidity, windspeed,
sunrise.format('H:mm'), sunset.format('H:mm'), weather_description))
image.crop((0,0, top_section_width, top_section_height)).save('weather.png')
except Exception as e:
"""If no response was received from the openweathermap
api server, add the cloud with question mark"""
print('__________OWM-ERROR!__________')
print('Reason: ',e)
write_text(icon_large, icon_large, '\uf07b', weather_icon_pos,
font = w_font, adapt_fontsize = True)
pass
#if __name__ == '__main__':
#main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,18 @@
ical_urls = ["https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics",
"https://calendar.google.com/calendar/ical/ohcobp9hs097e9nnbppks7blv4%40group.calendar.google.com/private-55859b2165097102e0c061e978eb4926/basic.ics",
"https://calendar.yahoo.com/saadnaseer63/3ac8573af0f38b65367a4e3d287bf06d/ycal.ics?id=1407"]
rss_feeds = ["http://feeds.bbci.co.uk/news/world/rss.xml#"]
update_interval = "60"
api_key = "57c07b8f2ae09e348d32317f1bfe3f52"
location = "Stuttgart, DE"
week_starts_on = "Monday"
events_max_range = "60"
calibration_hours = [0,12,18]
display_colours = "bwr"
language = "en"
units = "metric"
hours = "24"
top_section = "Weather"
middle_section = "Agenda"
bottom_section = "RSS"
show_last_update_time = "False"

View File

@ -0,0 +1,48 @@
from configuration import *
from settings import *
"""
def wrapper(text, font=default, max_width = display_width):
counter = 0
padding = 50
lines = []
if font.getsize(text)[0] < max_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] < max_width- padding:
lines.append(line)
line = ''
counter = i
if i == len(text.split()) and line != '':
lines.append(line)
return lines
"""
def text_wrap(text, font=default, line_width = display_width):
counter, lines = 0, []
if font.getsize(text)[0] < line_width:
lines.append(text)
else:
while font.getsize(text)[0] < line_width:
"""
for i in range(1, len(text.split())+1):
line = ' '.join(text.split()[counter:i])
print(line, font.getsize(line)[0])
"""
#text = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'
#text = 'Russia submersible fire was in battery compartment Fourteen crew died in the fire on board'
text = "Russian LGBT activist Yelena Grigoryeva murdered in St Petersburg: Yelena Grigoryeva, 41, was stabbed and strangled near her home in St Petersburg, relatives say."
lines = text_wrap(text, default, display_width)
line = len(max(lines, key=len))
for i in lines:
print(i, ' ' * (line-len(i)),'| width: ',default.getsize(i)[0])

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,171 @@
"""
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
from urllib.request import urlopen
from settings import language
from pytz import timezone
import numpy as np
import os
"""Set the display height and width (in pixels)"""
display_height, display_width = 640, 384
"""Create 3 sections of the display, based on percentage"""
top_section_width = middle_section_width = bottom_section_width = display_width
top_section_height = int(display_height*0.10)
middle_section_height = int(display_height*0.65)
bottom_section_height = int(display_height - middle_section_height -
top_section_height)
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])
"""Fonts handling"""
fontpath = path+'fonts/'
NotoSansCJK = fontpath+'NotoSansCJK/NotoSansCJKsc-'
NotoSans = fontpath+'NotoSans/NotoSans-SemiCondensed'
weatherfont = fontpath+'WeatherFont/weathericons-regular-webfont.ttf'
if language in ['ja','zh','zh_tw','ko']:
default = ImageFont.truetype(NotoSansCJK+'Light.otf', 18)
semi = ImageFont.truetype(NotoSansCJK+'DemiLight.otf', 18)
bold = ImageFont.truetype(NotoSansCJK+'Regular.otf', 18)
month_font = ImageFont.truetype(NotoSansCJK+'DemiLight.otf', 40)
else:
default = ImageFont.truetype(NotoSans+'Light.ttf', 18)
semi = ImageFont.truetype(NotoSans+'.ttf', 18)
bold = ImageFont.truetype(NotoSans+'Medium.ttf', 18)
month_font = ImageFont.truetype(NotoSans+'Light.ttf', 40)
w_font = ImageFont.truetype(weatherfont, 10)
x_padding = int((display_width % 10) // 2)
line_height = default.getsize('hg')[1]
line_width = display_width- x_padding
image = Image.new('RGB', (display_width, display_height), 'white')
#def main():
def write_text(box_width, box_height, text, tuple,
font=default, alignment='middle', adapt_fontsize = False):
text_width, text_height = font.getsize(text)
if adapt_fontsize == True:
size = 10
while text_width < box_width and text_height < box_height:
size += 1
font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getsize(text)
while (text_width, text_height) > (box_width, box_height):
text=text[0:-1]
text_width, text_height = font.getsize(text)
if alignment is "" or "middle" or None:
x = int((box_width / 2) - (text_width / 2))
if alignment is 'left':
x = 0
y = int((box_height / 2) - (text_height / 1.7))
space = Image.new('RGB', (box_width, box_height), color='white')
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
image.paste(space, tuple)
"""Custom function to display longer text into multiple lines (wrapping)"""
def text_wrap(text, font=default, line_width = display_width):
counter, padding = 0, 60
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
"""Check if internet is available by trying to reach google"""
def internet_available():
try:
urlopen('https://google.com',timeout=5)
return True
except URLError as err:
return False
'''Get system timezone and set timezone accordingly'''
def get_tz():
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):
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 reduce_colours(image):
buffer = np.array(image)
r,g,b = buffer[:,:,0], buffer[:,:,1], buffer[:,:,2]
if display_colours == "bwr":
buffer[np.logical_and(r > 245, g > 245)] = [255,255,255] #white
buffer[np.logical_and(r > 245, g < 245)] = [255,0,0] #red
buffer[np.logical_and(r != 255, r == g )] = [0,0,0] #black
else:
buffer[np.logical_and(r > 245, g > 245)] = [255,255,255] #white
buffer[g < 255] = [0,0,0] #black
image = Image.fromarray(buffer).rotate(270, expand=True)
return image
def calibrate(cycles):
"""Function for Calibration"""
import e_paper_drivers
epd = e_paper_drivers.EPD()
print('----------Started calibration of E-Paper display----------')
for i in range(cycles):
epd.init()
print('Calibrating black...')
epd.display_frame(epd.get_frame_buffer(black))
if display_colours == "bwr":
print('calibrating red...')
epd.display_frame(epd.get_frame_buffer(red))
print('Calibrating white...')
epd.display_frame(epd.get_frame_buffer(white))
epd.sleep()
print('Cycle {0} of {1} complete'.format(i, cycle))
print('-----------Calibration complete----------')
#if __name__ == '__main__':
#main()

View File

@ -0,0 +1,15 @@
ical_urls = [""]
update_interval = "60"
api_key = ""
location = "Stuttgart, DE"
week_starts_on = "Monday"
events_max_range = "60"
calibration_hours = [0,12,18]
display_colours = "bwr"
language = "en"
units = "metric"
hours = "24"
top_section = "Weather"
middle_section = "Agenda"
bottom_section = "RSS"
show_last_update_time = "False"