Revert "modules refactoring + slight changes to folder structure"
This reverts commit 3fe9675bef
.
This commit is contained in:
parent
3fe9675bef
commit
c8786f6006
@ -1,6 +1,9 @@
|
|||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from inkycal.config import settings, layout
|
from inkycal.configuration.settings_parser import inkycal_settings as settings
|
||||||
|
from inkycal.display.layout import inkycal_layout as layout
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##modules = settings.which_modules()
|
##modules = settings.which_modules()
|
||||||
##for module in modules:
|
##for module in modules:
|
||||||
@ -9,28 +12,23 @@ from inkycal.config import settings, layout
|
|||||||
## #import_module('modules.'+module)
|
## #import_module('modules.'+module)
|
||||||
##print(module)
|
##print(module)
|
||||||
|
|
||||||
settings_file = "/home/pi/Desktop/Inkycal/inkycal/config/settings.json"
|
settings_file = '/home/pi/Desktop/settings.json'
|
||||||
|
|
||||||
|
|
||||||
class inkycal:
|
class inkycal:
|
||||||
"""Main class for inkycal
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, settings_file_path):
|
def __init__(self, settings_file_path):
|
||||||
"""Load settings file from path"""
|
"""Load settings file from path"""
|
||||||
|
|
||||||
# Load settings file
|
# Load settings file
|
||||||
self.settings = settings(settings_file_path)
|
self.settings = settings(settings_file_path)
|
||||||
self.model = self.settings.model
|
self.model = self.settings.model
|
||||||
|
|
||||||
def create_canvas(self):
|
def create_canvas(self):
|
||||||
"""Create a canvas with same size as the specified model"""
|
"""Create a canvas with same size as the specified model"""
|
||||||
|
|
||||||
self.layout = layout(model=self.model)
|
self.layout = layout(model=self.model)
|
||||||
|
|
||||||
def create_custom_canvas(self, width=None, height=None,
|
def create_custom_canvas(self, width=None, height=None,
|
||||||
supports_colour=False):
|
supports_colour=False):
|
||||||
"""Create a custom canvas by specifying height and width"""
|
"""Create a custom canvas by specifying height and width"""
|
||||||
|
|
||||||
self.layout = layout(model=model, width=width, height=height,
|
self.layout = layout(model=model, width=width, height=height,
|
||||||
supports_colour=supports_colour)
|
supports_colour=supports_colour)
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
from .Inkycal import inkycal
|
|
Binary file not shown.
Binary file not shown.
@ -1,4 +0,0 @@
|
|||||||
from .parser import settings
|
|
||||||
print('loaded settings')
|
|
||||||
from .layout import layout
|
|
||||||
print('loaded layout')
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,102 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Layout module for Inky-Calendar software.
|
|
||||||
Copyright by aceisace
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
class layout:
|
|
||||||
"""Page layout handling"""
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
def __init__(self, model=None, width=None, height=None,
|
|
||||||
supports_colour=False):
|
|
||||||
"""Initialize parameters for specified epaper model
|
|
||||||
Use model parameter to specify display OR
|
|
||||||
Crate a custom display with given width and height"""
|
|
||||||
|
|
||||||
self.background_colour = 'white'
|
|
||||||
self.text_colour = 'black'
|
|
||||||
|
|
||||||
if (model != None) and (width == None) and (height == None):
|
|
||||||
display_dimensions = {
|
|
||||||
'epd_7_in_5_v2_colour': (800, 400),
|
|
||||||
'epd_7_in_5_v2': (800, 400),
|
|
||||||
'epd_7_in_5_colour': (640, 384),
|
|
||||||
'epd_7_in_5': (640, 384),
|
|
||||||
'epd_5_in_83_colour': (600, 448),
|
|
||||||
'epd_5_in_83': (600, 448),
|
|
||||||
'epd_4_in_2_colour': (400, 300),
|
|
||||||
'epd_4_in_2': (400, 300),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.display_height, self.display_width = display_dimensions[model]
|
|
||||||
if 'colour' in model:
|
|
||||||
self.three_colour_support = True
|
|
||||||
|
|
||||||
elif width and height:
|
|
||||||
self.display_height = width
|
|
||||||
self.display_width = height
|
|
||||||
self.supports_colour = supports_colour
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("Can't create a layout without given sizes")
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.top_section_width = self.display_width
|
|
||||||
self.middle_section_width = self.display_width
|
|
||||||
self.bottom_section_width = self.display_width
|
|
||||||
self.create_sections()
|
|
||||||
|
|
||||||
def create_sections(self, top_section=0.10, middle_section=0.65,
|
|
||||||
bottom_section=0.25):
|
|
||||||
"""Allocate fixed percentage height for top and middle section
|
|
||||||
e.g. 0.2 = 20% (Leave empty for default values)
|
|
||||||
Set top/bottom_section to 0 to allocate more space for the middle section
|
|
||||||
"""
|
|
||||||
scale = lambda percentage: round(percentage * self.display_height)
|
|
||||||
|
|
||||||
if top_section == 0 or bottom_section == 0:
|
|
||||||
if top_section == 0:
|
|
||||||
self.top_section_height = 0
|
|
||||||
|
|
||||||
if bottom_section == 0:
|
|
||||||
self.bottom_section_height = 0
|
|
||||||
|
|
||||||
self.middle_section_height = scale(1 - top_section - bottom_section)
|
|
||||||
else:
|
|
||||||
if top_section + middle_section + bottom_section > 1.0:
|
|
||||||
print('All percentages should add up to max 100%, not more!')
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.top_section_height = scale(top_section)
|
|
||||||
self.middle_section_height = scale(middle_section)
|
|
||||||
self.bottom_section_height = (self.display_height -
|
|
||||||
self.top_section_height - self.middle_section_height)
|
|
||||||
|
|
||||||
logging.debug('top-section size: {} x {} px'.format(
|
|
||||||
self.top_section_width, self.top_section_height))
|
|
||||||
logging.debug('middle-section size: {} x {} px'.format(
|
|
||||||
self.middle_section_width, self.middle_section_height))
|
|
||||||
logging.debug('bottom-section size: {} x {} px'.format(
|
|
||||||
self.bottom_section_width, self.bottom_section_height))
|
|
||||||
|
|
||||||
|
|
||||||
def get_section_size(self, section):
|
|
||||||
"""Enter top/middle/bottom to get the size of the section as a tuple:
|
|
||||||
(width, height)"""
|
|
||||||
|
|
||||||
if section not in ['top','middle','bottom']:
|
|
||||||
raise Exception('Invalid section: ', section)
|
|
||||||
else:
|
|
||||||
if section == 'top':
|
|
||||||
size = (self.top_section_width, self.top_section_height)
|
|
||||||
elif section == 'middle':
|
|
||||||
size = (self.middle_section_width, self.middle_section_height)
|
|
||||||
elif section == 'bottom':
|
|
||||||
size = (self.bottom_section_width, self.bottom_section_height)
|
|
||||||
return size
|
|
@ -1,137 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Json settings parser. Currently in alpha!
|
|
||||||
Copyright by aceisace
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
from os import chdir #Ad-hoc
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# Check of jsmin can/should be used to parse jsonc settings file
|
|
||||||
# Remove check of fixed settings file location. Ask user to specify path
|
|
||||||
# to settings file
|
|
||||||
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
class settings:
|
|
||||||
"""Load and validate settings from the settings file"""
|
|
||||||
|
|
||||||
__supported_languages = ['en', 'de', 'ru', 'it', 'es', 'fr', 'el', 'sv', 'nl',
|
|
||||||
'pl', 'ua', 'nb', 'vi', 'zh_tw', 'zh-cn', 'ja', 'ko']
|
|
||||||
__supported_units = ['metric', 'imperial']
|
|
||||||
__supported_hours = [12, 24]
|
|
||||||
__supported_display_orientation = ['normal', 'upside_down']
|
|
||||||
__supported_models = [
|
|
||||||
'epd_7_in_5_v2_colour', 'epd_7_in_5_v2',
|
|
||||||
'epd_7_in_5_colour', 'epd_7_in_5',
|
|
||||||
'epd_5_in_83_colour','epd_5_in_83',
|
|
||||||
'epd_4_in_2_colour', 'epd_4_in_2'
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, settings_file_path):
|
|
||||||
"""Load settings from path (folder or settings.json file)"""
|
|
||||||
try:
|
|
||||||
if settings_file_path.endswith('settings.json'):
|
|
||||||
folder = settings_file_path.split('/settings.json')[0]
|
|
||||||
else:
|
|
||||||
folder = settings_file_path
|
|
||||||
|
|
||||||
chdir(folder)
|
|
||||||
with open("settings.json") as file:
|
|
||||||
self.raw_settings = json.load(file)
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
print('No settings file found in specified location')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.language = self.raw_settings['language']
|
|
||||||
if self.language not in self.__supported_languages or type(self.language) != str:
|
|
||||||
print('Unsupported language: {}!. Switching to english'.format(language))
|
|
||||||
self.language = 'en'
|
|
||||||
|
|
||||||
|
|
||||||
self.units = self.raw_settings['units']
|
|
||||||
if self.units not in self.__supported_units or type(self.units) != str:
|
|
||||||
print('Units ({}) not supported, using metric units.'.format(units))
|
|
||||||
self.units = 'metric'
|
|
||||||
|
|
||||||
|
|
||||||
self.hours = self.raw_settings['hours']
|
|
||||||
if self.hours not in self.__supported_hours or type(self.hours) != int:
|
|
||||||
print('Selected hours: {} not supported, using 24-hours'.format(hours))
|
|
||||||
self.hours = '24'
|
|
||||||
|
|
||||||
|
|
||||||
self.model = self.raw_settings['model']
|
|
||||||
if self.model not in self.__supported_models or type(self.model) != str:
|
|
||||||
print('Model: {} not supported. Please select a valid option'.format(model))
|
|
||||||
print('Switching to 7.5" ePaper black-white (v1) (fallback)')
|
|
||||||
self.model = 'epd_7_in_5'
|
|
||||||
|
|
||||||
|
|
||||||
self.calibration_hours = self.raw_settings['calibration_hours']
|
|
||||||
if not self.calibration_hours or type(self.calibration_hours) != list:
|
|
||||||
print('Invalid calibration hours: {}'.format(calibration_hours))
|
|
||||||
print('Using default option, 0am,12am,6pm')
|
|
||||||
self.calibration_hours = [0,12,18]
|
|
||||||
|
|
||||||
|
|
||||||
self.display_orientation = self.raw_settings['display_orientation']
|
|
||||||
if self.display_orientation not in self.__supported_display_orientation or type(
|
|
||||||
self.display_orientation) != str:
|
|
||||||
print('Invalid ({}) display orientation.'.format(display_orientation))
|
|
||||||
print('Switching to default orientation, normal-mode')
|
|
||||||
self.display_orientation = 'normal'
|
|
||||||
|
|
||||||
### Check if empty, If empty, set to none
|
|
||||||
for sections in self.raw_settings['panels']:
|
|
||||||
|
|
||||||
if sections['location'] == 'top':
|
|
||||||
self.top_section = sections['type']
|
|
||||||
self.top_section_config = sections['config']
|
|
||||||
|
|
||||||
elif sections['location'] == 'middle':
|
|
||||||
self.middle_section = sections['type']
|
|
||||||
self.middle_section_config = sections['config']
|
|
||||||
|
|
||||||
elif sections['location'] == 'bottom':
|
|
||||||
self.bottom_section = sections['type']
|
|
||||||
self.bottom_section_config = sections['config']
|
|
||||||
|
|
||||||
|
|
||||||
print('settings loaded')
|
|
||||||
except Exception as e:
|
|
||||||
print(e.reason)
|
|
||||||
|
|
||||||
def module_init(self, module_name):
|
|
||||||
"""Get all data from settings file by providing the module name"""
|
|
||||||
if module_name == self.top_section:
|
|
||||||
config = self.top_section_config
|
|
||||||
elif module_name == self.middle_section:
|
|
||||||
config = self.middle_section_config
|
|
||||||
elif module_name == self.bottom_section:
|
|
||||||
config = self.bottom_section_config
|
|
||||||
else:
|
|
||||||
print('Invalid module name!')
|
|
||||||
config = None
|
|
||||||
|
|
||||||
for module in self.raw_settings['panels']:
|
|
||||||
if module_name == module['type']:
|
|
||||||
location = module['location']
|
|
||||||
|
|
||||||
return config, location
|
|
||||||
|
|
||||||
def which_modules(self):
|
|
||||||
"""Returns a list of modules (from settings file) which should be loaded
|
|
||||||
on start"""
|
|
||||||
lst = [self.top_section, self.middle_section, self.bottom_section]
|
|
||||||
return lst
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print('running settings parser as standalone...')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"language": "en",
|
|
||||||
"units": "metric",
|
|
||||||
"hours": 24,
|
|
||||||
"model": "epd_7_in_5_v2_colour",
|
|
||||||
"update_interval": 60,
|
|
||||||
"calibration_hours": [
|
|
||||||
0,
|
|
||||||
12,
|
|
||||||
18
|
|
||||||
],
|
|
||||||
"display_orientation": "normal",
|
|
||||||
"panels": [
|
|
||||||
{
|
|
||||||
"location": "top",
|
|
||||||
"type": "inkycal_weather",
|
|
||||||
"config": {
|
|
||||||
"api_key": "topsecret",
|
|
||||||
"location": "Stuttgart, DE"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"location": "middle",
|
|
||||||
"type": "inkycal_calendar",
|
|
||||||
"config": {
|
|
||||||
"week_starts_on": "Monday",
|
|
||||||
"ical_urls": [
|
|
||||||
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"location": "bottom",
|
|
||||||
"type": "inkycal_rss",
|
|
||||||
"config": {
|
|
||||||
"rss_urls": [
|
|
||||||
"http://feeds.bbci.co.uk/news/world/rss.xml#"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
from .functions import *
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,362 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Inky-Calendar custom-functions for ease-of-use
|
|
||||||
|
|
||||||
Copyright by aceisace
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
|
||||||
from urllib.request import urlopen
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
##from glob import glob
|
|
||||||
##import importlib
|
|
||||||
##import subprocess as subp
|
|
||||||
##import numpy
|
|
||||||
##import arrow
|
|
||||||
##from pytz import timezone
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##"""Set some display parameters"""
|
|
||||||
##driver = importlib.import_module('drivers.'+model)
|
|
||||||
|
|
||||||
# Get the path to the Inky-Calendar folder
|
|
||||||
top_level = os.path.dirname(
|
|
||||||
os.path.abspath(os.path.dirname(__file__))).split('/inkycal')[0]
|
|
||||||
|
|
||||||
# Get path of 'fonts' and 'images' folders within Inky-Calendar folder
|
|
||||||
fonts_location = top_level + '/fonts/'
|
|
||||||
images = top_level + '/images/'
|
|
||||||
|
|
||||||
# Get available fonts within fonts folder
|
|
||||||
fonts = {}
|
|
||||||
|
|
||||||
for path,dirs,files in os.walk(fonts_location):
|
|
||||||
for filename in files:
|
|
||||||
if filename.endswith('.otf'):
|
|
||||||
name = filename.split('.otf')[0]
|
|
||||||
fonts[name] = os.path.join(path, filename)
|
|
||||||
|
|
||||||
if filename.endswith('.ttf'):
|
|
||||||
name = filename.split('.ttf')[0]
|
|
||||||
fonts[name] = os.path.join(path, filename)
|
|
||||||
|
|
||||||
del name, filename, files
|
|
||||||
|
|
||||||
available_fonts = [key for key,values in fonts.items()]
|
|
||||||
|
|
||||||
def get_fonts():
|
|
||||||
"""Print all available fonts by name"""
|
|
||||||
for fonts in available_fonts:
|
|
||||||
print(fonts)
|
|
||||||
|
|
||||||
|
|
||||||
def get_system_tz():
|
|
||||||
"""Get the timezone set by the system"""
|
|
||||||
try:
|
|
||||||
local_tz = time.tzname[1]
|
|
||||||
except:
|
|
||||||
print('System timezone could not be parsed!')
|
|
||||||
print('Please set timezone manually!. Setting timezone to None...')
|
|
||||||
local_tz = None
|
|
||||||
return local_tz
|
|
||||||
|
|
||||||
|
|
||||||
def auto_fontsize(font, max_height):
|
|
||||||
"""Adjust the fontsize to fit 80% of max_height
|
|
||||||
returns the font object with modified size"""
|
|
||||||
fontsize = font.getsize('hg')[1]
|
|
||||||
while font.getsize('hg')[1] <= (max_height * 0.80):
|
|
||||||
fontsize += 1
|
|
||||||
font = ImageFont.truetype(font.path, fontsize)
|
|
||||||
return font
|
|
||||||
|
|
||||||
|
|
||||||
def write(image, xy, box_size, text, font=None, **kwargs):
|
|
||||||
"""Write text on specified image
|
|
||||||
image = on which image should the text be added?
|
|
||||||
xy = xy-coordinates as tuple -> (x,y)
|
|
||||||
box_size = size of text-box -> (width,height)
|
|
||||||
text = string (what to write)
|
|
||||||
font = which font to use
|
|
||||||
"""
|
|
||||||
allowed_kwargs = ['alignment', 'autofit', 'colour', 'rotation'
|
|
||||||
'fill_width', 'fill_height']
|
|
||||||
|
|
||||||
# Validate kwargs
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
if key not in allowed_kwargs:
|
|
||||||
print('{0} does not exist'.format(key))
|
|
||||||
|
|
||||||
# Set kwargs if given, it not, use defaults
|
|
||||||
alignment = kwargs['alignment'] if 'alignment' in kwargs else 'center'
|
|
||||||
autofit = kwargs['autofit'] if 'autofit' in kwargs else False
|
|
||||||
fill_width = kwargs['fill_width'] if 'fill_width' in kwargs else 1.0
|
|
||||||
fill_height = kwargs['fill_height'] if 'fill_height' in kwargs else 0.8
|
|
||||||
colour = kwargs['colour'] if 'colour' in kwargs else 'black'
|
|
||||||
rotation = kwargs['rotation'] if 'rotation' in kwargs else None
|
|
||||||
|
|
||||||
x,y = xy
|
|
||||||
box_width, box_height = box_size
|
|
||||||
|
|
||||||
# Increase fontsize to fit specified height and width of text box
|
|
||||||
if (autofit == True) or (fill_width != 1.0) or (fill_height != 0.8):
|
|
||||||
size = 8
|
|
||||||
font = ImageFont.truetype(font.path, size)
|
|
||||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
|
||||||
while (text_width < int(box_width * fill_width) and
|
|
||||||
text_height < int(box_height * fill_height)):
|
|
||||||
size += 1
|
|
||||||
font = ImageFont.truetype(font.path, size)
|
|
||||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
|
||||||
|
|
||||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
|
||||||
|
|
||||||
|
|
||||||
# Truncate text if text is too long so it can fit inside the box
|
|
||||||
if (text_width, text_height) > (box_width, box_height):
|
|
||||||
logging.debug('text too big for space, truncating now...')
|
|
||||||
while (text_width, text_height) > (box_width, box_height):
|
|
||||||
text=text[0:-1]
|
|
||||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
|
||||||
logging.debug('truncated text:', text)
|
|
||||||
|
|
||||||
# Align text to desired position
|
|
||||||
if alignment == "center" or None:
|
|
||||||
x = int((box_width / 2) - (text_width / 2))
|
|
||||||
elif alignment == 'left':
|
|
||||||
x = 0
|
|
||||||
elif alignment == 'right':
|
|
||||||
x = int(box_width - text_width)
|
|
||||||
|
|
||||||
y = int((box_height / 2) - (text_height / 2))
|
|
||||||
|
|
||||||
# Draw the text in the text-box
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
space = Image.new('RGBA', (box_width, box_height))
|
|
||||||
ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font)
|
|
||||||
## space = Image.new('RGBA', (box_width, box_height), color= 'red')
|
|
||||||
## ImageDraw.Draw(space).text((x, y), text, fill='white', font=font)
|
|
||||||
|
|
||||||
if rotation != None:
|
|
||||||
space.rotate(rotation, expand = True)
|
|
||||||
|
|
||||||
# Update only region with text (add text with transparent background)
|
|
||||||
image.paste(space, xy, space)
|
|
||||||
|
|
||||||
|
|
||||||
def text_wrap(text, font=None, max_width = None):
|
|
||||||
"""Split long text (text-wrapping). Returns a list"""
|
|
||||||
lines = []
|
|
||||||
if font.getsize(text)[0] < max_width:
|
|
||||||
lines.append(text)
|
|
||||||
else:
|
|
||||||
words = text.split(' ')
|
|
||||||
i = 0
|
|
||||||
while i < len(words):
|
|
||||||
line = ''
|
|
||||||
while i < len(words) and font.getsize(line + words[i])[0] <= max_width:
|
|
||||||
line = line + words[i] + " "
|
|
||||||
i += 1
|
|
||||||
if not line:
|
|
||||||
line = words[i]
|
|
||||||
i += 1
|
|
||||||
lines.append(line)
|
|
||||||
return lines
|
|
||||||
|
|
||||||
|
|
||||||
def internet_available():
|
|
||||||
"""check if the internet is available"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
urlopen('https://google.com',timeout=5)
|
|
||||||
return True
|
|
||||||
except URLError as err:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def draw_square(image, xy, size, radius=5, thickness=2):
|
|
||||||
"""Draws a square with round corners at (x,y)
|
|
||||||
xy = position e.g: (5,10)
|
|
||||||
size = size of square (width, height)
|
|
||||||
radius: corner radius
|
|
||||||
thickness = border thickness
|
|
||||||
"""
|
|
||||||
|
|
||||||
x, y, diameter = xy[0], xy[1], radius*2
|
|
||||||
colour='black'
|
|
||||||
width, height = size
|
|
||||||
lenght = width - diameter
|
|
||||||
|
|
||||||
# Set coordinates for round square
|
|
||||||
p1, p2 = (x+radius, y), (x+radius+lenght, y)
|
|
||||||
p3, p4 = (x+width, y+radius), (x+width, y+radius+lenght)
|
|
||||||
p5, p6 = (p2[0], y+height), (p1[0], y+height)
|
|
||||||
p7, p8 = (x, p4[1]), (x,p3[1])
|
|
||||||
c1, c2 = (x,y), (x+diameter, y+diameter)
|
|
||||||
c3, c4 = ((x+width)-diameter, y), (x+width, y+diameter)
|
|
||||||
c5, c6 = ((x+width)-diameter, (y+height)-diameter), (x+width, y+height)
|
|
||||||
c7, c8 = (x, (y+height)-diameter), (x+diameter, y+height)
|
|
||||||
|
|
||||||
# Draw lines and arcs, creating a square with round corners
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
|
|
||||||
draw.line( (p1, p2) , fill=colour, width = thickness)
|
|
||||||
draw.line( (p3, p4) , fill=colour, width = thickness)
|
|
||||||
draw.line( (p5, p6) , fill=colour, width = thickness)
|
|
||||||
draw.line( (p7, p8) , fill=colour, width = thickness)
|
|
||||||
draw.arc( (c1, c2) , 180, 270, fill=colour, width=thickness)
|
|
||||||
draw.arc( (c3, c4) , 270, 360, fill=colour, width=thickness)
|
|
||||||
draw.arc( (c5, c6) , 0, 90, fill=colour, width=thickness)
|
|
||||||
draw.arc( (c7, c8) , 90, 180, fill=colour, width=thickness)
|
|
||||||
|
|
||||||
|
|
||||||
##"""Custom function to add text on an image"""
|
|
||||||
##def write_text(space_width, space_height, text, tuple,
|
|
||||||
## font=default, alignment='middle', autofit = False, fill_width = 1.0,
|
|
||||||
## fill_height = 0.8, colour = text_colour, rotation = None):
|
|
||||||
## """tuple refers to (x,y) position on display"""
|
|
||||||
## if autofit == True or fill_width != 1.0 or fill_height != 0.8:
|
|
||||||
## size = 8
|
|
||||||
## font = ImageFont.truetype(font.path, size)
|
|
||||||
## text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
|
||||||
## while text_width < int(space_width * fill_width) and text_height < int(space_height * fill_height):
|
|
||||||
## size += 1
|
|
||||||
## font = ImageFont.truetype(font.path, size)
|
|
||||||
## text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
|
||||||
##
|
|
||||||
## text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
|
||||||
##
|
|
||||||
## while (text_width, text_height) > (space_width, space_height):
|
|
||||||
## text=text[0:-1]
|
|
||||||
## text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
|
||||||
## if alignment is "" or "middle" or None:
|
|
||||||
## x = int((space_width / 2) - (text_width / 2))
|
|
||||||
## if alignment is 'left':
|
|
||||||
## x = 0
|
|
||||||
## if font != w_font:
|
|
||||||
## y = int((space_height / 2) - (text_height / 1.7))
|
|
||||||
## else:
|
|
||||||
## y = y = int((space_height / 2) - (text_height / 2))
|
|
||||||
##
|
|
||||||
## space = Image.new('RGBA', (space_width, space_height))
|
|
||||||
## ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font)
|
|
||||||
## if rotation != None:
|
|
||||||
## space.rotate(rotation, expand = True)
|
|
||||||
##
|
|
||||||
## if colour == 'black' or 'white':
|
|
||||||
## image.paste(space, tuple, space)
|
|
||||||
## else:
|
|
||||||
## image_col.paste(space, tuple, space)
|
|
||||||
|
|
||||||
|
|
||||||
"""Not required anymore?"""
|
|
||||||
##def clear_image(section, colour = background_colour):
|
|
||||||
## """Clear the image"""
|
|
||||||
## width, height = eval(section+'_width'), eval(section+'_height')
|
|
||||||
## position = (0, eval(section+'_offset'))
|
|
||||||
## box = Image.new('RGB', (width, height), colour)
|
|
||||||
## image.paste(box, position)
|
|
||||||
##
|
|
||||||
## if three_colour_support == True:
|
|
||||||
## image_col.paste(box, position)
|
|
||||||
|
|
||||||
|
|
||||||
"""Not required anymore?"""
|
|
||||||
##def crop_image(input_image, section):
|
|
||||||
## """Crop an input image to the desired section"""
|
|
||||||
## x1, y1 = 0, eval(section+'_offset')
|
|
||||||
## x2, y2 = eval(section+'_width'), y1 + eval(section+'_height')
|
|
||||||
## image = input_image.crop((x1,y1,x2,y2))
|
|
||||||
## return image
|
|
||||||
|
|
||||||
|
|
||||||
"""Not required anymore?"""
|
|
||||||
##def fix_ical(ical_url):
|
|
||||||
## """Use iCalendars in compatability mode (without alarms)"""
|
|
||||||
## ical = str(urlopen(ical_url).read().decode())
|
|
||||||
## beginAlarmIndex = 0
|
|
||||||
## while beginAlarmIndex >= 0:
|
|
||||||
## beginAlarmIndex = ical.find('BEGIN:VALARM')
|
|
||||||
## if beginAlarmIndex >= 0:
|
|
||||||
## endAlarmIndex = ical.find('END:VALARM')
|
|
||||||
## ical = ical[:beginAlarmIndex] + ical[endAlarmIndex+12:]
|
|
||||||
## return ical
|
|
||||||
|
|
||||||
|
|
||||||
"""Not required anymore?"""
|
|
||||||
##def image_cleanup():
|
|
||||||
## """Delete all files in the image folder"""
|
|
||||||
## print('Cleanup of previous images...', end = '')
|
|
||||||
## for temp_files in glob(image_path+'*'):
|
|
||||||
## os.remove(temp_files)
|
|
||||||
## print('Done')
|
|
||||||
|
|
||||||
|
|
||||||
##def optimise_colours(image, threshold=220):
|
|
||||||
## buffer = numpy.array(image.convert('RGB'))
|
|
||||||
## red, green = buffer[:, :, 0], buffer[:, :, 1]
|
|
||||||
## buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0,0,0] #grey->black
|
|
||||||
## image = Image.fromarray(buffer)
|
|
||||||
## return image
|
|
||||||
##
|
|
||||||
##def calibrate_display(no_of_cycles):
|
|
||||||
## """How many times should each colour be calibrated? Default is 3"""
|
|
||||||
## epaper = driver.EPD()
|
|
||||||
## epaper.init()
|
|
||||||
##
|
|
||||||
## white = Image.new('1', (display_width, display_height), 'white')
|
|
||||||
## black = Image.new('1', (display_width, display_height), 'black')
|
|
||||||
##
|
|
||||||
## print('----------Started calibration of E-Paper display----------')
|
|
||||||
## if 'colour' in model:
|
|
||||||
## for _ in range(no_of_cycles):
|
|
||||||
## print('Calibrating...', end= ' ')
|
|
||||||
## print('black...', end= ' ')
|
|
||||||
## epaper.display(epaper.getbuffer(black), epaper.getbuffer(white))
|
|
||||||
## print('colour...', end = ' ')
|
|
||||||
## epaper.display(epaper.getbuffer(white), epaper.getbuffer(black))
|
|
||||||
## print('white...')
|
|
||||||
## epaper.display(epaper.getbuffer(white), epaper.getbuffer(white))
|
|
||||||
## print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles))
|
|
||||||
## else:
|
|
||||||
## for _ in range(no_of_cycles):
|
|
||||||
## print('Calibrating...', end= ' ')
|
|
||||||
## print('black...', end = ' ')
|
|
||||||
## epaper.display(epaper.getbuffer(black))
|
|
||||||
## print('white...')
|
|
||||||
## epaper.display(epaper.getbuffer(white)),
|
|
||||||
## print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles))
|
|
||||||
##
|
|
||||||
## print('-----------Calibration complete----------')
|
|
||||||
## epaper.sleep()
|
|
||||||
|
|
||||||
|
|
||||||
"""Not required anymore?"""
|
|
||||||
##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)
|
|
@ -0,0 +1,2 @@
|
|||||||
|
from .layout import inkycal_layout
|
||||||
|
print('imported layout class')
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,7 +5,7 @@ RSS module for Inky-Calendar Project
|
|||||||
Copyright by aceisace
|
Copyright by aceisace
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from inkycal.custom import *
|
from inkycal.render.functions import *
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -20,13 +20,7 @@ size = (384, 160)
|
|||||||
config = {'rss_urls': ['http://feeds.bbci.co.uk/news/world/rss.xml#']}
|
config = {'rss_urls': ['http://feeds.bbci.co.uk/news/world/rss.xml#']}
|
||||||
|
|
||||||
|
|
||||||
class rss:
|
class inkycal_rss:
|
||||||
"""RSS class
|
|
||||||
parses rss feeds from given urls and writes them on the image
|
|
||||||
"""
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
def __init__(self, section_size, section_config):
|
def __init__(self, section_size, section_config):
|
||||||
"""Initialize inkycal_rss module"""
|
"""Initialize inkycal_rss module"""
|
||||||
@ -37,8 +31,8 @@ class rss:
|
|||||||
self.background_colour = 'white'
|
self.background_colour = 'white'
|
||||||
self.font_colour = 'black'
|
self.font_colour = 'black'
|
||||||
self.fontsize = 12
|
self.fontsize = 12
|
||||||
self.font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'],
|
self.font = ImageFont.truetype(
|
||||||
size = self.fontsize)
|
fonts['NotoSans-SemiCondensed'], size = self.fontsize)
|
||||||
self.padding_x = 0.02
|
self.padding_x = 0.02
|
||||||
self.padding_y = 0.05
|
self.padding_y = 0.05
|
||||||
print('{0} loaded'.format(self.name))
|
print('{0} loaded'.format(self.name))
|
||||||
@ -72,20 +66,11 @@ class rss:
|
|||||||
im_width = int(self.width - (self.width * 2 * self.padding_x))
|
im_width = int(self.width - (self.width * 2 * self.padding_x))
|
||||||
im_height = int(self.height - (self.height * 2 * self.padding_y))
|
im_height = int(self.height - (self.height * 2 * self.padding_y))
|
||||||
im_size = im_width, im_height
|
im_size = im_width, im_height
|
||||||
logging.info('image size: {} x {} px'.format(im_width, im_height))
|
|
||||||
|
|
||||||
# Create an image for black pixels and one for coloured pixels
|
# Create an image for black pixels and one for coloured pixels
|
||||||
im_black = Image.new('RGB', size = im_size, color = self.background_colour)
|
im_black = Image.new('RGB', size = im_size, color = self.background_colour)
|
||||||
im_colour = Image.new('RGB', size = im_size, color = 'white')
|
im_colour = Image.new('RGB', size = im_size, color = 'white')
|
||||||
|
|
||||||
# Check if internet is available
|
|
||||||
if internet_available() == True:
|
|
||||||
logging.info('Connection test passed')
|
|
||||||
else:
|
|
||||||
logging.error('Network could not be reached :/')
|
|
||||||
raise Exception('Network could not be reached :(')
|
|
||||||
|
|
||||||
|
|
||||||
# Set some parameters for formatting rss feeds
|
# Set some parameters for formatting rss feeds
|
||||||
line_spacing = 1
|
line_spacing = 1
|
||||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||||
@ -99,6 +84,11 @@ class rss:
|
|||||||
line_positions = [
|
line_positions = [
|
||||||
(0, spacing_top + _ * line_height ) for _ in range(max_lines)]
|
(0, spacing_top + _ * line_height ) for _ in range(max_lines)]
|
||||||
|
|
||||||
|
if internet_available() == True:
|
||||||
|
print('Connection test passed')
|
||||||
|
else:
|
||||||
|
# write 'No network available :('
|
||||||
|
raise Exception('Network could not be reached :(')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create list containing all rss-feeds from all rss-feed urls
|
# Create list containing all rss-feeds from all rss-feed urls
|
||||||
|
@ -1,479 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Weather module for Inky-Calendar software.
|
|
||||||
Copyright by aceisace
|
|
||||||
"""
|
|
||||||
|
|
||||||
from inkycal.custom import *
|
|
||||||
import pyowm
|
|
||||||
import math, decimal
|
|
||||||
import arrow
|
|
||||||
from locale import getdefaultlocale as sys_locale
|
|
||||||
|
|
||||||
config = {'api_key': 'top-secret', 'location': 'Stuttgart, DE'}
|
|
||||||
size = (384,80)
|
|
||||||
|
|
||||||
class weather:
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
def __init__(self, section_size, section_config):
|
|
||||||
"""Initialize inkycal_weather module"""
|
|
||||||
self.name = os.path.basename(__file__).split('.py')[0]
|
|
||||||
self.config = section_config
|
|
||||||
self.width, self.height = section_size
|
|
||||||
self.background_colour = 'white'
|
|
||||||
self.font_colour = 'black'
|
|
||||||
self.fontsize = 12
|
|
||||||
self.font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'],
|
|
||||||
size = self.fontsize)
|
|
||||||
self.padding_x = 0.02
|
|
||||||
self.padding_y = 0.05
|
|
||||||
|
|
||||||
# Weather-specfic options
|
|
||||||
self.owm = pyowm.OWM(config['api_key'])
|
|
||||||
self.units = 'metric' # metric # imperial
|
|
||||||
self.hour_format = '12' # 12 #24
|
|
||||||
self.timezone = get_system_tz()
|
|
||||||
self.round_temperature = True
|
|
||||||
self.round_windspeed = True
|
|
||||||
self.use_beaufort = True
|
|
||||||
self.show_wind_direction = False
|
|
||||||
self.use_wind_direction_icon = False
|
|
||||||
self.forecast_interval = 'hourly' # daily # hourly
|
|
||||||
self.locale = sys_locale()[0]
|
|
||||||
self.weatherfont = ImageFont.truetype(fonts['weathericons-regular-webfont'],
|
|
||||||
size = self.fontsize)
|
|
||||||
|
|
||||||
print('{0} loaded'.format(self.name))
|
|
||||||
|
|
||||||
def set(self, **kwargs):
|
|
||||||
"""Manually set some parameters of this module"""
|
|
||||||
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
if key in self.__dict__:
|
|
||||||
setattr(self, key, value)
|
|
||||||
else:
|
|
||||||
print('{0} does not exist'.format(key))
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get(self, **kwargs):
|
|
||||||
"""Manually get some parameters of this module"""
|
|
||||||
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
if key in self.__dict__:
|
|
||||||
getattr(self, key, value)
|
|
||||||
else:
|
|
||||||
print('{0} does not exist'.format(key))
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_options(self):
|
|
||||||
"""Get all options which can be changed"""
|
|
||||||
|
|
||||||
return self.__dict__
|
|
||||||
|
|
||||||
|
|
||||||
def generate_image(self):
|
|
||||||
"""Generate image for this module"""
|
|
||||||
|
|
||||||
# Define new image size with respect to padding
|
|
||||||
im_width = int(self.width - (self.width * 2 * self.padding_x))
|
|
||||||
im_height = int(self.height - (self.height * 2 * self.padding_y))
|
|
||||||
im_size = im_width, im_height
|
|
||||||
logging.info('image size: {} x {} px'.format(im_width, im_height))
|
|
||||||
|
|
||||||
# Create an image for black pixels and one for coloured pixels
|
|
||||||
im_black = Image.new('RGB', size = im_size, color = self.background_colour)
|
|
||||||
im_colour = Image.new('RGB', size = im_size, color = 'white')
|
|
||||||
|
|
||||||
# Check if internet is available
|
|
||||||
if internet_available() == True:
|
|
||||||
logging.info('Connection test passed')
|
|
||||||
else:
|
|
||||||
raise Exception('Network could not be reached :(')
|
|
||||||
|
|
||||||
def get_moon_phase():
|
|
||||||
"""Calculate the current (approximate) moon phase"""
|
|
||||||
|
|
||||||
dec = decimal.Decimal
|
|
||||||
diff = now - arrow.get(2001, 1, 1)
|
|
||||||
days = dec(diff.days) + (dec(diff.seconds) / dec(86400))
|
|
||||||
lunations = dec("0.20439731") + (days * dec("0.03386319269"))
|
|
||||||
position = lunations % dec(1)
|
|
||||||
index = math.floor((position * dec(8)) + dec("0.5"))
|
|
||||||
return {0: '\uf095',1: '\uf099',2: '\uf09c',3: '\uf0a0',
|
|
||||||
4: '\uf0a3',5: '\uf0a7',6: '\uf0aa',7: '\uf0ae' }[int(index) & 7]
|
|
||||||
|
|
||||||
|
|
||||||
def is_negative(temp):
|
|
||||||
"""Check if temp is below freezing point of water (0°C/30°F)
|
|
||||||
returns True if temp below freezing point, else False"""
|
|
||||||
answer = False
|
|
||||||
|
|
||||||
if temp_unit == 'celsius' and round(float(temp.split('°')[0])) <= 0:
|
|
||||||
answer = True
|
|
||||||
elif temp_unit == 'fahrenheit' and round(float(temp.split('°')[0])) <= 0:
|
|
||||||
answer = True
|
|
||||||
return answer
|
|
||||||
|
|
||||||
# Lookup-table for weather icons and weather codes
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def draw_icon(image, xy, box_size, icon, rotation = None):
|
|
||||||
"""Custom function to add icons of weather font on image
|
|
||||||
image = on which image should the text be added?
|
|
||||||
xy = xy-coordinates as tuple -> (x,y)
|
|
||||||
box_size = size of text-box -> (width,height)
|
|
||||||
icon = icon-unicode, looks this up in weathericons dictionary
|
|
||||||
"""
|
|
||||||
x,y = xy
|
|
||||||
box_width, box_height = box_size
|
|
||||||
text = icon
|
|
||||||
font = self.weatherfont
|
|
||||||
|
|
||||||
# Increase fontsize to fit specified height and width of text box
|
|
||||||
size = 8
|
|
||||||
font = ImageFont.truetype(font.path, size)
|
|
||||||
text_width, text_height = font.getsize(text)
|
|
||||||
while (text_width < int(box_width * 0.9) and
|
|
||||||
text_height < int(box_height * 0.9)):
|
|
||||||
size += 1
|
|
||||||
font = ImageFont.truetype(font.path, size)
|
|
||||||
text_width, text_height = font.getsize(text)
|
|
||||||
|
|
||||||
text_width, text_height = font.getsize(text)
|
|
||||||
|
|
||||||
# Align text to desired position
|
|
||||||
x = int((box_width / 2) - (text_width / 2))
|
|
||||||
y = int((box_height / 2) - (text_height / 2))
|
|
||||||
|
|
||||||
# Draw the text in the text-box
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
space = Image.new('RGBA', (box_width, box_height))
|
|
||||||
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
|
|
||||||
|
|
||||||
if rotation != None:
|
|
||||||
space.rotate(rotation, expand = True)
|
|
||||||
|
|
||||||
# Update only region with text (add text with transparent background)
|
|
||||||
image.paste(space, xy, space)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# column1 column2 column3 column4 column5 column6 column7
|
|
||||||
# |----------|----------|----------|----------|----------|----------|----------|
|
|
||||||
# | time | temperat.| moonphase| forecast1| forecast2| forecast3| forecast4|
|
|
||||||
# | current |----------|----------|----------|----------|----------|----------|
|
|
||||||
# | weather | humidity | sunrise | icon1 | icon2 | icon3 | icon4 |
|
|
||||||
# | icon |----------|----------|----------|----------|----------|----------|
|
|
||||||
# | | windspeed| sunset | temperat.| temperat.| temperat.| temperat.|
|
|
||||||
# |----------|----------|----------|----------|----------|----------|----------|
|
|
||||||
|
|
||||||
|
|
||||||
# Calculate size rows and columns
|
|
||||||
col_width = im_width // 7
|
|
||||||
|
|
||||||
if (im_height // 3) > col_width//2:
|
|
||||||
row_height = (im_height // 4)
|
|
||||||
else:
|
|
||||||
row_height = (im_height // 3)
|
|
||||||
|
|
||||||
|
|
||||||
# Adjust the fontsize to make use of most free space
|
|
||||||
# self.font = auto_fontsize(self.font, row_height)
|
|
||||||
|
|
||||||
# Calculate spacings for better centering
|
|
||||||
spacing_top = int( (im_width % col_width) / 2 )
|
|
||||||
spacing_left = int( (im_height % row_height) / 2 )
|
|
||||||
|
|
||||||
# Define sizes for weather icons
|
|
||||||
icon_small = int(col_width / 3)
|
|
||||||
icon_medium = icon_small * 2
|
|
||||||
icon_large = icon_small * 3
|
|
||||||
|
|
||||||
print('col_width=', col_width, 'row_height:', row_height)
|
|
||||||
print('small, medium ,large:', icon_small, icon_medium, icon_large)
|
|
||||||
|
|
||||||
# Calculate the x-axis position of each col
|
|
||||||
col1 = spacing_top
|
|
||||||
col2 = col1 + col_width
|
|
||||||
col3 = col2 + col_width
|
|
||||||
col4 = col3 + col_width
|
|
||||||
col5 = col4 + col_width
|
|
||||||
col6 = col5 + col_width
|
|
||||||
col7 = col6 + col_width
|
|
||||||
|
|
||||||
# Calculate the y-axis position of each row
|
|
||||||
row1 = spacing_left
|
|
||||||
row2 = row1 + row_height
|
|
||||||
row3 = row2 + row_height
|
|
||||||
|
|
||||||
# Positions for current weather details
|
|
||||||
weather_icon_pos = (col1, row1)
|
|
||||||
temperature_icon_pos = (col2, row1)
|
|
||||||
temperature_pos = (col2+icon_small, row1)
|
|
||||||
|
|
||||||
print('temp icon pos:', temperature_icon_pos)
|
|
||||||
print('temp:', temperature_pos)
|
|
||||||
|
|
||||||
humidity_icon_pos = (col2, row2)
|
|
||||||
humidity_pos = (col2+icon_small, row2)
|
|
||||||
|
|
||||||
print('hum icon pos:', humidity_icon_pos)
|
|
||||||
print('hum pos:', humidity_pos)
|
|
||||||
|
|
||||||
windspeed_icon_pos = (col2, row3)
|
|
||||||
windspeed_pos = (col2+icon_small, row3)
|
|
||||||
|
|
||||||
# Positions for sunrise, sunset, moonphase
|
|
||||||
moonphase_pos = (col3, row1)
|
|
||||||
sunrise_icon_pos = (col3, row2)
|
|
||||||
sunrise_time_pos = (col3+icon_small, row2)
|
|
||||||
sunset_icon_pos = (col3, row3)
|
|
||||||
sunset_time_pos = (col3+ icon_small, row3)
|
|
||||||
|
|
||||||
# Positions for forecast 1
|
|
||||||
stamp_fc1 = (col4, row1)
|
|
||||||
icon_fc1 = (col4, row2)
|
|
||||||
temp_fc1 = (col4, row3)
|
|
||||||
|
|
||||||
# Positions for forecast 2
|
|
||||||
stamp_fc2 = (col5, row1)
|
|
||||||
icon_fc2 = (col5, row2)
|
|
||||||
temp_fc2 = (col5, row3)
|
|
||||||
|
|
||||||
# Positions for forecast 3
|
|
||||||
stamp_fc3 = (col6, row1)
|
|
||||||
icon_fc3 = (col6, row2)
|
|
||||||
temp_fc3 = (col6, row3)
|
|
||||||
|
|
||||||
# Positions for forecast 4
|
|
||||||
stamp_fc4 = (col7, row1)
|
|
||||||
icon_fc4 = (col7, row2)
|
|
||||||
temp_fc4 = (col7, row3)
|
|
||||||
|
|
||||||
# Create current-weather and weather-forecast objects
|
|
||||||
weather = self.owm.weather_at_place(self.config['location']).get_weather()
|
|
||||||
forecast = self.owm.three_hours_forecast(self.config['location'])
|
|
||||||
|
|
||||||
# Set decimals
|
|
||||||
dec_temp = None if self.round_temperature == True else 1
|
|
||||||
dec_wind = None if self.round_windspeed == True else 1
|
|
||||||
|
|
||||||
# Set correct temperature units
|
|
||||||
if self.units == 'metric':
|
|
||||||
temp_unit = 'celsius'
|
|
||||||
elif self.units == 'imperial':
|
|
||||||
temp_unit = 'fahrenheit'
|
|
||||||
|
|
||||||
|
|
||||||
# Get current time
|
|
||||||
now = arrow.utcnow()
|
|
||||||
|
|
||||||
if self.forecast_interval == 'hourly':
|
|
||||||
|
|
||||||
# Forecasts are provided for every 3rd full hour
|
|
||||||
# find out how many hours there are until the next 3rd full hour
|
|
||||||
if (now.hour % 3) != 0:
|
|
||||||
hour_gap = 3 - (now.hour % 3)
|
|
||||||
else:
|
|
||||||
hour_gap = 3
|
|
||||||
|
|
||||||
# Create timings for hourly forcasts
|
|
||||||
forecast_timings = [now.shift(hours = + hour_gap + _).floor('hour')
|
|
||||||
for _ in range(0,12,3)]
|
|
||||||
|
|
||||||
# Create forecast objects for given timings
|
|
||||||
forecasts = [forecast.get_weather_at(forecast_time.datetime) for
|
|
||||||
forecast_time in forecast_timings]
|
|
||||||
|
|
||||||
# Add forecast-data to fc_data dictionary
|
|
||||||
fc_data = {}
|
|
||||||
for forecast in forecasts:
|
|
||||||
temp = '{}°'.format(round(
|
|
||||||
forecast.get_temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
|
|
||||||
|
|
||||||
icon = forecast.get_weather_icon_name()
|
|
||||||
fc_data['fc'+str(forecasts.index(forecast)+1)] = {
|
|
||||||
'temp':temp,
|
|
||||||
'icon':icon,
|
|
||||||
'stamp': forecast_timings[forecasts.index(forecast)].format('H.00'
|
|
||||||
if self.hour_format == '24' else 'h a')
|
|
||||||
}
|
|
||||||
|
|
||||||
elif self.forecast_interval == 'daily':
|
|
||||||
|
|
||||||
def calculate_forecast(days_from_today):
|
|
||||||
"""Get temperature range and most frequent icon code for forecast
|
|
||||||
days_from_today should be int from 1-4: e.g. 2 -> 2 days from today
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Create a list containing time-objects for every 3rd hour of the day
|
|
||||||
time_range = list(arrow.Arrow.range('hour',
|
|
||||||
now.shift(days=days_from_today).floor('day'),
|
|
||||||
now.shift(days=days_from_today).ceil('day')
|
|
||||||
))[::3]
|
|
||||||
|
|
||||||
# Get forecasts for each time-object
|
|
||||||
forecasts = [forecast.get_weather_at(_.datetime) for _ in time_range]
|
|
||||||
|
|
||||||
|
|
||||||
# Get all temperatures for this day
|
|
||||||
daily_temp = [round(_.get_temperature(unit=temp_unit)['temp'],
|
|
||||||
ndigits=dec_temp) for _ in forecasts]
|
|
||||||
# Calculate min. and max. temp for this day
|
|
||||||
temp_range = '{}°/{}°'.format(max(daily_temp), min(daily_temp))
|
|
||||||
|
|
||||||
|
|
||||||
# Get all weather icon codes for this day
|
|
||||||
daily_icons = [_.get_weather_icon_name() for _ in forecasts]
|
|
||||||
# Find most common element from all weather icon codes
|
|
||||||
status = max(set(daily_icons), key=daily_icons.count)
|
|
||||||
|
|
||||||
weekday = now.shift(days=days_from_today).format('ddd', locale=
|
|
||||||
self.locale)
|
|
||||||
return {'temp':temp_range, 'icon':status, 'stamp': weekday}
|
|
||||||
|
|
||||||
fc_data = [calculate_forecast(days) for days in range (1,5)]
|
|
||||||
|
|
||||||
for key,val in fc_data.items():
|
|
||||||
logging.info((key,val))
|
|
||||||
|
|
||||||
# Get some current weather details
|
|
||||||
temperature = '{}°'.format(weather.get_temperature(unit=temp_unit)['temp'])
|
|
||||||
weather_icon = weather.get_weather_icon_name()
|
|
||||||
humidity = str(weather.get_humidity())
|
|
||||||
windspeed = weather.get_wind(unit='meters_sec')['speed']
|
|
||||||
wind_angle = weather.get_wind()['deg']
|
|
||||||
wind_direction = ["N","NE","E","SE","S","SW","W","NW"][round(
|
|
||||||
wind_angle/45) % 8]
|
|
||||||
sunrise_raw = arrow.get(weather.get_sunrise_time()).to(self.timezone)
|
|
||||||
sunset_raw = arrow.get(weather.get_sunset_time()).to(self.timezone)
|
|
||||||
|
|
||||||
if self.hour_format == '12':
|
|
||||||
sunrise = sunrise_raw.format('h:mm a')
|
|
||||||
sunset = sunset_raw.format('h:mm a')
|
|
||||||
elif self.hour_format == '24':
|
|
||||||
sunrise = sunrise_raw.format('H:mm')
|
|
||||||
sunset = sunset_raw.format('H:mm')
|
|
||||||
|
|
||||||
# Format the windspeed to user preference
|
|
||||||
if self.use_beaufort == True:
|
|
||||||
windspeed_to_beaufort = [0.02, 1.5, 3.3, 5.4, 7.9, 10.7, 13.8, 17.1,
|
|
||||||
20.7, 24.4, 28.4, 32.6, 100]
|
|
||||||
wind = str([windspeed_to_beaufort.index(_) for _ in windspeed_to_beaufort
|
|
||||||
if windspeed < _][0])
|
|
||||||
|
|
||||||
elif self.use_beaufort == False:
|
|
||||||
meters_sec = round(windspeed, ndigits = dec_wind)
|
|
||||||
miles_per_hour = round(windspeed * 2.23694, ndigits = dec_wind)
|
|
||||||
|
|
||||||
if self.units == 'metric':
|
|
||||||
wind = str(meters_sec) + 'm/s'
|
|
||||||
|
|
||||||
elif self.units == 'imperial':
|
|
||||||
wind = str(miles_per_hour) + 'mph'
|
|
||||||
|
|
||||||
if self.show_wind_direction == True:
|
|
||||||
wind += '({0})'.format(wind_direction)
|
|
||||||
|
|
||||||
|
|
||||||
dec = decimal.Decimal
|
|
||||||
moonphase = get_moon_phase()
|
|
||||||
|
|
||||||
# Fill weather details in col 1 (current weather icon)
|
|
||||||
# write(im_black, (col_width, row_height), now_str, text_now_pos, font = font)
|
|
||||||
draw_icon(im_colour, weather_icon_pos, (icon_large, icon_large),
|
|
||||||
weathericons[weather_icon])
|
|
||||||
|
|
||||||
# Fill weather details in col 2 (temp, humidity, wind)
|
|
||||||
draw_icon(im_colour, temperature_icon_pos, (row_height, row_height),
|
|
||||||
'\uf053')
|
|
||||||
|
|
||||||
if is_negative(temperature):
|
|
||||||
write(im_black, temperature_pos, (col_width-icon_small, row_height),
|
|
||||||
temperature, font = self.font, fill_height = 0.9)
|
|
||||||
else:
|
|
||||||
write(im_black, temperature_pos, (col_width-icon_small, row_height),
|
|
||||||
temperature, font = self.font)
|
|
||||||
|
|
||||||
draw_icon(im_colour, humidity_icon_pos, (row_height, row_height),
|
|
||||||
'\uf07a')
|
|
||||||
|
|
||||||
write(im_black, humidity_pos, (col_width-icon_small, row_height),
|
|
||||||
humidity+'%', font = self.font, fill_height = 0.9)
|
|
||||||
|
|
||||||
if self.use_wind_direction_icon == False:
|
|
||||||
draw_icon(im_colour, windspeed_icon_pos, (icon_small, icon_small),
|
|
||||||
'\uf050')
|
|
||||||
else:
|
|
||||||
draw_icon(im_colour, windspeed_icon_pos, (icon_small, icon_small),
|
|
||||||
'\uf0b1', rotation = -wind_degrees)
|
|
||||||
|
|
||||||
write(im_black, windspeed_pos, (col_width-icon_small, row_height),
|
|
||||||
wind, font=self.font, fill_height = 0.9)
|
|
||||||
|
|
||||||
# Fill weather details in col 3 (moonphase, sunrise, sunset)
|
|
||||||
draw_icon(im_colour, moonphase_pos, (col_width, row_height), moonphase)
|
|
||||||
|
|
||||||
draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051')
|
|
||||||
write(im_black, sunrise_time_pos, (col_width-icon_small, icon_small), sunrise,
|
|
||||||
font = self.font, autofit = True)
|
|
||||||
|
|
||||||
draw_icon(im_colour, sunset_icon_pos, (icon_small, icon_small), '\uf052')
|
|
||||||
write(im_black, sunset_time_pos, (col_width-icon_small, icon_small), sunset,
|
|
||||||
font = self.font, autofit = True)
|
|
||||||
|
|
||||||
# Add the forecast data to the correct places
|
|
||||||
for pos in range(1, len(fc_data)+1):
|
|
||||||
stamp = fc_data['fc'+str(pos)]['stamp']
|
|
||||||
icon = weathericons[fc_data['fc'+str(pos)]['icon']]
|
|
||||||
temp = fc_data['fc'+str(pos)]['temp']
|
|
||||||
|
|
||||||
write(im_black, eval('stamp_fc'+str(pos)), (col_width, row_height),
|
|
||||||
stamp, font = self.font)
|
|
||||||
draw_icon(im_colour, eval('icon_fc'+str(pos)), (col_width, row_height),
|
|
||||||
icon)
|
|
||||||
write(im_black, eval('temp_fc'+str(pos)), (col_width, row_height),
|
|
||||||
temp, font = self.font)
|
|
||||||
|
|
||||||
# Add borders around each sub-section
|
|
||||||
square_h = int((row_height*3)*0.9)
|
|
||||||
square_w = int((col_width*0.9))
|
|
||||||
draw_square(im_colour, (col1, row1), (col_width*3, square_h))
|
|
||||||
draw_square(im_colour, (col4, row1), (square_w, square_h))
|
|
||||||
draw_square(im_colour, (col5, row1), (square_w, square_h))
|
|
||||||
draw_square(im_colour, (col6, row1), (square_w, square_h))
|
|
||||||
draw_square(im_colour, (col7, row1), (square_w, square_h))
|
|
||||||
|
|
||||||
## except Exception as e:
|
|
||||||
## """If something went wrong, print a Error message on the Terminal"""
|
|
||||||
## print('Failed!')
|
|
||||||
## print('Error in weather module!')
|
|
||||||
## print('Reason: ',e)
|
|
||||||
## clear_image('top_section')
|
|
||||||
## write(top_section_width, top_section_height, str(e),
|
|
||||||
## (0, 0), font = font)
|
|
||||||
## weather_image = crop_image(image, 'top_section')
|
|
||||||
## weather_image.save(image_path+'inkycal_weather.png')
|
|
||||||
## pass
|
|
||||||
##
|
|
||||||
# Save image of black and colour channel in image-folder
|
|
||||||
im_black.save(images+self.name+'.png')
|
|
||||||
im_colour.save(images+self.name+'_colour.png')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print('running {0} in standalone mode'.format(
|
|
||||||
os.path.basename(__file__).split('.py')[0]))
|
|
||||||
a = weather(size, config)
|
|
||||||
a.generate_image()
|
|
Loading…
Reference in New Issue
Block a user