Merge pull request #81 from aceisace/dev

v1.7.1 -> v1.7.2
This commit is contained in:
Ace 2020-02-16 22:50:35 +01:00 committed by GitHub
commit 2d3ca3727d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 795 additions and 231 deletions

View File

@ -4,11 +4,30 @@ The order is from latest to oldest and structured in the following way:
* Version name with date of publishing * Version name with date of publishing
* Sections with either 'added', 'fixed', 'updated' and 'changed' * Sections with either 'added', 'fixed', 'updated' and 'changed'
## [1.7] Mid December 2019 (date not confirmed yet) ## [1.7.1] Mid January 2020
### Added
* Added support for 4.2", 5.83", 7.5" (v2) E-Paper display
* Added driver files for above mentioned E-Paper displays
### Changed
* Slight changes in naming of generated images
* Slight changes in importing module names (now using dynamic imports)
* Changed driver files for all E-Papers with the latest ones from waveshare (v4)
* Slightly changed the way modules are executed
### Removed
* Removed option for selecting colour from settings file
### Fixed
* Fixed a problem where the calibration function would only update half the display on the 7.5" black-white E-Paper
* Implemented a possible bugfix for 'begin must be before end' error.
## [1.7] Mid December 2019
### Added ### Added
* Added support for sections (top-,middle-,and bottom section) * Added support for sections (top-,middle-,and bottom section)
* Added support for weather forecasts. * Added support for weather forecasts.
* Added support for moon phase * Added support for moon phase
* Added support for events in Calendar module * Added support for events in Calendar module
* Added support for coloured negative temperature * Added support for coloured negative temperature
@ -16,31 +35,37 @@ The order is from latest to oldest and structured in the following way:
* Added support for wind direction in weather module * Added support for wind direction in weather module
* Added support for decimal places in weather module * Added support for decimal places in weather module
* Added extra customisation options (see configuration file) * Added extra customisation options (see configuration file)
* Added support for recurring events
* Added forecasts in weather module
* Added info about moon phase in weather module
* Added info about sunrise and sunset time in weather module
* Added support for colour-changing temperature (for coloured E-Paper displays, the temperature will red if it drops below 0°Celcius)
* Added support for decimal places in weather section (wind speed, temperature)
* Added beaufort scale to show windspeed
* Added option to show wind direction with an arrow
* Added new event and today icon in Calendar module
* Added sections showing upcoming events within Calendar module
* Added configuration file for additional configuration options
* Added new fonts with better readability
* Added support to manually change fontsize in each module
* Added more design customisation (text colour, background colours etc.)
### Changed ### Changed
* Refactoring of software. Split software into several smaller modules * Changed folder structure (Full software refactoring)
* Re-arranged weather section layout * Split main file into smaller modules, each with a specific task
* Icons (today, events) are generated on demand * Changed layout of E-Paper (top_section, middle_section, bottom_section)
* Merged calibration files into inkycal_drivers * Changed settings file, installer and web-UI
* Changed layout of Agenda module * Black and white E-Papers now use dithering option to map pixels to either black and white
* Changed icons for marking today on Calendar module
* Added more options in function 'write_text'
* Text does not have any background colour anymore (transparent)
* Optimised calibration function for faster calibration, especially for coloured E-Papers
* Changed settings file
### Removed ### Removed
* Removed last-updated feature * Removed non-readable fonts
* Removed all icons stored as images * Removed all icons in form of image files. The new icons are generated with PIL on the spot
* Removed calibration file (calibration.py) * Removed option to reduce colours for black and white E-Papers
### Fixed ### Fixed
* Fixed a few bugs related to the ics library * Fixed problem with RSS feeds not displaying more than one feed
* Fine-tuned image pre-processing (mapping pixels to specific colours) * Fixed image rendering
* Fixed a problem where RSS feeds would not display more than one post * Fixed problems when setting the weekstart to Sunday
* Fixed a problem where certain weather icons would not be shown
## [1.6] Mid May 2019 ## [1.6] Mid May 2019

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# E-Paper-Calendar software installer for Raspberry Pi running Debian 10 (a.k.a. Buster) with Desktop # E-Paper-Calendar software installer for Raspberry Pi running Debian 10 (a.k.a. Buster) with Desktop
# Version: 1.7 (Early Dec 2019) # Version: 1.7.2 (Mid Feb 2020)
echo -e "\e[1mPlease select an option from below:" echo -e "\e[1mPlease select an option from below:"
echo -e "\e[97mEnter \e[91m[1]\e[97m to update Inky-Calendar software" #Option 1 : UPDATE echo -e "\e[97mEnter \e[91m[1]\e[97m to update Inky-Calendar software" #Option 1 : UPDATE
@ -90,17 +90,6 @@ if [ "$option" = 1 ] || [ "$option" = 2 ]; then # This happens when installing o
# Create symlinks of settings and configuration file # Create symlinks of settings and configuration file
ln -s /home/"$USER"/Inky-Calendar/settings/settings.py /home/"$USER"/Inky-Calendar/modules/ ln -s /home/"$USER"/Inky-Calendar/settings/settings.py /home/"$USER"/Inky-Calendar/modules/
ln -s /home/"$USER"/Inky-Calendar/settings/configuration.py /home/"$USER"/Inky-Calendar/modules/ ln -s /home/"$USER"/Inky-Calendar/settings/configuration.py /home/"$USER"/Inky-Calendar/modules/
# add a short info
cat > /home/pi/Inky-Calendar/Info.txt << EOF
This document contains a short info of the Inky-Calendar software version
Version: 1.7
Installer version: 1.7 (Mid December 2019)
settings file: /home/$USER/Inky-Calendar/settings/settings.py
If the time was set correctly, you installed this software on:
$(date)
EOF
echo "" echo ""
echo -e "\e[97mDo you want the software to start automatically at boot?" echo -e "\e[97mDo you want the software to start automatically at boot?"

View File

@ -1,7 +1,7 @@
#!/usr/bin/python3 #!/usr/bin/python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
v1.7.1 v1.7.2
Main file of Inky-Calendar software. Creates dynamic images for each section, Main file of Inky-Calendar software. Creates dynamic images for each section,
assembles them and sends it to the E-Paper assembles them and sends it to the E-Paper
@ -17,17 +17,35 @@ import gc
"""Perepare for execution of main programm""" """Perepare for execution of main programm"""
calibration_countdown = 'initial' calibration_countdown = 'initial'
skip_calibration = False skip_calibration = False
upside_down = False
image_cleanup() image_cleanup()
top_section_module = importlib.import_module(top_section) try:
middle_section_module = importlib.import_module(middle_section) top_section_module = importlib.import_module(top_section)
bottom_section_module = importlib.import_module(bottom_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 """ """Check time and calibrate display if time """
while True: while True:
now = arrow.now(tz=get_tz()) now = arrow.now(tz=get_tz())
for _ in range(1): for _ in range(1):
image = Image.new('RGB', (display_width, display_height), background_colour) 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------------------""" """------------------Add short info------------------"""
print('Current Date: {0} \nCurrent Time: {1}'.format(now.format( print('Current Date: {0} \nCurrent Time: {1}'.format(now.format(
@ -53,36 +71,62 @@ while True:
'displays causes ghosting') 'displays causes ghosting')
"""----------------Generating and assembling images------""" """----------------------top-section-image-----------------------------"""
try: try:
top_section_module.main() top_section_module.main()
top_section_image = Image.open(image_path + top_section+'.png') top_section_image = Image.open(image_path + top_section+'.png')
image.paste(top_section_image, (0, 0)) image.paste(top_section_image, (0, 0))
print('Done')
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: except Exception as error:
print(error) print(error)
pass pass
"""----------------------middle-section-image---------------------------"""
try: try:
middle_section_module.main() middle_section_module.main()
middle_section_image = Image.open(image_path + middle_section+'.png') middle_section_image = Image.open(image_path + middle_section+'.png')
image.paste(middle_section_image, (0, middle_section_offset)) image.paste(middle_section_image, (0, middle_section_offset))
print('Done')
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: except Exception as error:
print(error) print(error)
pass pass
"""----------------------bottom-section-image---------------------------"""
try: try:
bottom_section_module.main() bottom_section_module.main()
bottom_section_image = Image.open(image_path + bottom_section+'.png') bottom_section_image = Image.open(image_path + bottom_section+'.png')
image.paste(bottom_section_image, (0, bottom_section_offset)) image.paste(bottom_section_image, (0, bottom_section_offset))
print('Done')
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: except Exception as error:
print(error) print(error)
pass 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') 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-----------""" """---------Refreshing E-Paper with newly created image-----------"""
epaper = driver.EPD() epaper = driver.EPD()
print('Initialising E-Paper...', end = '') print('Initialising E-Paper...', end = '')
@ -91,12 +135,11 @@ while True:
if three_colour_support == True: if three_colour_support == True:
print('Sending image data and refreshing display...', end='') print('Sending image data and refreshing display...', end='')
black_im, red_im = split_colours(image) epaper.display(epaper.getbuffer(image), epaper.getbuffer(image_col))
epaper.display(epaper.getbuffer(black_im), epaper.getbuffer(red_im))
print('Done') print('Done')
else: else:
print('Sending image data and refreshing display...', end='') print('Sending image data and refreshing display...', end='')
epaper.display(epaper.getbuffer(image.convert('1', dither=True))) epaper.display(epaper.getbuffer(image))
print('Done') print('Done')
print('Sending E-Paper to deep sleep...', end = '') print('Sending E-Paper to deep sleep...', end = '')

View File

@ -18,7 +18,7 @@ border_top = int(middle_section_height * 0.02)
border_left = int(middle_section_width * 0.02) border_left = int(middle_section_width * 0.02)
"""Choose font optimised for the agenda section""" """Choose font optimised for the agenda section"""
font = ImageFont.truetype(NotoSans+'Medium.ttf', agenda_font_size) font = ImageFont.truetype(NotoSans+'Medium.ttf', agenda_fontsize)
line_height = int(font.getsize('hg')[1] * 1.2) + 1 line_height = int(font.getsize('hg')[1] * 1.2) + 1
line_width = int(middle_section_width - (border_left*2)) line_width = int(middle_section_width - (border_left*2))
@ -33,10 +33,7 @@ event_col_start = time_col_start + time_col_width
"""Find max number of lines that can fit in the middle section and allocate """Find max number of lines that can fit in the middle section and allocate
a position for each line""" a position for each line"""
if bottom_section: max_lines = int(middle_section_height+bottom_section_height -
max_lines = int((middle_section_height - border_top*2) // line_height)
else:
max_lines = int(middle_section_height+bottom_section_height -
(border_top * 2))// line_height (border_top * 2))// line_height
line_pos = [(border_left, int(top_section_height + border_top + line * line_height)) line_pos = [(border_left, int(top_section_height + border_top + line * line_height))
@ -104,8 +101,14 @@ def generate_image():
agenda_events[events]['date_str'], line_pos[events], font = font) agenda_events[events]['date_str'], line_pos[events], font = font)
previous_date = agenda_events[events]['date'] previous_date = agenda_events[events]['date']
draw.line((date_col_start, line_pos[events][1],
line_width,line_pos[events][1]), fill = 'red' if three_colour_support == True else 'black') 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': elif agenda_events[events]['type'] == 'timed_event':
write_text(time_col_width, line_height, agenda_events[events]['time'], write_text(time_col_width, line_height, agenda_events[events]['time'],
@ -123,12 +126,13 @@ def generate_image():
(event_col_start, line_pos[events][1]), alignment = 'left', font = font) (event_col_start, line_pos[events][1]), alignment = 'left', font = font)
"""Crop the image to show only the middle section""" """Crop the image to show only the middle section"""
if bottom_section: agenda_image = image.crop((0,middle_section_offset,display_width, display_height))
agenda_image = crop_image(image, 'middle_section')
else:
agenda_image = image.crop((0,middle_section_offset,display_width, display_height))
agenda_image.save(image_path+'inkycal_agenda.png') 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') print('Done')
except Exception as e: except Exception as e:
@ -136,8 +140,16 @@ def generate_image():
print('Failed!') print('Failed!')
print('Error in Agenda module!') print('Error in Agenda module!')
print('Reason: ',e) 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 pass
def main(): def main():
generate_image() generate_image()

View File

@ -16,7 +16,7 @@ at_in_your_language = 'at'
event_icon = 'square' # dot #square event_icon = 'square' # dot #square
style = "DD MMM" style = "DD MMM"
font = ImageFont.truetype(NotoSans+'.ttf', calendar_font_size) font = ImageFont.truetype(NotoSans+'.ttf', calendar_fontsize)
space_between_lines = 0 space_between_lines = 0
if show_events == True: if show_events == True:
@ -98,7 +98,7 @@ def generate_image():
"""Add the numbers on the correct positions""" """Add the numbers on the correct positions"""
for i in range(len(calendar_flat)): for i in range(len(calendar_flat)):
if calendar_flat[i] != 0: if calendar_flat[i] not in (0, int(now.day)):
write_text(icon_width, icon_height, str(calendar_flat[i]), grid[i]) 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""" """Draw a red/black circle with the current day of month in white"""
@ -110,11 +110,13 @@ def generate_image():
x_text = int((icon_width / 2) - (text_width / 2)) x_text = int((icon_width / 2) - (text_width / 2))
y_text = int((icon_height / 2) - (text_height / 1.7)) y_text = int((icon_height / 2) - (text_height / 1.7))
ImageDraw.Draw(icon).ellipse((x_circle-radius, y_circle-radius, ImageDraw.Draw(icon).ellipse((x_circle-radius, y_circle-radius,
x_circle+radius, y_circle+radius), fill= 'red' if x_circle+radius, y_circle+radius), fill= 'black', outline=None)
three_colour_support == True else 'black', outline=None)
ImageDraw.Draw(icon).text((x_text, y_text), str(now.day), fill='white', ImageDraw.Draw(icon).text((x_text, y_text), str(now.day), fill='white',
font=bold) font=bold)
image.paste(icon, current_day_pos, icon) 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""" """Create some reference points for the current month"""
days_current_month = calendar.monthrange(now.year, now.month)[1] days_current_month = calendar.monthrange(now.year, now.month)[1]
@ -152,38 +154,47 @@ def generate_image():
for days in days_with_events: for days in days_with_events:
draw_square((int(grid[calendar_flat.index(days)][0]+center_x), draw_square((int(grid[calendar_flat.index(days)][0]+center_x),
int(grid[calendar_flat.index(days)][1] + center_y )), int(grid[calendar_flat.index(days)][1] + center_y )),
8, square_size , square_size) 8, square_size , square_size, colour='black')
"""Add a small section showing events of today and tomorrow""" """Add a small section showing events of today and tomorrow"""
event_list = ['{0} {1} {2} : {3}'.format(today_in_your_language, event_list = []
at_in_your_language, event.begin.format('HH:mm' if hours == 24 else
'hh:mm'), event.name) for event in calendar_events if event.begin.day
== now.day and now < event.end]
event_list += ['{0} {1} {2} : {3}'.format(tomorrow_in_your_language,
at_in_your_language, event.begin.format('HH:mm' if hours == 24 else
'hh:mm'), event.name) for event in calendar_events if event.begin.day
== now.replace(days=1).day]
after_two_days = now.replace(days=2).floor('day') after_two_days = now.replace(days=2).floor('day')
event_list += ['{0} {1} {2} : {3}'.format(event.begin.format('D MMM'), for event in calendar_events:
at_in_your_language, event.begin.format('HH:mm' if hours == 24 else if event.begin.day == now.day and now < event.end:
'hh:mm'), event.name) for event in upcoming_events if event.end > if event.all_day:
after_two_days] 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:] del event_list[max_event_lines:]
if event_list: if event_list:
for lines in event_list: for lines in event_list:
write_text(main_area_width, int(events_height/max_event_lines), lines, write_text(main_area_width, int(events_height/max_event_lines), lines,
event_lines[event_list.index(lines)], alignment='left', event_lines[event_list.index(lines)], font=font, alignment='left')
fill_height = 0.7)
else: else:
write_text(main_area_width, int(events_height/max_event_lines), write_text(main_area_width, int(events_height/max_event_lines),
'No upcoming events.', event_lines[0], alignment='left', 'No upcoming events.', event_lines[0], font=font, alignment='left')
fill_height = 0.7)
"""Set print_events_to True to print all events in this month""" """Set print_events_to True to print all events in this month"""
style = 'DD MMM YY HH:mm' style = 'DD MMM YY HH:mm'
@ -197,6 +208,10 @@ def generate_image():
calendar_image = crop_image(image, 'middle_section') calendar_image = crop_image(image, 'middle_section')
calendar_image.save(image_path+'inkycal_calendar.png') 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') print('Done')
except Exception as e: except Exception as e:
@ -204,6 +219,11 @@ def generate_image():
print('Failed!') print('Failed!')
print('Error in Calendar module!') print('Error in Calendar module!')
print('Reason: ',e) 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 pass
def main(): def main():

View File

@ -74,6 +74,7 @@ def fetch_events():
for events in upcoming_events: for events in upcoming_events:
if events.all_day and events.duration.days > 1: if events.all_day and events.duration.days > 1:
events.end = events.end.replace(days=-2) events.end = events.end.replace(days=-2)
events.make_all_day()
if not events.all_day: if not events.all_day:
events.end = events.end.to(timezone) events.end = events.end.to(timezone)

View File

@ -2,82 +2,178 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Experimental image module for Inky-Calendar software Experimental image module for Inky-Calendar software
Displays an image on the E-Paper. Currently only supports black and white Displays an image on the E-Paper. Work in progress!
Copyright by aceisace Copyright by aceisace
""" """
from __future__ import print_function from __future__ import print_function
from PIL import Image
from configuration import * from configuration import *
import os from os import path
from PIL import ImageOps
import requests
import numpy
import inkycal_drivers as drivers """----------------------------------------------------------------"""
#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?
"""----------------------------------------------------------------"""
display = drivers.EPD() # First determine dimensions
if mode == 'horizontal':
display_width, display_height == display_height, display_width
# Where is the image? if mode == 'vertical':
path = '/home/pi//Desktop/test.JPG' pass
class inkycal_image: # .. Then substitute possibly parameterized path
# TODO Get (assigned) panel dimensions instead of display dimensions
def __init__(self, path): path = path.replace('{model}', model).replace('{width}',str(display_width)).replace('{height}',str(display_height))
self.image = Image.open(path)
self.im_width = self.image.width
self.im_height = self.image.height
def check_mode(self):
if self.image.mode != 'RGB' or 'L' or '1':
print('Image mode not supported, converting')
self.image = self.image.convert('RGB')
def preview(self):
self.image.save(path+'temp.png')
os.system("gpicview "+path+'temp.png')
os.system('rm '+path+'temp.png')
def check_size(self, alignment = 'middle', padding_colour='white'):
if display_height < self.im_height or display_width < self.im_width:
print('Image too large for the display, cropping image')
if alignment == 'middle' or None:
x1 = int((self.im_width - display_width) / 2)
y1 = int((self.im_height - display_height) / 2)
x2,y2 = x1+display_width, y1+display_height
self.image = self.image.crop((x1,y1,x2,y2))
if alignment != 'middle' or None:
print('Sorry, this feature has not been implemented yet')
raise NotImplementedError
elif display_height > self.im_height and display_width > self.im_width:
print('Image smaller than display, shifting image to center')
x = int( (display_width - self.im_width) /2)
y = int( (display_height - self.im_height) /2)
canvas = Image.new('RGB', (display_width, display_height), color=padding_colour)
canvas.paste(self.image, (x,y))
self.image = canvas
"""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: else:
print('Image file exact. no further action required') # 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
def auto_flip(self): """Turn image upside-down if specified"""
if self.im_height < self.im_width: if upside_down == True:
print('rotating image') im.rotate(180, expand = True)
self.image = self.image.rotate(270, expand=True)
self.im_width = self.image.width
self.im_height = self.image.height
def to_mono(self):
self.image = self.image.convert('1', dither=True)
def prepare_image(self, alignment='middle'): if mode == 'auto':
self.check_mode() if (im.width > im.height) and (display_width < display_height):
self.auto_flip() print('display vertical, image horizontal -> flipping image')
self.check_size(alignment = alignment) im = im.rotate(90, expand=True)
self.to_mono() if (im.width < im.height) and (display_width > display_height):
print('display horizontal, image vertical -> flipping image')
im = im.rotate(90, expand=True)
return self.image 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
#single line command: def fit_height(image, height):
display.show_image(inkycal_image(path).prepare_image(), reduce_colours=False) """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

@ -14,7 +14,7 @@ border_top = int(bottom_section_height * 0.05)
border_left = int(bottom_section_width * 0.02) border_left = int(bottom_section_width * 0.02)
"""Choose font optimised for the weather section""" """Choose font optimised for the weather section"""
font = ImageFont.truetype(NotoSans+'.ttf', rss_font_size) font = ImageFont.truetype(NotoSans+'.ttf', rss_fontsize)
space_between_lines = 1 space_between_lines = 1
line_height = font.getsize('hg')[1] + space_between_lines line_height = font.getsize('hg')[1] + space_between_lines
line_width = bottom_section_width - (border_left*2) line_width = bottom_section_width - (border_left*2)
@ -69,6 +69,11 @@ def generate_image():
rss_image = crop_image(image, 'bottom_section') rss_image = crop_image(image, 'bottom_section')
rss_image.save(image_path+'inkycal_rss.png') 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') print('Done')
except Exception as e: except Exception as e:
@ -76,8 +81,14 @@ def generate_image():
print('Failed!') print('Failed!')
print('Error in RSS module!') print('Error in RSS module!')
print('Reason: ',e) 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 pass
def main(): def main():
generate_image() generate_image()

View File

@ -120,7 +120,7 @@ def to_units(kelvin):
ndigits = decimal_places_temperature) ndigits = decimal_places_temperature)
if units == 'metric': if units == 'metric':
conversion = str(degrees_celsius) + '°C' conversion = str(degrees_celsius) + '°C'
if units == 'imperial': if units == 'imperial':
conversion = str(fahrenheit) + 'F' conversion = str(fahrenheit) + 'F'
@ -171,7 +171,7 @@ def generate_image():
forecast = owm.three_hours_forecast(location) forecast = owm.three_hours_forecast(location)
"""Round the hour to the nearest multiple of 3""" """Round the hour to the nearest multiple of 3"""
now = arrow.now(tz=get_tz()) now = arrow.utcnow()
if (now.hour % 3) != 0: if (now.hour % 3) != 0:
hour_gap = 3 - (now.hour % 3) hour_gap = 3 - (now.hour % 3)
else: else:
@ -258,9 +258,10 @@ def generate_image():
write_text(icon_small, icon_small, '\uf0b1', windspeed_icon_now_pos, write_text(icon_small, icon_small, '\uf0b1', windspeed_icon_now_pos,
font = w_font, fill_height = 0.9, rotation = -wind_degrees) font = w_font, fill_height = 0.9, rotation = -wind_degrees)
write_text(coloumn_width-icon_small, row_height, write_text(coloumn_width-icon_small, row_height, temperature_now,
temperature_now, temperature_now_pos, font = font, colour = temperature_now_pos, font = font, colour= red_temp(temperature_now))
red_temp(temperature_now))
write_text(coloumn_width-icon_small, row_height, humidity_now+'%', write_text(coloumn_width-icon_small, row_height, humidity_now+'%',
humidity_now_pos, font = font) humidity_now_pos, font = font)
write_text(coloumn_width-icon_small, row_height, wind, write_text(coloumn_width-icon_small, row_height, wind,
@ -326,24 +327,31 @@ def generate_image():
draw.line((coloumn5, line_start_y, coloumn5, 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((coloumn6, line_start_y, coloumn6, line_end_y), fill='black')
draw.line((coloumn7, line_start_y, coloumn7, line_end_y), fill='black') draw.line((coloumn7, line_start_y, coloumn7, line_end_y), fill='black')
draw.line((0, top_section_height-border_top, top_section_width-
border_left, top_section_height-border_top),
fill='red' if three_colour_support == 'True' else 'black' , width=3)
weather_image = crop_image(image, 'top_section') 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') 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') print('Done')
except Exception as e: except Exception as e:
"""If no response was received from the openweathermap """If something went wrong, print a Error message on the Terminal"""
api server, add the cloud with question mark""" print('Failed!')
print('__________OWM-ERROR!__________') print('Error in weather module!')
print('Reason: ',e) print('Reason: ',e)
write_text(icon_medium, icon_medium, '\uf07b', weather_icon_now_pos, clear_image('top_section')
font = w_font, fill_height = 1.0) write_text(top_section_width, top_section_height, str(e),
message = 'No internet connectivity or API timeout' (0, 0), font = font)
write_text(coloumn_width*6, row_height, message, humidity_icon_now_pos,
font = font)
weather_image = crop_image(image, 'top_section') weather_image = crop_image(image, 'top_section')
weather_image.save(image_path+'inkycal_weather.png') weather_image.save(image_path+'inkycal_weather.png')
pass pass

1
release.txt Normal file
View File

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

View File

@ -16,6 +16,7 @@ from pytz import timezone
import os import os
from glob import glob from glob import glob
import importlib import importlib
import subprocess as subp
"""Set the image background colour and text colour""" """Set the image background colour and text colour"""
background_colour = 'white' background_colour = 'white'
@ -34,10 +35,23 @@ else:
"""Create 3 sections of the display, based on percentage""" """Create 3 sections of the display, based on percentage"""
top_section_width = middle_section_width = bottom_section_width = display_width top_section_width = middle_section_width = bottom_section_width = display_width
top_section_height = int(display_height*0.11) if top_section and bottom_section:
middle_section_height = int(display_height*0.65) top_section_height = int(display_height*0.11)
bottom_section_height = int(display_height - middle_section_height - bottom_section_height = int(display_height*0.24)
top_section_height)
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""" """Find out the y-axis position of each section"""
top_section_offset = 0 top_section_offset = 0
@ -60,48 +74,52 @@ NotoSansCJK = fontpath+'NotoSansCJK/NotoSansCJKsc-'
NotoSans = fontpath+'NotoSans/NotoSans-SemiCondensed' NotoSans = fontpath+'NotoSans/NotoSans-SemiCondensed'
weatherfont = fontpath+'WeatherFont/weathericons-regular-webfont.ttf' weatherfont = fontpath+'WeatherFont/weathericons-regular-webfont.ttf'
"""Fonts sizes""" """Fontsizes"""
default_font_size = 18 default_fontsize = 18
agenda_font_size = 14 agenda_fontsize = 14
calendar_font_size = 16 calendar_fontsize = 14
rss_font_size = 14 rss_fontsize = 14
weather_font_size = 12 weather_fontsize = 12
"""Automatically select correct fonts to support set language""" """Automatically select correct fonts to support set language"""
if language in ['ja','zh','zh_tw','ko']: if language in ['ja','zh','zh_tw','ko']:
default = ImageFont.truetype(NotoSansCJK+'Regular.otf', default_font_size) default = ImageFont.truetype(NotoSansCJK+'Regular.otf', default_fontsize)
semi = ImageFont.truetype(NotoSansCJK+'Medium.otf', default_font_size) semi = ImageFont.truetype(NotoSansCJK+'Medium.otf', default_fontsize)
bold = ImageFont.truetype(NotoSansCJK+'Bold.otf', default_font_size) bold = ImageFont.truetype(NotoSansCJK+'Bold.otf', default_fontsize)
else: else:
default = ImageFont.truetype(NotoSans+'.ttf', default_font_size) default = ImageFont.truetype(NotoSans+'.ttf', default_fontsize)
semi = ImageFont.truetype(NotoSans+'Medium.ttf', default_font_size) semi = ImageFont.truetype(NotoSans+'Medium.ttf', default_fontsize)
bold = ImageFont.truetype(NotoSans+'SemiBold.ttf', default_font_size) bold = ImageFont.truetype(NotoSans+'SemiBold.ttf', default_fontsize)
w_font = ImageFont.truetype(weatherfont, weather_font_size) w_font = ImageFont.truetype(weatherfont, weather_fontsize)
"""Create image with given parameters""" """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 = Image.new('RGB', (display_width, display_height), background_colour)
image_col = Image.new('RGB', (display_width, display_height), 'white')
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
draw_col = ImageDraw.Draw(image_col)
"""Custom function to add text on an image""" """Custom function to add text on an image"""
def write_text(space_width, space_height, text, tuple, def write_text(space_width, space_height, text, tuple,
font=default, alignment='middle', autofit = False, fill_width = 1.0, font=default, alignment='middle', autofit = False, fill_width = 1.0,
fill_height = 0.8, colour = text_colour, rotation = None): 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: if autofit == True or fill_width != 1.0 or fill_height != 0.8:
size = 8 size = 8
font = ImageFont.truetype(font.path, size) font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getsize(text) 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): while text_width < int(space_width * fill_width) and text_height < int(space_height * fill_height):
size += 1 size += 1
font = ImageFont.truetype(font.path, size) font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getsize(text) text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
text_width, text_height = font.getsize(text) text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
while (text_width, text_height) > (space_width, space_height): while (text_width, text_height) > (space_width, space_height):
text=text[0:-1] text=text[0:-1]
text_width, text_height = font.getsize(text) text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
if alignment is "" or "middle" or None: if alignment is "" or "middle" or None:
x = int((space_width / 2) - (text_width / 2)) x = int((space_width / 2) - (text_width / 2))
if alignment is 'left': if alignment is 'left':
@ -115,7 +133,11 @@ def write_text(space_width, space_height, text, tuple,
ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font) ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font)
if rotation != None: if rotation != None:
space.rotate(rotation, expand = True) space.rotate(rotation, expand = True)
image.paste(space, tuple, space)
if colour == 'black' or 'white':
image.paste(space, tuple, space)
else:
image_col.paste(space, tuple, space)
def clear_image(section, colour = background_colour): def clear_image(section, colour = background_colour):
"""Clear the image""" """Clear the image"""
@ -124,6 +146,10 @@ def clear_image(section, colour = background_colour):
box = Image.new('RGB', (width, height), colour) box = Image.new('RGB', (width, height), colour)
image.paste(box, position) image.paste(box, position)
if three_colour_support == True:
image_col.paste(box, position)
def crop_image(input_image, section): def crop_image(input_image, section):
"""Crop an input image to the desired section""" """Crop an input image to the desired section"""
x1, y1 = 0, eval(section+'_offset') x1, y1 = 0, eval(section+'_offset')
@ -152,8 +178,8 @@ 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""" """Draws a square with round corners at position (x,y) from tuple"""
x, y, diameter = tuple[0], tuple[1], radius*2 x, y, diameter = tuple[0], tuple[1], radius*2
line_length = width - diameter line_length = width - diameter
p1, p2 = (x+radius, y), (x+radius+line_length, y) p1, p2 = (x+radius, y), (x+radius+line_length, y)
p3, p4 = (x+width, y+radius), (x+width, y+radius+line_length) p3, p4 = (x+width, y+radius), (x+width, y+radius+line_length)
p5, p6 = (p2[0], y+height), (p1[0], y+height) p5, p6 = (p2[0], y+height), (p1[0], y+height)
p7, p8 = (x, p4[1]), (x,p3[1]) p7, p8 = (x, p4[1]), (x,p3[1])
@ -161,15 +187,26 @@ def draw_square(tuple, radius, width, height, colour=text_colour, line_width=1):
c3, c4 = ((x+width)-diameter, y), (x+width, y+diameter) c3, c4 = ((x+width)-diameter, y), (x+width, y+diameter)
c5, c6 = ((x+width)-diameter, (y+height)-diameter), (x+width, y+height) c5, c6 = ((x+width)-diameter, (y+height)-diameter), (x+width, y+height)
c7, c8 = (x, (y+height)-diameter), (x+diameter, y+height) c7, c8 = (x, (y+height)-diameter), (x+diameter, y+height)
draw.line( (p1, p2) , fill=colour, width = line_width) if three_colour_support == True:
draw.line( (p3, p4) , fill=colour, width = line_width) draw_col.line( (p1, p2) , fill=colour, width = line_width)
draw.line( (p5, p6) , fill=colour, width = line_width) draw_col.line( (p3, p4) , fill=colour, width = line_width)
draw.line( (p7, p8) , fill=colour, width = line_width) draw_col.line( (p5, p6) , fill=colour, width = line_width)
draw.arc( (c1, c2) , 180, 270, fill=colour, width=line_width) draw_col.line( (p7, p8) , fill=colour, width = line_width)
draw.arc( (c3, c4) , 270, 360, fill=colour, width=line_width) draw_col.arc( (c1, c2) , 180, 270, fill=colour, width=line_width)
draw.arc( (c5, c6) , 0, 90, fill=colour, width=line_width) draw_col.arc( (c3, c4) , 270, 360, fill=colour, width=line_width)
draw.arc( (c7, c8) , 90, 180, 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(): def internet_available():
"""check if the internet is available""" """check if the internet is available"""
@ -206,23 +243,12 @@ def image_cleanup():
os.remove(temp_files) os.remove(temp_files)
print('Done') print('Done')
def split_colours(image): def optimise_colours(image, threshold=220):
if three_colour_support == True: buffer = numpy.array(image.convert('RGB'))
"""Split image into two, one for red pixels, the other for black pixels""" red, green = buffer[:, :, 0], buffer[:, :, 1]
buffer = numpy.array(image.convert('RGB')) buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0,0,0] #grey->black
red, green = buffer[:, :, 0], buffer[:, :, 1] image = Image.fromarray(buffer)
buffer_red, buffer_black = numpy.array(image), numpy.array(image) return image
buffer_red[numpy.logical_and(red >= 200, green <= 90)] = [0,0,0] #red->black
red1 = buffer_red[:,:,0]
buffer_red[red1 != 0] = [255,255,255] #white
red_im = Image.fromarray(buffer_red).convert('1',dither=True).rotate(270,expand=True)
buffer_black[numpy.logical_and(red <= 180, red == green)] = [0,0,0] #black
red2 = buffer_black[:,:,0]
buffer_black[red2 != 0] = [255,255,255] # white
black_im = Image.fromarray(buffer_black).convert('1', dither=True).rotate(270,expand=True)
return black_im, red_im
def calibrate_display(no_of_cycles): def calibrate_display(no_of_cycles):
"""How many times should each colour be calibrated? Default is 3""" """How many times should each colour be calibrated? Default is 3"""
@ -235,20 +261,46 @@ def calibrate_display(no_of_cycles):
print('----------Started calibration of E-Paper display----------') print('----------Started calibration of E-Paper display----------')
if 'colour' in model: if 'colour' in model:
for _ in range(no_of_cycles): for _ in range(no_of_cycles):
print('Calibrating black...') print('Calibrating...', end= ' ')
print('black...', end= ' ')
epaper.display(epaper.getbuffer(black), epaper.getbuffer(white)) epaper.display(epaper.getbuffer(black), epaper.getbuffer(white))
print('Calibrating red/yellow...') print('colour...', end = ' ')
epaper.display(epaper.getbuffer(white), epaper.getbuffer(black)) epaper.display(epaper.getbuffer(white), epaper.getbuffer(black))
print('Calibrating white...') print('white...')
epaper.display(epaper.getbuffer(white), epaper.getbuffer(white)) epaper.display(epaper.getbuffer(white), epaper.getbuffer(white))
print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles)) print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles))
else: else:
for _ in range(no_of_cycles): for _ in range(no_of_cycles):
print('Calibrating black...') print('Calibrating...', end= ' ')
print('black...', end = ' ')
epaper.display(epaper.getbuffer(black)) epaper.display(epaper.getbuffer(black))
print('Calibrating white...') print('white...')
epaper.display(epaper.getbuffer(white)), epaper.display(epaper.getbuffer(white)),
print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles)) print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles))
print('-----------Calibration complete----------') print('-----------Calibration complete----------')
epaper.sleep() 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

@ -35,7 +35,7 @@ body{
<input id="ical_urls" type="text" placeholder="'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics'"> <input id="ical_urls" type="text" placeholder="'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics'">
</div> </div>
<div class="field"> <div class="field">
<label>RSS-Feed URL/s in the following format: 'URL 1', 'URL 2', 'URL 3'</label> <label>RSS-Feed URL/s in the following format: 'URL 1', 'URL 2', 'URL 3'</label>
<input id="rss_urls" type="text" placeholder="'http://feeds.bbci.co.uk/news/world/rss.xml#'"> <input id="rss_urls" type="text" placeholder="'http://feeds.bbci.co.uk/news/world/rss.xml#'">
</div> </div>
@ -78,7 +78,6 @@ body{
<input id="api_key" type="text" placeholder=""> <input id="api_key" type="text" placeholder="">
</div> </div>
<div class="field"> <div class="field">
<label>Location (for weather data)</label> <label>Location (for weather data)</label>
<details class="ts accordion"> <details class="ts accordion">
@ -273,7 +272,7 @@ body{
<div class="field"> <div class="field">
<label>What should be displayed in the middle (main) section?</label> <label>What should be displayed in the middle (main) section?</label>
<div class="ts checkboxes"> <div class="ts checkboxes" id="cb_middle_section">
<div class="ts radio checkbox"> <div class="ts radio checkbox">
<input id="Calendar" type="radio" name="ms" checked> <input id="Calendar" type="radio" name="ms" checked>
<label for="Calendar">A monthly Calendar</label> <label for="Calendar">A monthly Calendar</label>
@ -282,11 +281,52 @@ body{
<input id="Agenda" type="radio" name="ms"> <input id="Agenda" type="radio" name="ms">
<label for="Agenda">Agenda of upcoming events</label> <label for="Agenda">Agenda of upcoming events</label>
</div> </div>
<div class="ts radio checkbox">
<input id="Image" type="radio" name="ms">
<label for="Image">An image</label>
</div>
<div class="ts radio checkbox"> <div class="ts radio checkbox">
<input id="middle_blank" type="radio" name="ms"> <input id="middle_blank" type="radio" name="ms">
<label for="middle_blank">Nothing</label> <label for="middle_blank">Nothing</label>
</div> </div>
</div> </div>
</div>
<div class="field" id="Image_Config" style="display:none;">
<div class="field">
<label>What is the URl or path of the image?</label>
<details class="ts accordion">
<summary>
<i class="dropdown icon"></i> Info
</summary>
<div class="content">
The following parameters will be substituted:
<ul>
<li><code>{model}</code> - substituted by the E-Paper model name.</li>
<li><code>{width}</code> - substituted by the panel width.</li>
<li><code>{height}</code> - substituted by the panel width.</li>
</ul>
</div>
</details>
<input id="image_path" type="text" placeholder="https://github.com/aceisace/Inky-Calendar/blob/master/Gallery/Inky-Calendar-logo.png?raw=true"/>
</div>
<div class="field">
<label>Do you want to send extra data while obtaining the image?</label>
<details class="ts accordion">
<summary>
<i class="dropdown icon"></i> Info
</summary>
<div class="content">
<p>Optional data. When specified, this data is sent as Json to the image url using POST.
<br/>This is useful for some dynamically generated images.
</p>
</div>
</details>
<textarea id="image_path_body" type="text" rows="4" placeholder='[
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
]'></textarea>
</div>
</div> </div>
<div class="field"> <div class="field">
@ -305,13 +345,14 @@ body{
<div class="content"> <div class="content">
<p>Instructions<br> <p>Instructions<br>
Insert your peesonal details and preferences and click on 'Generate'. Copy the downloaded file to the Raspberry Pi and place it in: '/home/pi/Inky-Calendar/settings/' (inside the settings folder within the Inky-Calendar folder. Lastly, reboot the Raspberry Pi to apply the changes. You can also manually run the software with: Insert your personal details and preferences and click on 'Generate'. Copy the downloaded file to the Raspberry Pi and place it in: '/home/pi/Inky-Calendar/settings/' (inside the settings folder within the Inky-Calendar folder. Lastly, reboot the Raspberry Pi to apply the changes. You can also manually run the software with:
python3 /home/pi/Inky-Calendar/modules/inkycal.py.</p> python3 /home/pi/Inky-Calendar/modules/inkycal.py.</p>
</div> </div>
</form> </form>
<br> <br>
<button class="ts primary button" onClick="generate();">Generate</button> <button class="ts primary button" onClick="generate(false);">Generate</button>
<button class="ts secondary button" onClick="generate(true);">Generate (as JSON, experimental)</button>
<br><br> <br><br>
<kbd>Developed by Toby Chui for Inky-Calendar Project, modified by aceisace. Licensed under MIT</kbd> <kbd>Developed by Toby Chui for Inky-Calendar Project, modified by aceisace. Licensed under MIT</kbd>
<details class="ts accordion"> <details class="ts accordion">
@ -333,9 +374,32 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
<br> <br>
<script> <script>
var template = 'ical_urls = [{ical_urls}]\nrss_feeds = [{rss_urls}]\nupdate_interval = "{update_interval}"\napi_key = "{api_key}"\nlocation = "{location}"\nweek_starts_on = "{week_starts_on}"\ncalibration_hours = [{calibration_hours}]\nmodel = "{model}"\nlanguage = "{language}"\nunits = "{units}"\nhours = "{hours}"\ntop_section = "{top_section}"\nmiddle_section = "{middle_section}"\nbottom_section = "{bottom_section}"'; var template = `ical_urls = [{ical_urls}]
rss_feeds = [{rss_urls}]
update_interval = "{update_interval}"
api_key = "{api_key}"
location = "{location}"
week_starts_on = "{week_starts_on}"
calibration_hours = [{calibration_hours}]
model = "{model}"
language = "{language}"
units = "{units}"
hours = "{hours}"
top_section = "{top_section}"
middle_section = "{middle_section}"
bottom_section = "{bottom_section}"
inkycal_image_path = "{image_path}"
inkycal_image_path_body = "{image_path_body}"`;
function generate(){ $('#cb_middle_section').change(function(){
if($('#Image').prop("checked")) {
$('#Image_Config').show();
} else {
$('#Image_Config').hide();
}
});
function generate(json){
var ical_urls = $("#ical_urls").val().trim(); var ical_urls = $("#ical_urls").val().trim();
if (ical_urls == ""){ if (ical_urls == ""){
ical_urls = $("#ical_urls").attr("placeholder"); ical_urls = $("#ical_urls").attr("placeholder");
@ -475,6 +539,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
if ($('#Agenda').is(':checked')){ if ($('#Agenda').is(':checked')){
middle_section = "inkycal_agenda"; middle_section = "inkycal_agenda";
} }
if ($('#Image').is(':checked')){
middle_section = "inkycal_image";
}
if ($('#middle_blank').is(':checked')){ if ($('#middle_blank').is(':checked')){
middle_section = ""; middle_section = "";
} }
@ -484,9 +551,18 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
bottom_section = ""; bottom_section = "";
} }
var image_path = $("#image_path").val().trim();
if (image_path == ""){
image_path = $("#image_path").attr("placeholder");
}
var image_path_body = $("#image_path").val().trim();
//console.log(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, model, language, units, hours, top_section, middle_section, bottom_section); //console.log(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, model, language, units, hours, top_section, middle_section, bottom_section);
createPythonSetting(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, model, language, units, hours, top_section, middle_section, bottom_section); if(json)
downloadSettingsAsJson(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, model, language, units, hours, top_section, middle_section, bottom_section, image_path, image_path_body)
else
createPythonSetting(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, model, language, units, hours, top_section, middle_section, bottom_section, image_path, image_path_body);
} }
function rk(content,key,value){ function rk(content,key,value){
@ -494,7 +570,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
return content.split("{" + key + "}").join(value); return content.split("{" + key + "}").join(value);
} }
function createPythonSetting(a,b,c,d,e,f,g,h,i,j,k,l,m,n){ function createPythonSetting(a,b,c,d,e,f,g,h,i,j,k,l,m,n, image_path, image_path_body){
var box = template; var box = template;
box = rk(box,"ical_urls",a); box = rk(box,"ical_urls",a);
box = rk(box,"rss_urls",b); box = rk(box,"rss_urls",b);
@ -510,7 +586,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
box = rk(box,"top_section",l); box = rk(box,"top_section",l);
box = rk(box,"middle_section",m); box = rk(box,"middle_section",m);
box = rk(box,"bottom_section",n); box = rk(box,"bottom_section",n);
box = rk(box,"image_path",image_path);
box = rk(box,"image_path_body",image_path_body);
var config = new Blob([box], {type : "text/plain"}); var config = new Blob([box], {type : "text/plain"});
var link = document.createElement('link'); var link = document.createElement('link');
@ -523,6 +600,112 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
} }
function TrimSingleQuotes(text){
return text.replace(/^'+/g,"").replace(/'+$/g,"")
}
function downloadSettingsAsJson(
ical_urls,
rss_urls,
update_interval,
api_key,
location,
week_starts_on,
calibration_hours,
model,
language,
units,
hours,
top_section,
middle_section,
bottom_section,
image_path,
image_path_body
) {
var result = {
"language" : language, // "en", "de", "fr", "jp" etc.
"units" : units, // "metric", "imperial"
"hours" : Number(hours), // 24, 12
"model" : model,
"update_interval" : Number(update_interval), // 10, 15, 20, 30, 60
"calibration_hours" : calibration_hours.split(",").map(function(x){ return Number(x);}), // Do not change unless you know what you are doing
"panels" : []
};
switch(top_section){
case "inkycal_weather":
result.panels.push(
{
"location" : "top",
"type" : "inkycal_weather",
"config" : {
"api_key" : api_key, //Your openweathermap API-KEY -> "api-key"
"location" : location //"City name, Country code"
}
}
)
break;
default:
break;
}
switch(middle_section){
case "inkycal_agenda":
case "inkycal_calendar":
result.panels.push(
{
"location" : "middle",
"type" : middle_section,
"config" : {
"week_starts_on" : week_starts_on, //"Sunday", "Monday"...
"ical_urls" : ical_urls.split().map(function(x){ return TrimSingleQuotes(x);})
}
}
)
break;
case "inkycal_image":
result.panels.push(
{
"location" : "middle",
"type" : middle_section,
"config" : {
"image_path" : TrimSingleQuotes(image_path),
"image_path_body" : image_path_body
}
}
)
break;
default:
break;
}
switch(bottom_section){
case "inkycal_rss":
result.panels.push(
{
"location" : "bottom",
"type" : bottom_section,
"config" : {
"rss_urls" : rss_urls.split().map(function(x){ return TrimSingleQuotes(x);})
}
}
)
break;
default:
break;
}
var config = new Blob([JSON.stringify(result, null, "\t")], {type : "text/json"});
var link = document.createElement('link');
link.href = window.URL.createObjectURL(config);
var a = document.createElement('A');
a.href = link.href;
a.download = link.href.substr(link.href.lastIndexOf('/') + 1);
document.body.appendChild(a);
$(a).attr('download','settings.jsonc');
a.click();
document.body.removeChild(a);
}
</script> </script>
</body> </body>

91
settings/settings.jsonc Normal file
View File

@ -0,0 +1,91 @@
{
"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

@ -23,6 +23,38 @@ bottom_section = "inkycal_rss" # "inkycal_rss"
# URLs should have this sign (") on both side -> "url1" # URLs should have this sign (") on both side -> "url1"
# If more than one URL is used, separate each one with a comma -> "url1", "url2" # 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""" """Supported E-Paper models"""
# epd_7_in_5_v2_colour # 7.5" high-res black-white-red/yellow # 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_v2 # 7.5" high-res black-white