Merge branch 'main' into weather_scaling
This commit is contained in:
@@ -10,7 +10,9 @@ import inkycal.modules.inkycal_todoist
|
||||
import inkycal.modules.inkycal_image
|
||||
import inkycal.modules.inkycal_jokes
|
||||
import inkycal.modules.inkycal_slideshow
|
||||
# import inkycal.modules.inkycal_server
|
||||
import inkycal.modules.inkycal_stocks
|
||||
import inkycal.modules.inkycal_webshot
|
||||
import inkycal.modules.inkycal_xkcd
|
||||
|
||||
# Main file
|
||||
from inkycal.main import Inkycal
|
||||
|
@@ -1,2 +1,3 @@
|
||||
from .functions import *
|
||||
from .inkycal_exceptions import *
|
||||
from .openweathermap_wrapper import OpenWeatherMap
|
@@ -1,15 +1,15 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Inkycal custom-functions for ease-of-use
|
||||
|
||||
Copyright by aceinnolab
|
||||
"""
|
||||
import logging
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
||||
from urllib.request import urlopen
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import requests
|
||||
from PIL import ImageFont, ImageDraw, Image
|
||||
|
||||
logs = logging.getLogger(__name__)
|
||||
logs.setLevel(level=logging.INFO)
|
||||
@@ -98,11 +98,13 @@ def auto_fontsize(font, max_height):
|
||||
Returns:
|
||||
A PIL font object with modified height.
|
||||
"""
|
||||
|
||||
fontsize = font.getsize('hg')[1]
|
||||
while font.getsize('hg')[1] <= (max_height * 0.80):
|
||||
text_bbox = font.getbbox("hg")
|
||||
text_height = text_bbox[3]
|
||||
fontsize = text_height
|
||||
while text_height <= (max_height * 0.80):
|
||||
fontsize += 1
|
||||
font = ImageFont.truetype(font.path, fontsize)
|
||||
text_height = text_bbox[3]
|
||||
return font
|
||||
|
||||
|
||||
@@ -154,21 +156,34 @@ def write(image, xy, box_size, text, font=None, **kwargs):
|
||||
if autofit or (fill_width != 1.0) or (fill_height != 0.8):
|
||||
size = 8
|
||||
font = ImageFont.truetype(font.path, size)
|
||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
||||
text_bbox = font.getbbox(text)
|
||||
text_width = text_bbox[2] - text_bbox[0]
|
||||
text_bbox_height = font.getbbox("hg")
|
||||
text_height = text_bbox_height[3] - text_bbox_height[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_bbox = font.getbbox(text)
|
||||
text_width = text_bbox[2] - text_bbox[0]
|
||||
text_bbox_height = font.getbbox("hg")
|
||||
text_height = text_bbox_height[3] - text_bbox_height[1]
|
||||
|
||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
||||
text_bbox = font.getbbox(text)
|
||||
text_width = text_bbox[2] - text_bbox[0]
|
||||
text_bbox_height = font.getbbox("hg")
|
||||
text_height = text_bbox_height[3] - text_bbox_height[1]
|
||||
|
||||
# Truncate text if text is too long so it can fit inside the box
|
||||
if (text_width, text_height) > (box_width, box_height):
|
||||
logs.debug(('truncating {}'.format(text)))
|
||||
while (text_width, text_height) > (box_width, box_height):
|
||||
text = text[0:-1]
|
||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
||||
text_bbox = font.getbbox(text)
|
||||
text_width = text_bbox[2] - text_bbox[0]
|
||||
text_bbox_height = font.getbbox("hg")
|
||||
text_height = text_bbox_height[3] - text_bbox_height[1]
|
||||
logs.debug(text)
|
||||
|
||||
# Align text to desired position
|
||||
@@ -215,14 +230,17 @@ def text_wrap(text, font=None, max_width=None):
|
||||
A list containing chunked strings of the full text.
|
||||
"""
|
||||
lines = []
|
||||
if font.getsize(text)[0] < max_width:
|
||||
|
||||
text_width = font.getlength(text)
|
||||
|
||||
if text_width < 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:
|
||||
while i < len(words) and font.getlength(line + words[i]) <= max_width:
|
||||
line = line + words[i] + " "
|
||||
i += 1
|
||||
if not line:
|
||||
@@ -247,12 +265,14 @@ def internet_available():
|
||||
>>> if internet_available():
|
||||
>>> #...do something that requires internet connectivity
|
||||
"""
|
||||
|
||||
try:
|
||||
urlopen('https://google.com', timeout=5)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
for attempt in range(3):
|
||||
try:
|
||||
requests.get('https://google.com', timeout=5)
|
||||
return True
|
||||
except:
|
||||
print(f"Network could not be reached: {traceback.print_exc()}")
|
||||
time.sleep(5)
|
||||
return False
|
||||
|
||||
|
||||
def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)):
|
||||
@@ -279,17 +299,17 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)):
|
||||
|
||||
colour = 'black'
|
||||
|
||||
# size from function paramter
|
||||
# size from function parameter
|
||||
width, height = int(size[0] * (1 - shrinkage[0])), int(size[1] * (1 - shrinkage[1]))
|
||||
|
||||
# shift cursor to move rectangle to center
|
||||
offset_x, offset_y = int((size[0] - width) / 2), int((size[1] - height) / 2)
|
||||
|
||||
x, y, diameter = xy[0] + offset_x, xy[1] + offset_y, radius * 2
|
||||
# lenght of rectangle size
|
||||
# length of rectangle size
|
||||
a, b = (width - diameter), (height - diameter)
|
||||
|
||||
# Set coordinates for staright lines
|
||||
# Set coordinates for straight lines
|
||||
p1, p2 = (x + radius, y), (x + radius + a, y)
|
||||
p3, p4 = (x + width, y + radius), (x + width, y + radius + b)
|
||||
p5, p6 = (p2[0], y + height), (p1[0], y + height)
|
||||
@@ -313,3 +333,12 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)):
|
||||
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)
|
||||
|
||||
|
||||
def draw_border_2(im: Image, xy: tuple, size: tuple, radius: int):
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
x, y = xy
|
||||
w, h = size
|
||||
|
||||
draw.rounded_rectangle(xy=(x, y, x + w, y + h), outline="black", radius=radius)
|
43
inkycal/custom/openweathermap_wrapper.py
Normal file
43
inkycal/custom/openweathermap_wrapper.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import logging
|
||||
from enum import Enum
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class WEATHER_OPTIONS(Enum):
|
||||
CURRENT_WEATHER = "weather"
|
||||
|
||||
class FORECAST_INTERVAL(Enum):
|
||||
THREE_HOURS = "3h"
|
||||
FIVE_DAYS = "5d"
|
||||
|
||||
|
||||
|
||||
class OpenWeatherMap:
|
||||
def __init__(self, api_key:str, city_id:int, units:str) -> None:
|
||||
self.api_key = api_key
|
||||
self.city_id = city_id
|
||||
assert (units in ["metric", "imperial"] )
|
||||
self.units = units
|
||||
self._api_version = "2.5"
|
||||
self._base_url = f"https://api.openweathermap.org/data/{self._api_version}"
|
||||
|
||||
|
||||
def get_current_weather(self) -> dict:
|
||||
current_weather_url = f"{self._base_url}/weather?id={self.city_id}&appid={self.api_key}&units={self.units}"
|
||||
response = requests.get(current_weather_url)
|
||||
if not response.ok:
|
||||
raise AssertionError(f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}")
|
||||
data = json.loads(response.text)
|
||||
return data
|
||||
|
||||
def get_weather_forecast(self) -> dict:
|
||||
forecast_url = f"{self._base_url}/forecast?id={self.city_id}&appid={self.api_key}&units={self.units}"
|
||||
response = requests.get(forecast_url)
|
||||
if not response.ok:
|
||||
raise AssertionError(f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}")
|
||||
data = json.loads(response.text)["list"]
|
||||
return data
|
||||
|
@@ -4,6 +4,7 @@ Copyright by aceisace
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
import traceback
|
||||
from importlib import import_module
|
||||
from PIL import Image
|
||||
|
||||
@@ -43,7 +44,7 @@ class Display:
|
||||
except FileNotFoundError:
|
||||
raise Exception('SPI could not be found. Please check if SPI is enabled')
|
||||
|
||||
def render(self, im_black: Image.Image, im_colour=Image.Image or None) -> None:
|
||||
def render(self, im_black: Image, im_colour=Image or None) -> None:
|
||||
"""Renders an image on the selected E-Paper display.
|
||||
|
||||
Initlializes the E-Paper display, sends image data and executes command
|
||||
@@ -66,7 +67,6 @@ class Display:
|
||||
|
||||
|
||||
Rendering black-white on coloured E-Paper displays:
|
||||
|
||||
>>> sample_image = Image.open('path/to/file.png')
|
||||
>>> display = Display('my_coloured_display')
|
||||
>>> display.render(sample_image, sample_image)
|
||||
@@ -82,14 +82,7 @@ class Display:
|
||||
|
||||
epaper = self._epaper
|
||||
|
||||
if not self.supports_colour:
|
||||
print('Initialising..', end='')
|
||||
epaper.init()
|
||||
print('Updating display......', end='')
|
||||
epaper.display(epaper.getbuffer(im_black))
|
||||
print('Done')
|
||||
|
||||
elif self.supports_colour:
|
||||
if self.supports_colour:
|
||||
if not im_colour:
|
||||
raise Exception('im_colour is required for coloured epaper displays')
|
||||
print('Initialising..', end='')
|
||||
@@ -97,6 +90,12 @@ class Display:
|
||||
print('Updating display......', end='')
|
||||
epaper.display(epaper.getbuffer(im_black), epaper.getbuffer(im_colour))
|
||||
print('Done')
|
||||
else:
|
||||
print('Initialising..', end='')
|
||||
epaper.init()
|
||||
print('Updating display......', end='')
|
||||
epaper.display(epaper.getbuffer(im_black))
|
||||
print('Done')
|
||||
|
||||
print('Sending E-Paper to deep sleep...', end='')
|
||||
epaper.sleep()
|
||||
@@ -173,9 +172,10 @@ class Display:
|
||||
try:
|
||||
driver = import_driver(model_name)
|
||||
return driver.EPD_WIDTH, driver.EPD_HEIGHT
|
||||
except Exception as e:
|
||||
except:
|
||||
logging.error(f'Failed to load driver for ${model_name}. Check spelling?')
|
||||
raise e;
|
||||
print(traceback.format_exc())
|
||||
raise AssertionError("Could not import driver")
|
||||
|
||||
@classmethod
|
||||
def get_display_names(cls) -> list:
|
||||
|
@@ -35,6 +35,8 @@ from inkycal.display.drivers import epdconfig
|
||||
EPD_WIDTH = 800
|
||||
EPD_HEIGHT = 480
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EPD:
|
||||
def __init__(self):
|
||||
@@ -48,11 +50,11 @@ class EPD:
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.delay_ms(20)
|
||||
epdconfig.digital_write(self.reset_pin, 0)
|
||||
epdconfig.delay_ms(2)
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.delay_ms(20)
|
||||
|
||||
def send_command(self, command):
|
||||
epdconfig.digital_write(self.dc_pin, 0)
|
||||
@@ -66,14 +68,21 @@ class EPD:
|
||||
epdconfig.spi_writebyte([data])
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def send_data2(self, data):
|
||||
epdconfig.digital_write(self.dc_pin, 1)
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.SPI.writebytes2(data)
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def ReadBusy(self):
|
||||
logging.debug("e-Paper busy")
|
||||
logger.debug("e-Paper busy")
|
||||
self.send_command(0x71)
|
||||
busy = epdconfig.digital_read(self.busy_pin)
|
||||
while (busy == 0):
|
||||
self.send_command(0x71)
|
||||
busy = epdconfig.digital_read(self.busy_pin)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.delay_ms(20)
|
||||
logger.debug("e-Paper busy release")
|
||||
|
||||
def init(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
@@ -81,6 +90,12 @@ class EPD:
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x06) # btst
|
||||
self.send_data(0x17)
|
||||
self.send_data(0x17)
|
||||
self.send_data(0x28) # If an exception is displayed, try using 0x38
|
||||
self.send_data(0x17)
|
||||
|
||||
self.send_command(0x01) # POWER SETTING
|
||||
self.send_data(0x07)
|
||||
self.send_data(0x07) # VGH=20V,VGL=-20V
|
||||
@@ -114,47 +129,39 @@ class EPD:
|
||||
return 0
|
||||
|
||||
def getbuffer(self, image):
|
||||
# logging.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||
buf = [0xFF] * (int(self.width / 8) * self.height)
|
||||
image_monocolor = image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
img = image
|
||||
imwidth, imheight = img.size
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
logging.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] == 0:
|
||||
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||
img = img.convert('1')
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
logging.debug("Horizontal")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if pixels[x, y] == 0:
|
||||
buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
|
||||
# image has correct dimensions, but needs to be rotated
|
||||
img = img.rotate(90, expand=True).convert('1')
|
||||
else:
|
||||
logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
|
||||
# return a blank buffer
|
||||
return [0x00] * (int(self.width / 8) * self.height)
|
||||
|
||||
buf = bytearray(img.tobytes('raw'))
|
||||
# The bytes need to be inverted, because in the PIL world 0=black and 1=white, but
|
||||
# in the e-paper world 0=white and 1=black.
|
||||
for i in range(len(buf)):
|
||||
buf[i] ^= 0xFF
|
||||
return buf
|
||||
|
||||
def display(self, image):
|
||||
self.send_command(0x13)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(~image[i]);
|
||||
self.send_data2(image)
|
||||
|
||||
self.send_command(0x12)
|
||||
epdconfig.delay_ms(100)
|
||||
self.ReadBusy()
|
||||
|
||||
def Clear(self):
|
||||
buf = [0x00] * (int(self.width / 8) * self.height)
|
||||
self.send_command(0x10)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_data2(buf)
|
||||
self.send_command(0x13)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_data2(buf)
|
||||
self.send_command(0x12)
|
||||
epdconfig.delay_ms(100)
|
||||
self.ReadBusy()
|
||||
@@ -166,5 +173,6 @@ class EPD:
|
||||
self.send_command(0x07) # DEEP_SLEEP
|
||||
self.send_data(0XA5)
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
### END OF FILE ###
|
||||
### END OF FILE ###
|
227
inkycal/main.py
227
inkycal/main.py
@@ -1,6 +1,3 @@
|
||||
#!python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Main class for inkycal Project
|
||||
Copyright by aceinnolab
|
||||
@@ -9,11 +6,12 @@ Copyright by aceinnolab
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import traceback
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
import arrow
|
||||
import numpy
|
||||
import asyncio
|
||||
|
||||
|
||||
from inkycal.custom import *
|
||||
from inkycal.display import Display
|
||||
@@ -27,7 +25,6 @@ stream_handler = logging.StreamHandler()
|
||||
stream_handler.setLevel(logging.ERROR)
|
||||
|
||||
|
||||
|
||||
if not os.path.exists(f'{top_level}/logs'):
|
||||
os.mkdir(f'{top_level}/logs')
|
||||
|
||||
@@ -37,9 +34,7 @@ logging.basicConfig(
|
||||
format='%(asctime)s | %(name)s | %(levelname)s: %(message)s',
|
||||
datefmt='%d-%m-%Y %H:%M:%S',
|
||||
handlers=[
|
||||
|
||||
stream_handler, # add stream handler from above
|
||||
|
||||
RotatingFileHandler( # log to a file too
|
||||
f'{top_level}/logs/inkycal.log', # file to log
|
||||
maxBytes=2097152, # 2MB max filesize
|
||||
@@ -71,15 +66,18 @@ class Inkycal:
|
||||
to improve rendering on E-Papers. Set this to False for 9.7" E-Paper.
|
||||
"""
|
||||
|
||||
def __init__(self, settings_path=None, render=True):
|
||||
def __init__(self, settings_path:str or None=None, render:bool=True):
|
||||
"""Initialise Inkycal"""
|
||||
|
||||
self._release = '2.0.2'
|
||||
# Get the release version from setup.py
|
||||
with open(f'{top_level}/setup.py') as setup_file:
|
||||
for line in setup_file:
|
||||
if line.startswith('__version__'):
|
||||
self._release = line.split("=")[-1].replace("'", "").replace('"', "").replace(" ", "")
|
||||
break
|
||||
|
||||
# Check if render was set correctly
|
||||
if render not in [True, False]:
|
||||
raise Exception(f'render must be True or False, not "{render}"')
|
||||
self.render = render
|
||||
self.info = None
|
||||
|
||||
# load settings file - throw an error if file could not be found
|
||||
if settings_path:
|
||||
@@ -89,7 +87,7 @@ class Inkycal:
|
||||
self.settings = settings
|
||||
|
||||
except FileNotFoundError:
|
||||
raise SettingsFileNotFoundError
|
||||
raise FileNotFoundError(f"No settings.json file could be found in the specified location: {settings_path}")
|
||||
|
||||
else:
|
||||
try:
|
||||
@@ -106,6 +104,8 @@ class Inkycal:
|
||||
# Option to use epaper image optimisation, reduces colours
|
||||
self.optimize = True
|
||||
|
||||
self.show_border = self.settings.get('border_around_modules', False)
|
||||
|
||||
# Load drivers if image should be rendered
|
||||
if self.render:
|
||||
# Init Display class with model in settings file
|
||||
@@ -121,7 +121,7 @@ class Inkycal:
|
||||
# init calibration state
|
||||
self._calibration_state = False
|
||||
|
||||
# Load and intialize modules specified in the settings file
|
||||
# Load and initialise modules specified in the settings file
|
||||
self._module_number = 1
|
||||
for module in settings['modules']:
|
||||
module_name = module['name']
|
||||
@@ -168,10 +168,10 @@ class Inkycal:
|
||||
update_timings = [(60 - int(interval_mins) * updates) for updates in
|
||||
range(60 // int(interval_mins))][::-1]
|
||||
|
||||
# Calculate time in mins until next update
|
||||
# Calculate time in minutes until next update
|
||||
minutes = [_ for _ in update_timings if _ >= now.minute][0] - now.minute
|
||||
|
||||
# Print the remaining time in mins until next update
|
||||
# Print the remaining time in minutes until next update
|
||||
print(f'{minutes} minutes left until next refresh')
|
||||
|
||||
# Calculate time in seconds until next update
|
||||
@@ -205,6 +205,8 @@ class Inkycal:
|
||||
print(f'generating image(s) for {name}...', end="")
|
||||
try:
|
||||
black, colour = module.generate_image()
|
||||
if self.show_border:
|
||||
draw_border_2(im=black, xy=(1, 1), size=(black.width - 2, black.height - 2), radius=5)
|
||||
black.save(f"{self.image_folder}module{number}_black.png", "PNG")
|
||||
colour.save(f"{self.image_folder}module{number}_colour.png", "PNG")
|
||||
print('OK!')
|
||||
@@ -259,12 +261,12 @@ class Inkycal:
|
||||
return res
|
||||
|
||||
|
||||
def run(self):
|
||||
async def run(self):
|
||||
"""Runs main program in nonstop mode.
|
||||
|
||||
Uses an infinity loop to run Inkycal nonstop. Inkycal generates the image
|
||||
from all modules, assembles them in one image, refreshed the E-Paper and
|
||||
then sleeps until the next sheduled update.
|
||||
then sleeps until the next scheduled update.
|
||||
"""
|
||||
|
||||
# Get the time of initial run
|
||||
@@ -300,6 +302,8 @@ class Inkycal:
|
||||
|
||||
try:
|
||||
black, colour = module.generate_image()
|
||||
if self.show_border:
|
||||
draw_border_2(im=black, xy=(1, 1), size=(black.width - 2, black.height - 2), radius=5)
|
||||
black.save(f"{self.image_folder}module{number}_black.png", "PNG")
|
||||
colour.save(f"{self.image_folder}module{number}_colour.png", "PNG")
|
||||
self.info += f"module {number}: OK "
|
||||
@@ -327,7 +331,7 @@ class Inkycal:
|
||||
|
||||
self._calibration_check()
|
||||
if self._calibration_state:
|
||||
# after calibration we have to forcefully rewrite the screen
|
||||
# after calibration, we have to forcefully rewrite the screen
|
||||
self._remove_hashes(self.image_folder)
|
||||
|
||||
if self.supports_colour:
|
||||
@@ -365,7 +369,7 @@ class Inkycal:
|
||||
f'program started {runtime.humanize()}')
|
||||
|
||||
sleep_time = self.countdown()
|
||||
time.sleep(sleep_time)
|
||||
await asyncio.sleep(sleep_time)
|
||||
|
||||
@staticmethod
|
||||
def _merge_bands():
|
||||
@@ -536,7 +540,7 @@ class Inkycal:
|
||||
self.Display.calibrate()
|
||||
|
||||
def _calibration_check(self):
|
||||
"""Calibration sheduler
|
||||
"""Calibration scheduler
|
||||
uses calibration hours from settings file to check if calibration is due"""
|
||||
now = arrow.now()
|
||||
# print('hour:', now.hour, 'hours:', self._calibration_hours)
|
||||
@@ -547,187 +551,6 @@ class Inkycal:
|
||||
else:
|
||||
self._calibration_state = False
|
||||
|
||||
@classmethod
|
||||
def add_module(cls, filepath):
|
||||
"""registers a third party module for inkycal.
|
||||
|
||||
Uses the full filepath of the third party module to check if it is inside
|
||||
the correct folder, then checks if it's an inkycal module. Lastly, the
|
||||
init files in /inkycal and /inkycal/modules are updated to allow using
|
||||
the new module.
|
||||
|
||||
Args:
|
||||
- filepath: The full filepath of the third party module. Modules should be
|
||||
in Inkycal/inkycal/modules.
|
||||
|
||||
Usage:
|
||||
- download a third-party module. The exact link is provided by the
|
||||
developer of that module and starts with
|
||||
`https://raw.githubusercontent.com/...`
|
||||
|
||||
enter the following in bash to download a module::
|
||||
|
||||
$ cd Inkycal/inkycal/modules #navigate to modules folder in inkycal
|
||||
$ wget https://raw.githubusercontent.com/... #download the module
|
||||
|
||||
then register it with this function::
|
||||
|
||||
>>> from inkycal import Inkycal
|
||||
>>> Inkycal.add_module('/full/path/to/the/module/in/inkycal/modules.py')
|
||||
"""
|
||||
|
||||
module_folder = top_level + '/inkycal/modules'
|
||||
|
||||
if module_folder in filepath:
|
||||
filename = filepath.split('.py')[0].split('/')[-1]
|
||||
|
||||
# Extract name of class from given module and validate if it's an inkycal
|
||||
# module
|
||||
with open(filepath, mode='r') as module:
|
||||
module_content = module.read().splitlines()
|
||||
|
||||
for line in module_content:
|
||||
if '(inkycal_module):' in line:
|
||||
classname = line.split(' ')[-1].split('(')[0]
|
||||
break
|
||||
|
||||
if not classname:
|
||||
raise TypeError("your module doesn't seem to be a correct inkycal module.."
|
||||
"Please check your module again.")
|
||||
|
||||
# Check if filename or classname exists in init of module folder
|
||||
with open(module_folder + '/__init__.py', mode='r') as file:
|
||||
module_init = file.read().splitlines()
|
||||
|
||||
print('checking module init file..')
|
||||
for line in module_init:
|
||||
if filename in line:
|
||||
raise Exception(
|
||||
"A module with this filename already exists! \n"
|
||||
"Please consider renaming your module and try again."
|
||||
)
|
||||
if classname in line:
|
||||
raise Exception(
|
||||
"A module with this classname already exists! \n"
|
||||
"Please consider renaming your class and try again."
|
||||
)
|
||||
print('OK!')
|
||||
|
||||
# Check if filename or classname exists in init of inkycal folder
|
||||
with open(top_level + '/inkycal/__init__.py', mode='r') as file:
|
||||
inkycal_init = file.read().splitlines()
|
||||
|
||||
print('checking inkycal init file..')
|
||||
for line in inkycal_init:
|
||||
if filename in line:
|
||||
raise Exception(
|
||||
"A module with this filename already exists! \n"
|
||||
"Please consider renaming your module and try again."
|
||||
)
|
||||
if classname in line:
|
||||
raise Exception(
|
||||
"A module with this classname already exists! \n"
|
||||
"Please consider renaming your class and try again."
|
||||
)
|
||||
print('OK')
|
||||
|
||||
# If all checks have passed, add the module in the module init file
|
||||
with open(module_folder + '/__init__.py', mode='a') as file:
|
||||
file.write(f'from .{filename} import {classname} # Added by module adder')
|
||||
|
||||
# If all checks have passed, add the module in the inkycal init file
|
||||
with open(top_level + '/inkycal/__init__.py', mode='a') as file:
|
||||
file.write(f'import inkycal.modules.{filename} # Added by module adder')
|
||||
|
||||
print(f"Your module '{filename}' with class '{classname}' has been added "
|
||||
"successfully! Hooray!")
|
||||
return
|
||||
|
||||
# Check if module is inside the modules folder
|
||||
raise Exception(f"Your module should be in {module_folder} "
|
||||
f"but is currently in {filepath}")
|
||||
|
||||
@classmethod
|
||||
def remove_module(cls, filename, remove_file=True):
|
||||
"""unregisters an inkycal module.
|
||||
|
||||
Looks for given filename.py in /modules folder, removes entries of that
|
||||
module in init files inside /inkycal and /inkycal/modules
|
||||
|
||||
Args:
|
||||
- filename: The filename (with .py ending) of the module which should be
|
||||
unregistered. e.g. `'mymodule.py'`
|
||||
- remove_file: ->bool (True/False). If set to True, the module is deleted
|
||||
after unregistering it, else it remains in the /modules folder
|
||||
|
||||
|
||||
Usage:
|
||||
- Look for the module in Inkycal/inkycal/modules which should be removed.
|
||||
Only the filename (with .py) is required, not the full path.
|
||||
|
||||
Use this function to unregister the module from inkycal::
|
||||
|
||||
>>> from inkycal import Inkycal
|
||||
>>> Inkycal.remove_module('mymodule.py')
|
||||
"""
|
||||
|
||||
module_folder = top_level + '/inkycal/modules'
|
||||
|
||||
# Check if module is inside the modules folder and extract classname
|
||||
try:
|
||||
with open(f"{module_folder}/{filename}", mode='r') as file:
|
||||
module_content = file.read().splitlines()
|
||||
|
||||
for line in module_content:
|
||||
if '(inkycal_module):' in line:
|
||||
classname = line.split(' ')[-1].split('(')[0]
|
||||
break
|
||||
|
||||
if not classname:
|
||||
print('The module you are trying to remove is not an inkycal module.. '
|
||||
'Not removing it.')
|
||||
return
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"No module named {filename} found in {module_folder}")
|
||||
return
|
||||
|
||||
filename = filename.split('.py')[0]
|
||||
|
||||
# Create a memory backup of /modules init file
|
||||
with open(module_folder + '/__init__.py', mode='r') as file:
|
||||
module_init = file.read().splitlines()
|
||||
|
||||
print('removing line from module_init')
|
||||
# Remove lines that contain classname
|
||||
with open(module_folder + '/__init__.py', mode='w') as file:
|
||||
for line in module_init:
|
||||
if not classname in line:
|
||||
file.write(line + '\n')
|
||||
else:
|
||||
print('found, removing')
|
||||
|
||||
# Create a memory backup of inkycal init file
|
||||
with open(f"{top_level}/inkycal/__init__.py", mode='r') as file:
|
||||
inkycal_init = file.read().splitlines()
|
||||
|
||||
print('removing line from inkycal init')
|
||||
# Remove lines that contain classname
|
||||
with open(f"{top_level}/inkycal/__init__.py", mode='w') as file:
|
||||
for line in inkycal_init:
|
||||
if filename in line:
|
||||
print('found, removing')
|
||||
else:
|
||||
file.write(line + '\n')
|
||||
|
||||
# remove the file of the third party module if it exists and remove_file
|
||||
# was set to True (default)
|
||||
if os.path.exists(f"{module_folder}/{filename}.py") and remove_file is True:
|
||||
print('deleting module file')
|
||||
os.remove(f"{module_folder}/{filename}.py")
|
||||
|
||||
print(f"Your module '{filename}' with class '{classname}' was removed.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running inkycal main in standalone/debug mode')
|
||||
|
@@ -8,3 +8,5 @@ from .inkycal_jokes import Jokes
|
||||
from .inkycal_stocks import Stocks
|
||||
from .inkycal_slideshow import Slideshow
|
||||
from .inkycal_textfile_to_display import TextToDisplay
|
||||
from .inkycal_webshot import Webshot
|
||||
from .inkycal_xkcd import Xkcd
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Inkycal iCalendar parsing module
|
||||
Copyright by aceinnolab
|
||||
@@ -119,7 +117,7 @@ class iCalendar:
|
||||
|
||||
events = (
|
||||
{
|
||||
'title': events.get('SUMMARY').lstrip(),
|
||||
'title': events.get('SUMMARY').lstrip() if events.get('SUMMARY') else "",
|
||||
|
||||
'begin': arrow.get(events.get('DTSTART').dt).to(timezone) if (
|
||||
arrow.get(events.get('dtstart').dt).format('HH:mm') != '00:00')
|
||||
|
@@ -83,12 +83,11 @@ class Inkyimage:
|
||||
|
||||
@staticmethod
|
||||
def preview(image):
|
||||
""""Previews an image on gpicview (only works on Rapsbian with Desktop).
|
||||
"""
|
||||
path = '/home/pi/Desktop/'
|
||||
image.save(path + 'temp.png')
|
||||
os.system("gpicview " + path + 'temp.png')
|
||||
os.system('rm ' + path + 'temp.png')
|
||||
"""Previews an image on gpicview (only works on Rapsbian with Desktop)."""
|
||||
path = '~/temp'
|
||||
image.save(path + '/temp.png')
|
||||
os.system("gpicview " + path + '/temp.png')
|
||||
os.system('rm ' + path + '/temp.png')
|
||||
|
||||
def _image_loaded(self):
|
||||
"""returns True if image was loaded"""
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Inkycal Agenda Module
|
||||
Copyright by aceinnolab
|
||||
@@ -98,7 +96,9 @@ class Agenda(inkycal_module):
|
||||
|
||||
# Calculate the max number of lines that can fit on the image
|
||||
line_spacing = 1
|
||||
line_height = int(self.font.getsize('hg')[1]) + line_spacing
|
||||
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = im_height // line_height
|
||||
logger.debug(f'max lines: {max_lines}')
|
||||
@@ -109,9 +109,11 @@ class Agenda(inkycal_module):
|
||||
|
||||
# Create a list of dates for the next days
|
||||
agenda_events = [
|
||||
{'begin': today.shift(days=+_),
|
||||
'title': today.shift(days=+_).format(
|
||||
self.date_format, locale=self.language)}
|
||||
{
|
||||
'begin': today.shift(days=+_),
|
||||
'title': today.shift(days=+_).format(
|
||||
self.date_format, locale=self.language)
|
||||
}
|
||||
for _ in range(max_lines)]
|
||||
|
||||
# Load icalendar from config
|
||||
@@ -133,9 +135,9 @@ class Agenda(inkycal_module):
|
||||
# parser.show_events()
|
||||
|
||||
# Set the width for date, time and event titles
|
||||
date_width = int(max([self.font.getsize(
|
||||
dates['begin'].format(self.date_format, locale=self.language))[0]
|
||||
for dates in agenda_events]) * 1.2)
|
||||
date_width = int(max([self.font.getlength(
|
||||
dates['begin'].format(self.date_format, locale=self.language))
|
||||
for dates in agenda_events]) * 1.2)
|
||||
logger.debug(f'date_width: {date_width}')
|
||||
|
||||
# Calculate positions for each line
|
||||
@@ -147,9 +149,10 @@ class Agenda(inkycal_module):
|
||||
logger.info('Managed to parse events from urls')
|
||||
|
||||
# Find out how much space the event times take
|
||||
time_width = int(max([self.font.getsize(
|
||||
events['begin'].format(self.time_format, locale=self.language))[0]
|
||||
for events in upcoming_events]) * 1.2)
|
||||
|
||||
time_width = int(max([self.font.getlength(
|
||||
events['begin'].format(self.time_format, locale=self.language))
|
||||
for events in upcoming_events]) * 1.2)
|
||||
logger.debug(f'time_width: {time_width}')
|
||||
|
||||
# Calculate x-pos for time
|
||||
@@ -224,7 +227,3 @@ class Agenda(inkycal_module):
|
||||
|
||||
# return the images ready for the display
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone mode')
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Inkycal Calendar Module
|
||||
Copyright by aceinnolab
|
||||
@@ -110,7 +108,8 @@ class Calendar(inkycal_module):
|
||||
|
||||
# Allocate space for month-names, weekdays etc.
|
||||
month_name_height = int(im_height * 0.10)
|
||||
weekdays_height = int(self.font.getsize('hg')[1] * 1.25)
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
weekdays_height = int((text_bbox_height[3] - text_bbox_height[1])* 1.25)
|
||||
logger.debug(f"month_name_height: {month_name_height}")
|
||||
logger.debug(f"weekdays_height: {weekdays_height}")
|
||||
|
||||
@@ -182,15 +181,15 @@ class Calendar(inkycal_module):
|
||||
]
|
||||
logger.debug(f'weekday names: {weekday_names}')
|
||||
|
||||
for idx, weekday in enumerate(weekday_pos):
|
||||
for index, weekday in enumerate(weekday_pos):
|
||||
write(
|
||||
im_black,
|
||||
weekday,
|
||||
(icon_width, weekdays_height),
|
||||
weekday_names[idx],
|
||||
weekday_names[index],
|
||||
font=self.font,
|
||||
autofit=True,
|
||||
fill_height=1.0,
|
||||
fill_height=0.9,
|
||||
)
|
||||
|
||||
# Create a calendar template and flatten (remove nestings)
|
||||
@@ -207,6 +206,10 @@ class Calendar(inkycal_module):
|
||||
# remove zeros from calendar since they are not required
|
||||
calendar_flat = [num for num in calendar_flat if num != 0]
|
||||
|
||||
# ensure all numbers have the same size
|
||||
fontsize_numbers = int(min(icon_width, icon_height) * 0.5)
|
||||
number_font = ImageFont.truetype(self.font.path, fontsize_numbers)
|
||||
|
||||
# Add the numbers on the correct positions
|
||||
for number in calendar_flat:
|
||||
if number != int(now.day):
|
||||
@@ -215,9 +218,7 @@ class Calendar(inkycal_module):
|
||||
grid[number],
|
||||
(icon_width, icon_height),
|
||||
str(number),
|
||||
font=self.num_font,
|
||||
fill_height=0.5,
|
||||
fill_width=0.5,
|
||||
font=number_font,
|
||||
)
|
||||
|
||||
# Draw a red/black circle with the current day of month in white
|
||||
@@ -262,10 +263,10 @@ class Calendar(inkycal_module):
|
||||
from inkycal.modules.ical_parser import iCalendar
|
||||
|
||||
# find out how many lines can fit at max in the event section
|
||||
line_spacing = 0
|
||||
max_event_lines = events_height // (
|
||||
self.font.getsize('hg')[1] + line_spacing
|
||||
)
|
||||
line_spacing = 2
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
max_event_lines = events_height // (line_height + line_spacing)
|
||||
|
||||
# generate list of coordinates for each line
|
||||
events_offset = im_height - events_height
|
||||
@@ -293,14 +294,27 @@ class Calendar(inkycal_module):
|
||||
month_events = parser.get_events(month_start, month_end, self.timezone)
|
||||
parser.sort()
|
||||
self.month_events = month_events
|
||||
|
||||
# Initialize days_with_events as an empty list
|
||||
days_with_events = []
|
||||
|
||||
# find out on which days of this month events are taking place
|
||||
days_with_events = [
|
||||
int(events['begin'].format('D')) for events in month_events
|
||||
]
|
||||
# Handle multi-day events by adding all days between start and end
|
||||
for event in month_events:
|
||||
start_date = event['begin'].date()
|
||||
end_date = event['end'].date()
|
||||
|
||||
# Convert start and end dates to arrow objects with timezone
|
||||
start = arrow.get(event['begin'].date(), tzinfo=self.timezone)
|
||||
end = arrow.get(event['end'].date(), tzinfo=self.timezone)
|
||||
|
||||
# Use arrow's range function for generating dates
|
||||
for day in arrow.Arrow.range('day', start, end):
|
||||
day_num = int(day.format('D')) # get day number using arrow's format method
|
||||
if day_num not in days_with_events:
|
||||
days_with_events.append(day_num)
|
||||
|
||||
# remove duplicates (more than one event in a single day)
|
||||
list(set(days_with_events)).sort()
|
||||
days_with_events = sorted(set(days_with_events))
|
||||
self._days_with_events = days_with_events
|
||||
|
||||
# Draw a border with specified parameters around days with events
|
||||
@@ -329,31 +343,18 @@ class Calendar(inkycal_module):
|
||||
# Find out how much space (width) the date format requires
|
||||
lang = self.language
|
||||
|
||||
date_width = int(
|
||||
max(
|
||||
(
|
||||
self.font.getsize(
|
||||
events['begin'].format(self.date_format, locale=lang)
|
||||
)[0]
|
||||
for events in upcoming_events
|
||||
)
|
||||
)
|
||||
* 1.1
|
||||
date_width = int(max((
|
||||
self.font.getlength(events['begin'].format(self.date_format, locale=lang))
|
||||
for events in upcoming_events))* 1.1
|
||||
)
|
||||
|
||||
time_width = int(
|
||||
max(
|
||||
(
|
||||
self.font.getsize(
|
||||
events['begin'].format(self.time_format, locale=lang)
|
||||
)[0]
|
||||
for events in upcoming_events
|
||||
)
|
||||
)
|
||||
* 1.1
|
||||
time_width = int(max((
|
||||
self.font.getlength(events['begin'].format(self.time_format, locale=lang))
|
||||
for events in upcoming_events))* 1.1
|
||||
)
|
||||
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
|
||||
event_width_s = im_width - date_width - time_width
|
||||
event_width_l = im_width - date_width
|
||||
@@ -365,7 +366,13 @@ class Calendar(inkycal_module):
|
||||
cursor = 0
|
||||
for event in upcoming_events:
|
||||
if cursor < len(event_lines):
|
||||
the_name = event['title']
|
||||
event_duration = (event['end'] - event['begin']).days
|
||||
if event_duration > 1:
|
||||
# Format the duration using Arrow's localization
|
||||
days_translation = arrow.get().shift(days=event_duration).humanize(only_distance=True, locale=lang)
|
||||
the_name = f"{event['title']} ({days_translation})"
|
||||
else:
|
||||
the_name = event['title']
|
||||
the_date = event['begin'].format(self.date_format, locale=lang)
|
||||
the_time = event['begin'].format(self.time_format, locale=lang)
|
||||
# logger.debug(f"name:{the_name} date:{the_date} time:{the_time}")
|
||||
@@ -411,19 +418,16 @@ class Calendar(inkycal_module):
|
||||
cursor += 1
|
||||
else:
|
||||
symbol = '- '
|
||||
while self.font.getsize(symbol)[0] < im_width * 0.9:
|
||||
|
||||
while self.font.getlength(symbol) < im_width * 0.9:
|
||||
symbol += ' -'
|
||||
write(
|
||||
im_black,
|
||||
event_lines[0],
|
||||
(im_width, self.font.getsize(symbol)[1]),
|
||||
(im_width, line_height),
|
||||
symbol,
|
||||
font=self.font,
|
||||
)
|
||||
|
||||
# return the images ready for the display
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone mode')
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Feeds module for InkyCal Project
|
||||
Copyright by aceinnolab
|
||||
@@ -91,9 +89,11 @@ class Feeds(inkycal_module):
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
max_lines = (im_height // (line_height + line_spacing))
|
||||
|
||||
# Calculate padding from top so the lines look centralised
|
||||
spacing_top = int(im_height % line_height / 2)
|
||||
@@ -149,7 +149,3 @@ class Feeds(inkycal_module):
|
||||
|
||||
# return images
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone/debug mode')
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
iCanHazDadJoke module for InkyCal Project
|
||||
Special thanks to Erik Fredericks (@efredericks) for the template!
|
||||
@@ -54,10 +52,11 @@ class Jokes(inkycal_module):
|
||||
raise NetworkNotReachableError
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
line_spacing = 5
|
||||
text_bbox = self.font.getbbox("hg")
|
||||
line_height = text_bbox[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
max_lines = (im_height // (line_height + line_spacing))
|
||||
|
||||
logger.debug(f"max_lines: {max_lines}")
|
||||
|
||||
@@ -97,7 +96,3 @@ class Jokes(inkycal_module):
|
||||
|
||||
# Return images for black and colour channels
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone/debug mode')
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#!python3
|
||||
"""
|
||||
Stocks Module for Inkycal Project
|
||||
|
||||
@@ -10,26 +9,18 @@ Version 0.1: Migration to Inkycal 2.0.0b
|
||||
|
||||
by https://github.com/worstface
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
|
||||
from inkycal.modules.template import inkycal_module
|
||||
from inkycal.custom import write, internet_available
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
from matplotlib import pyplot
|
||||
|
||||
try:
|
||||
import yfinance as yf
|
||||
except ImportError:
|
||||
print('yfinance is not installed! Please install with:')
|
||||
print('pip3 install yfinance')
|
||||
from inkycal.custom import write, internet_available
|
||||
from inkycal.modules.template import inkycal_module
|
||||
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.image as mpimg
|
||||
except ImportError:
|
||||
print('matplotlib is not installed! Please install with:')
|
||||
print('pip3 install matplotlib')
|
||||
import yfinance as yf
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.image as mpimg
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -79,26 +70,24 @@ class Stocks(inkycal_module):
|
||||
im_colour = Image.new('RGB', size=im_size, color='white')
|
||||
|
||||
# Create tmp path
|
||||
tmpPath = '/tmp/inkycal_stocks/'
|
||||
tmpPath = 'temp/'
|
||||
|
||||
try:
|
||||
if not os.path.exists(tmpPath):
|
||||
os.mkdir(tmpPath)
|
||||
print(f"Successfully created tmp directory {tmpPath} ")
|
||||
except OSError:
|
||||
print(f"Creation of tmp directory {tmpPath} failed")
|
||||
if not os.path.exists(tmpPath):
|
||||
print(f"Creating tmp directory {tmpPath}")
|
||||
os.mkdir(tmpPath)
|
||||
|
||||
# Check if internet is available
|
||||
if internet_available() == True:
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise Exception('Network could not be reached :/')
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
text_bbox = self.font.getbbox("hg")
|
||||
line_height = text_bbox[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
max_lines = (im_height // (line_height + line_spacing))
|
||||
|
||||
logger.debug(f"max_lines: {max_lines}")
|
||||
|
||||
@@ -211,7 +200,7 @@ class Stocks(inkycal_module):
|
||||
else:
|
||||
parsed_tickers_colour.append("")
|
||||
|
||||
if (_ < len(tickerCount)):
|
||||
if _ < len(tickerCount):
|
||||
parsed_tickers.append("")
|
||||
parsed_tickers_colour.append("")
|
||||
|
||||
@@ -232,9 +221,10 @@ class Stocks(inkycal_module):
|
||||
logger.info(f'chartSpace is...{im_width} {im_height}')
|
||||
logger.info(f'open chart ...{chartPath}')
|
||||
chartImage = Image.open(chartPath)
|
||||
chartImage.thumbnail((im_width / 4, line_height * 4), Image.BICUBIC)
|
||||
chartImage.thumbnail((int(im_width / 4), int(line_height * 4)), Image.BICUBIC)
|
||||
pyplot.close()
|
||||
|
||||
chartPasteX = im_width - (chartImage.width)
|
||||
chartPasteX = im_width - chartImage.width
|
||||
chartPasteY = line_height * 5 * _
|
||||
logger.info(f'pasting chart image with index {_} to...{chartPasteX} {chartPasteY}')
|
||||
|
||||
@@ -265,6 +255,3 @@ class Stocks(inkycal_module):
|
||||
# Save image of black and colour channel in image-folder
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('running module in standalone/debug mode')
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#!python3
|
||||
"""
|
||||
Textfile module for InkyCal Project
|
||||
|
||||
@@ -7,44 +6,29 @@ If the content is too long, it will be truncated from the back until it fits
|
||||
|
||||
Copyright by aceinnolab
|
||||
"""
|
||||
from inkycal.modules.template import inkycal_module
|
||||
from inkycal.custom import *
|
||||
|
||||
from urllib.request import urlopen
|
||||
|
||||
from inkycal.custom import *
|
||||
from inkycal.modules.template import inkycal_module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TextToDisplay(inkycal_module):
|
||||
"""TextToDisplay module
|
||||
"""TextToDisplay module - Display text from a local file on the display
|
||||
"""
|
||||
|
||||
name = "Text module - Display text from a local file on the display"
|
||||
|
||||
requires = {
|
||||
"filepath": {
|
||||
"label": "Please enter a filepath or URL pointing to a .txt file",
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize inkycal_textfile_to_display module"""
|
||||
|
||||
super().__init__(config)
|
||||
|
||||
config = config['config']
|
||||
|
||||
# Check if all required parameters are present
|
||||
for param in self.requires:
|
||||
if param not in config:
|
||||
raise Exception(f'config is missing {param}')
|
||||
|
||||
# required parameters
|
||||
self.filepath = config["filepath"]
|
||||
|
||||
self.make_request = True if self.filepath.startswith("https://") else False
|
||||
|
||||
|
||||
# give an OK message
|
||||
print(f'{__name__} loaded')
|
||||
|
||||
@@ -66,17 +50,12 @@ class TextToDisplay(inkycal_module):
|
||||
im_black = Image.new('RGB', size=im_size, color='white')
|
||||
im_colour = Image.new('RGB', size=im_size, color='white')
|
||||
|
||||
# Check if internet is available
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise NetworkNotReachableError
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
line_spacing = 4
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
max_lines = im_height // line_height
|
||||
|
||||
# Calculate padding from top so the lines look centralised
|
||||
spacing_top = int(im_height % line_height / 2)
|
||||
@@ -87,6 +66,11 @@ class TextToDisplay(inkycal_module):
|
||||
|
||||
if self.make_request:
|
||||
logger.info("Detected http path, making request")
|
||||
# Check if internet is available
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise NetworkNotReachableError
|
||||
file_content = urlopen(self.filepath).read().decode('utf-8')
|
||||
else:
|
||||
# Create list containing all lines
|
||||
@@ -111,7 +95,3 @@ class TextToDisplay(inkycal_module):
|
||||
|
||||
# return images
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone/debug mode')
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Inkycal Todoist Module
|
||||
Copyright by aceinnolab
|
||||
@@ -86,9 +84,10 @@ class Todoist(inkycal_module):
|
||||
|
||||
# Set some parameters for formatting todos
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
max_lines = im_height // line_height
|
||||
|
||||
# Calculate padding from top so the lines look centralised
|
||||
spacing_top = int(im_height % line_height / 2)
|
||||
@@ -197,7 +196,3 @@ class Todoist(inkycal_module):
|
||||
|
||||
# return the images ready for the display
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone/debug mode')
|
||||
|
@@ -12,7 +12,7 @@ import math
|
||||
import decimal
|
||||
import arrow
|
||||
|
||||
from pyowm.owm import OWM
|
||||
from inkycal.custom import OpenWeatherMap
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -95,7 +95,7 @@ class Weather(inkycal_module):
|
||||
self.use_beaufort = config['use_beaufort']
|
||||
|
||||
# additional configuration
|
||||
self.owm = OWM(self.api_key).weather_manager()
|
||||
self.owm = OpenWeatherMap(api_key=self.api_key, city_id=self.location, units=config['units'])
|
||||
self.timezone = get_system_tz()
|
||||
self.locale = config['language']
|
||||
self.weatherfont = ImageFont.truetype(
|
||||
@@ -104,6 +104,42 @@ class Weather(inkycal_module):
|
||||
# give an OK message
|
||||
print(f"{__name__} loaded")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def mps_to_beaufort(meters_per_second:float) -> int:
|
||||
"""Map meters per second to the beaufort scale.
|
||||
|
||||
Args:
|
||||
meters_per_second:
|
||||
float representing meters per seconds
|
||||
|
||||
Returns:
|
||||
an integer of the beaufort scale mapping the input
|
||||
"""
|
||||
thresholds = [0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.7, 24.5, 28.4]
|
||||
return next((i for i, threshold in enumerate(thresholds) if meters_per_second < threshold), 11)
|
||||
|
||||
@staticmethod
|
||||
def mps_to_mph(meters_per_second:float) -> float:
|
||||
"""Map meters per second to miles per hour, rounded to one decimal place.
|
||||
|
||||
Args:
|
||||
meters_per_second:
|
||||
float representing meters per seconds.
|
||||
|
||||
Returns:
|
||||
float representing the input value in miles per hour.
|
||||
"""
|
||||
# 1 m/s is approximately equal to 2.23694 mph
|
||||
miles_per_hour = meters_per_second * 2.23694
|
||||
return round(miles_per_hour, 1)
|
||||
|
||||
@staticmethod
|
||||
def celsius_to_fahrenheit(celsius:int or float):
|
||||
"""Converts the given temperate from degrees Celsius to Fahrenheit."""
|
||||
fahrenheit = (celsius * 9 / 5) + 32
|
||||
return fahrenheit
|
||||
|
||||
def generate_image(self):
|
||||
"""Generate image for this module"""
|
||||
|
||||
@@ -124,7 +160,11 @@ class Weather(inkycal_module):
|
||||
raise NetworkNotReachableError
|
||||
|
||||
def get_moon_phase():
|
||||
"""Calculate the current (approximate) moon phase"""
|
||||
"""Calculate the current (approximate) moon phase
|
||||
|
||||
Returns:
|
||||
The corresponding moonphase-icon.
|
||||
"""
|
||||
|
||||
dec = decimal.Decimal
|
||||
diff = now - arrow.get(2001, 1, 1)
|
||||
@@ -154,7 +194,7 @@ class Weather(inkycal_module):
|
||||
return answer
|
||||
|
||||
# Lookup-table for weather icons and weather codes
|
||||
weathericons = {
|
||||
weather_icons = {
|
||||
'01d': '\uf00d',
|
||||
'02d': '\uf002',
|
||||
'03d': '\uf013',
|
||||
@@ -227,26 +267,26 @@ class Weather(inkycal_module):
|
||||
# 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)
|
||||
text_width, text_height = font.getbbox(text)[2:]
|
||||
|
||||
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.getbbox(text)[2:]
|
||||
|
||||
text_width, text_height = font.getsize(text)
|
||||
text_width, text_height = font.getbbox(text)[2:]
|
||||
|
||||
# Align text to desired position
|
||||
x = int((box_width / 2) - (text_width / 2))
|
||||
y = int((box_height / 2) - (text_height / 2) - (icon_size_correction[icon] * size) / 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:
|
||||
if rotation:
|
||||
space.rotate(rotation, expand=True)
|
||||
|
||||
# Update only region with text (add text with transparent background)
|
||||
@@ -350,14 +390,9 @@ class Weather(inkycal_module):
|
||||
temp_fc4 = (col7, row3)
|
||||
|
||||
# Create current-weather and weather-forecast objects
|
||||
if self.location.isdigit():
|
||||
logging.debug('looking up location by ID')
|
||||
weather = self.owm.weather_at_id(int(self.location)).weather
|
||||
forecast = self.owm.forecast_at_id(int(self.location), '3h')
|
||||
else:
|
||||
logging.debug('looking up location by string')
|
||||
weather = self.owm.weather_at_place(self.location).weather
|
||||
forecast = self.owm.forecast_at_place(self.location, '3h')
|
||||
logging.debug('looking up location by ID')
|
||||
weather = self.owm.get_current_weather()
|
||||
forecast = self.owm.get_weather_forecast()
|
||||
|
||||
# Set decimals
|
||||
dec_temp = None if self.round_temperature == True else 1
|
||||
@@ -369,12 +404,14 @@ class Weather(inkycal_module):
|
||||
elif self.units == 'imperial':
|
||||
temp_unit = 'fahrenheit'
|
||||
|
||||
logging.debug(f'temperature unit: {temp_unit}')
|
||||
logging.debug(f'temperature unit: {self.units}')
|
||||
logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}')
|
||||
|
||||
# Get current time
|
||||
now = arrow.utcnow()
|
||||
|
||||
fc_data = {}
|
||||
|
||||
if self.forecast_interval == 'hourly':
|
||||
|
||||
logger.debug("getting hourly forecasts")
|
||||
@@ -386,21 +423,22 @@ class Weather(inkycal_module):
|
||||
else:
|
||||
hour_gap = 3
|
||||
|
||||
# Create timings for hourly forcasts
|
||||
# Create timings for hourly forecasts
|
||||
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]
|
||||
forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in forecast_timings]
|
||||
|
||||
# Add forecast-data to fc_data dictionary
|
||||
fc_data = {}
|
||||
for forecast in forecasts:
|
||||
temp = '{}°'.format(round(
|
||||
forecast.temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
|
||||
if self.units == "metric":
|
||||
temp = f"{round(weather['main']['temp'], ndigits=dec_temp)}°C"
|
||||
else:
|
||||
temp = f"{round(self.celsius_to_fahrenheit(weather['weather']['main']['temp']), ndigits=dec_temp)}°F"
|
||||
|
||||
icon = forecast.weather_icon_name
|
||||
icon = forecast["weather"][0]["icon"]
|
||||
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
|
||||
'temp': temp,
|
||||
'icon': icon,
|
||||
@@ -412,38 +450,35 @@ class Weather(inkycal_module):
|
||||
|
||||
logger.debug("getting daily forecasts")
|
||||
|
||||
def calculate_forecast(days_from_today):
|
||||
def calculate_forecast(days_from_today) -> dict:
|
||||
"""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]
|
||||
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]
|
||||
forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in time_range]
|
||||
|
||||
# Get all temperatures for this day
|
||||
daily_temp = [round(_.temperature(unit=temp_unit)['temp'],
|
||||
ndigits=dec_temp) for _ in forecasts]
|
||||
daily_temp = [round(_["main"]["temp"]) for _ in forecasts]
|
||||
# Calculate min. and max. temp for this day
|
||||
temp_range = f'{max(daily_temp)}°/{min(daily_temp)}°'
|
||||
temp_range = f'{min(daily_temp)}°/{max(daily_temp)}°'
|
||||
|
||||
# Get all weather icon codes for this day
|
||||
daily_icons = [_.weather_icon_name for _ in forecasts]
|
||||
daily_icons = [_["weather"][0]["icon"] 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)
|
||||
weekday = now.shift(days=days_from_today).format('ddd', locale=self.locale)
|
||||
return {'temp': temp_range, 'icon': status, 'stamp': weekday}
|
||||
|
||||
forecasts = [calculate_forecast(days) for days in range(1, 5)]
|
||||
|
||||
fc_data = {}
|
||||
for forecast in forecasts:
|
||||
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
|
||||
'temp': forecast['temp'],
|
||||
@@ -455,13 +490,15 @@ class Weather(inkycal_module):
|
||||
logger.debug((key, val))
|
||||
|
||||
# Get some current weather details
|
||||
temperature = '{}°'.format(round(
|
||||
weather.temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
|
||||
if dec_temp != 0:
|
||||
temperature = f"{round(weather['main']['temp'])}°"
|
||||
else:
|
||||
temperature = f"{round(weather['main']['temp'],ndigits=dec_temp)}°"
|
||||
|
||||
weather_icon = weather.weather_icon_name
|
||||
humidity = str(weather.humidity)
|
||||
sunrise_raw = arrow.get(weather.sunrise_time()).to(self.timezone)
|
||||
sunset_raw = arrow.get(weather.sunset_time()).to(self.timezone)
|
||||
weather_icon = weather["weather"][0]["icon"]
|
||||
humidity = str(weather["main"]["humidity"])
|
||||
sunrise_raw = arrow.get(weather["sys"]["sunrise"]).to(self.timezone)
|
||||
sunset_raw = arrow.get(weather["sys"]["sunset"]).to(self.timezone)
|
||||
|
||||
logger.debug(f'weather_icon: {weather_icon}')
|
||||
|
||||
@@ -469,33 +506,29 @@ class Weather(inkycal_module):
|
||||
logger.debug('using 12 hour format for sunrise/sunset')
|
||||
sunrise = sunrise_raw.format('h:mm a')
|
||||
sunset = sunset_raw.format('h:mm a')
|
||||
|
||||
elif self.hour_format == 24:
|
||||
else:
|
||||
# 24 hours format
|
||||
logger.debug('using 24 hour format for sunrise/sunset')
|
||||
sunrise = sunrise_raw.format('H:mm')
|
||||
sunset = sunset_raw.format('H:mm')
|
||||
|
||||
# Format the windspeed to user preference
|
||||
# Format the wind-speed to user preference
|
||||
if self.use_beaufort:
|
||||
logger.debug("using beaufort for wind")
|
||||
wind = str(weather.wind(unit='beaufort')['speed'])
|
||||
|
||||
wind = str(self.mps_to_beaufort(weather["wind"]["speed"]))
|
||||
else:
|
||||
|
||||
if self.units == 'metric':
|
||||
logging.debug('getting windspeed in metric unit')
|
||||
wind = str(weather.wind(unit='meters_sec')['speed']) + 'm/s'
|
||||
logging.debug('getting wind speed in meters per second')
|
||||
wind = f"{weather['wind']['speed']} m/s"
|
||||
else:
|
||||
logging.debug('getting wind speed in imperial unit')
|
||||
wind = f"{self.mps_to_mph(weather['wind']['speed'])} miles/h"
|
||||
|
||||
elif self.units == 'imperial':
|
||||
logging.debug('getting windspeed in imperial unit')
|
||||
wind = str(weather.wind(unit='miles_hour')['speed']) + 'miles/h'
|
||||
|
||||
dec = decimal.Decimal
|
||||
moonphase = get_moon_phase()
|
||||
moon_phase = get_moon_phase()
|
||||
|
||||
# Fill weather details in col 1 (current weather icon)
|
||||
draw_icon(im_colour, weather_icon_pos, (col_width, im_height),
|
||||
weathericons[weather_icon])
|
||||
weather_icons[weather_icon])
|
||||
|
||||
# Fill weather details in col 2 (temp, humidity, wind)
|
||||
draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height),
|
||||
@@ -521,7 +554,7 @@ class Weather(inkycal_module):
|
||||
wind, font=self.font)
|
||||
|
||||
# Fill weather details in col 3 (moonphase, sunrise, sunset)
|
||||
draw_icon(im_colour, moonphase_pos, (col_width, row_height), moonphase)
|
||||
draw_icon(im_colour, moonphase_pos, (col_width, row_height), moon_phase)
|
||||
|
||||
draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051')
|
||||
write(im_black, sunrise_time_pos, (col_width - icon_small, row_height),
|
||||
@@ -535,7 +568,7 @@ class Weather(inkycal_module):
|
||||
for pos in range(1, len(fc_data) + 1):
|
||||
stamp = fc_data[f'fc{pos}']['stamp']
|
||||
|
||||
icon = weathericons[fc_data[f'fc{pos}']['icon']]
|
||||
icon = weather_icons[fc_data[f'fc{pos}']['icon']]
|
||||
temp = fc_data[f'fc{pos}']['temp']
|
||||
|
||||
write(im_black, eval(f'stamp_fc{pos}'), (col_width, row_height),
|
||||
@@ -548,7 +581,7 @@ class Weather(inkycal_module):
|
||||
border_h = row3 + row_height
|
||||
border_w = col_width - 3 # leave 3 pixels gap
|
||||
|
||||
# Add borders around each sub-section
|
||||
# Add borders around each subsection
|
||||
draw_border(im_black, (col1, row1), (col_width * 3 - 3, border_h),
|
||||
shrinkage=(0, 0))
|
||||
|
||||
|
164
inkycal/modules/inkycal_webshot.py
Normal file
164
inkycal/modules/inkycal_webshot.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Webshot module for Inkycal
|
||||
by https://github.com/worstface
|
||||
"""
|
||||
|
||||
from htmlwebshot import WebShot
|
||||
|
||||
from inkycal.custom import *
|
||||
from inkycal.modules.inky_image import Inkyimage as Images
|
||||
from inkycal.modules.template import inkycal_module
|
||||
from tests import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Webshot(inkycal_module):
|
||||
name = "Webshot - Displays screenshots of webpages"
|
||||
|
||||
# required parameters
|
||||
requires = {
|
||||
|
||||
"url": {
|
||||
"label": "Please enter the url",
|
||||
},
|
||||
"palette": {
|
||||
"label": "Which color palette should be used for the webshots?",
|
||||
"options": ["bw", "bwr", "bwy"]
|
||||
}
|
||||
}
|
||||
|
||||
optional = {
|
||||
|
||||
"crop_x": {
|
||||
"label": "Please enter the crop x-position",
|
||||
},
|
||||
"crop_y": {
|
||||
"label": "Please enter the crop y-position",
|
||||
},
|
||||
"crop_w": {
|
||||
"label": "Please enter the crop width",
|
||||
},
|
||||
"crop_h": {
|
||||
"label": "Please enter the crop height",
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, config):
|
||||
|
||||
super().__init__(config)
|
||||
|
||||
config = config['config']
|
||||
|
||||
self.url = config['url']
|
||||
self.palette = config['palette']
|
||||
|
||||
if "crop_h" in config and isinstance(config["crop_h"], str):
|
||||
self.crop_h = int(config["crop_h"])
|
||||
else:
|
||||
self.crop_h = 2000
|
||||
|
||||
if "crop_w" in config and isinstance(config["crop_w"], str):
|
||||
self.crop_w = int(config["crop_w"])
|
||||
else:
|
||||
self.crop_w = 2000
|
||||
|
||||
if "crop_x" in config and isinstance(config["crop_x"], str):
|
||||
self.crop_x = int(config["crop_x"])
|
||||
else:
|
||||
self.crop_x = 0
|
||||
|
||||
if "crop_y" in config and isinstance(config["crop_y"], str):
|
||||
self.crop_y = int(config["crop_y"])
|
||||
else:
|
||||
self.crop_y = 0
|
||||
|
||||
# give an OK message
|
||||
print(f'Inkycal webshot loaded')
|
||||
|
||||
def generate_image(self):
|
||||
"""Generate image for this module"""
|
||||
|
||||
# Create tmp path
|
||||
tmpFolder = Config.TEMP_PATH
|
||||
|
||||
if not os.path.exists(tmpFolder):
|
||||
print(f"Creating tmp directory {tmpFolder}")
|
||||
os.mkdir(tmpFolder)
|
||||
|
||||
# Define new image size with respect to padding
|
||||
im_width = int(self.width - (2 * self.padding_left))
|
||||
im_height = int(self.height - (2 * self.padding_top))
|
||||
im_size = im_width, im_height
|
||||
logger.info('image size: {} x {} px'.format(im_width, im_height))
|
||||
|
||||
# Create an image for black pixels and one for coloured pixels (required)
|
||||
im_black = Image.new('RGB', size=im_size, color='white')
|
||||
im_colour = Image.new('RGB', size=im_size, color='white')
|
||||
|
||||
# Check if internet is available
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise Exception('Network could not be reached :/')
|
||||
|
||||
logger.info(
|
||||
f'preparing webshot from {self.url}... cropH{self.crop_h} cropW{self.crop_w} cropX{self.crop_x} cropY{self.crop_y}')
|
||||
|
||||
shot = WebShot()
|
||||
|
||||
shot.params = {
|
||||
"--crop-x": self.crop_x,
|
||||
"--crop-y": self.crop_y,
|
||||
"--crop-w": self.crop_w,
|
||||
"--crop-h": self.crop_h,
|
||||
}
|
||||
|
||||
logger.info(f'getting webshot from {self.url}...')
|
||||
|
||||
try:
|
||||
shot.create_pic(url=self.url, output=f"{tmpFolder}/webshot.png")
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
print("If you have not already installed wkhtmltopdf, please use: sudo apt-get install wkhtmltopdf. See here for more details: https://github.com/1Danish-00/htmlwebshot/")
|
||||
raise Exception('Could not get webshot :/')
|
||||
|
||||
|
||||
logger.info(f'got webshot...')
|
||||
|
||||
webshotSpaceBlack = Image.new('RGBA', (im_width, im_height), (255, 255, 255, 255))
|
||||
webshotSpaceColour = Image.new('RGBA', (im_width, im_height), (255, 255, 255, 255))
|
||||
|
||||
im = Images()
|
||||
im.load(f'{tmpFolder}/webshot.png')
|
||||
im.remove_alpha()
|
||||
|
||||
imageAspectRatio = im_width / im_height
|
||||
webshotAspectRatio = im.image.width / im.image.height
|
||||
|
||||
if webshotAspectRatio > imageAspectRatio:
|
||||
imageScale = im_width / im.image.width
|
||||
else:
|
||||
imageScale = im_height / im.image.height
|
||||
|
||||
webshotHeight = int(im.image.height * imageScale)
|
||||
|
||||
im.resize(width=int(im.image.width * imageScale), height=webshotHeight)
|
||||
|
||||
im_webshot_black, im_webshot_colour = im.to_palette(self.palette)
|
||||
|
||||
webshotCenterPosY = int((im_height / 2) - (im.image.height / 2))
|
||||
|
||||
centerPosX = int((im_width / 2) - (im.image.width / 2))
|
||||
|
||||
webshotSpaceBlack.paste(im_webshot_black, (centerPosX, webshotCenterPosY))
|
||||
im_black.paste(webshotSpaceBlack)
|
||||
|
||||
webshotSpaceColour.paste(im_webshot_colour, (centerPosX, webshotCenterPosY))
|
||||
im_colour.paste(webshotSpaceColour)
|
||||
|
||||
im.clear()
|
||||
logger.info(f'added webshot image')
|
||||
|
||||
# Save image of black and colour channel in image-folder
|
||||
return im_black, im_colour
|
202
inkycal/modules/inkycal_xkcd.py
Normal file
202
inkycal/modules/inkycal_xkcd.py
Normal file
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
Inkycal XKCD module
|
||||
by https://github.com/worstface
|
||||
"""
|
||||
|
||||
import xkcd
|
||||
|
||||
from inkycal.custom import *
|
||||
from inkycal.modules.inky_image import Inkyimage as Images
|
||||
from inkycal.modules.template import inkycal_module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Xkcd(inkycal_module):
|
||||
name = "xkcd - Displays comics from xkcd.com by Randall Munroe"
|
||||
|
||||
# required parameters
|
||||
requires = {
|
||||
|
||||
"mode": {
|
||||
"label": "Please select the mode",
|
||||
"options": ["latest", "random"],
|
||||
"default": "latest"
|
||||
},
|
||||
"palette": {
|
||||
"label": "Which color palette should be used for the comic images?",
|
||||
"options": ["bw", "bwr", "bwy"]
|
||||
},
|
||||
"alt": {
|
||||
"label": "Would you like to add the alt text below the comic? If XKCD is not the only module you are showing, I recommend setting this to 'no'",
|
||||
"options": ["yes", "no"],
|
||||
"default": "no"
|
||||
},
|
||||
"filter": {
|
||||
"label": "Would you like to add a scaling filter? If the is far too big to be shown in the space you've allotted for it, the module will try to find another image for you. This only applies in random mode. If XKCD is not the only module you are showing, I recommend setting this to 'no'.",
|
||||
"options": ["yes", "no"],
|
||||
"default": "no"
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, config):
|
||||
|
||||
super().__init__(config)
|
||||
|
||||
config = config['config']
|
||||
|
||||
self.mode = config['mode']
|
||||
self.palette = config['palette']
|
||||
self.alt = config['alt']
|
||||
self.scale_filter = config['filter']
|
||||
|
||||
# give an OK message
|
||||
print(f'Inkycal XKCD loaded')
|
||||
|
||||
def generate_image(self):
|
||||
"""Generate image for this module"""
|
||||
|
||||
# Create tmp path
|
||||
tmpPath = f"{top_level}/temp"
|
||||
|
||||
if not os.path.exists(tmpPath):
|
||||
os.mkdir(tmpPath)
|
||||
|
||||
# Define new image size with respect to padding
|
||||
im_width = int(self.width - (2 * self.padding_left))
|
||||
im_height = int(self.height - (2 * self.padding_top))
|
||||
im_size = im_width, im_height
|
||||
logger.info('image size: {} x {} px'.format(im_width, im_height))
|
||||
|
||||
# Create an image for black pixels and one for coloured pixels (required)
|
||||
im_black = Image.new('RGB', size=im_size, color='white')
|
||||
im_colour = Image.new('RGB', size=im_size, color='white')
|
||||
|
||||
# Check if internet is available
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise Exception('Network could not be reached :/')
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
text_bbox = self.font.getbbox("hg")
|
||||
line_height = text_bbox[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = im_height // (line_height + line_spacing)
|
||||
|
||||
logger.debug(f"max_lines: {max_lines}")
|
||||
|
||||
# Calculate padding from top so the lines look centralised
|
||||
spacing_top = int(im_height % line_height / 2)
|
||||
|
||||
# Calculate line_positions
|
||||
line_positions = [(0, spacing_top + _ * line_height) for _ in range(max_lines)]
|
||||
|
||||
logger.debug(f'line positions: {line_positions}')
|
||||
|
||||
logger.info(f'getting xkcd comic...')
|
||||
|
||||
if self.mode == 'random':
|
||||
if self.scale_filter == 'no':
|
||||
xkcdComic = xkcd.getRandomComic()
|
||||
xkcdComic.download(output=tmpPath, outputFile='xkcdComic.png')
|
||||
else:
|
||||
perc = (2.1, 0.4)
|
||||
url = "test variable, not a real comic"
|
||||
while max(perc) > 1.75:
|
||||
print("looking for another comic, old comic was: ", perc, url)
|
||||
xkcdComic = xkcd.getRandomComic()
|
||||
xkcdComic.download(output=tmpPath, outputFile='xkcdComic.png')
|
||||
actual_size = Image.open(tmpPath + '/xkcdComic.png').size
|
||||
perc = (actual_size[0] / im_width, actual_size[1] / im_height)
|
||||
url = xkcdComic.getImageLink()
|
||||
print("found one! perc: ", perc, url)
|
||||
else:
|
||||
xkcdComic = xkcd.getLatestComic()
|
||||
xkcdComic.download(output=tmpPath, outputFile='xkcdComic.png')
|
||||
|
||||
logger.info(f'got xkcd comic...')
|
||||
title_lines = []
|
||||
title_lines.append(xkcdComic.getTitle())
|
||||
|
||||
altOffset = int(line_height * 1)
|
||||
|
||||
if self.alt == "yes":
|
||||
alt_text = xkcdComic.getAltText() # get the alt text, too (I break it up into multiple lines later on)
|
||||
|
||||
# break up the alt text into lines
|
||||
alt_lines = []
|
||||
current_line = ""
|
||||
for _ in alt_text.split(" "):
|
||||
# this breaks up the alt_text into words and creates each line by adding
|
||||
# one word at a time until the line is longer than the width of the module
|
||||
# then it appends the line to the alt_lines array and starts testing a new line
|
||||
# with the next word
|
||||
text_bbox = self.font.getbbox(current_line + _ + " ")
|
||||
|
||||
if text_bbox[2] < im_width:
|
||||
current_line = current_line + _ + " "
|
||||
else:
|
||||
alt_lines.append(current_line)
|
||||
current_line = _ + " "
|
||||
alt_lines.append(
|
||||
current_line) # this adds the last line to the array (or the only line, if the alt text is really short)
|
||||
altHeight = int(line_height * len(alt_lines)) + altOffset
|
||||
else:
|
||||
altHeight = 0 # this is added so that I don't need to add more "if alt is yes" conditionals when centering below. Now the centering code will work regardless of whether they want alttext or not
|
||||
|
||||
comicSpaceBlack = Image.new('RGBA', (im_width, im_height), (255, 255, 255, 255))
|
||||
comicSpaceColour = Image.new('RGBA', (im_width, im_height), (255, 255, 255, 255))
|
||||
|
||||
im = Images()
|
||||
im.load(f"{tmpPath}/xkcdComic.png")
|
||||
im.remove_alpha()
|
||||
|
||||
imageAspectRatio = im_width / im_height
|
||||
comicAspectRatio = im.image.width / im.image.height
|
||||
|
||||
if comicAspectRatio > imageAspectRatio:
|
||||
imageScale = im_width / im.image.width
|
||||
else:
|
||||
imageScale = im_height / im.image.height
|
||||
|
||||
comicHeight = int(im.image.height * imageScale)
|
||||
|
||||
headerHeight = int(line_height * 3 / 2)
|
||||
|
||||
if comicHeight + (headerHeight + altHeight) > im_height:
|
||||
comicHeight -= (headerHeight + altHeight)
|
||||
|
||||
im.resize(width=int(im.image.width * imageScale), height=comicHeight)
|
||||
|
||||
im_comic_black, im_comic_colour = im.to_palette(self.palette)
|
||||
|
||||
headerCenterPosY = int((im_height / 2) - ((im.image.height + headerHeight + altHeight) / 2))
|
||||
comicCenterPosY = int((im_height / 2) - ((im.image.height + headerHeight + altHeight) / 2) + headerHeight)
|
||||
altCenterPosY = int(
|
||||
(im_height / 2) - ((im.image.height + headerHeight + altHeight) / 2) + headerHeight + im.image.height)
|
||||
|
||||
centerPosX = int((im_width / 2) - (im.image.width / 2))
|
||||
|
||||
comicSpaceBlack.paste(im_comic_black, (centerPosX, comicCenterPosY))
|
||||
im_black.paste(comicSpaceBlack)
|
||||
|
||||
comicSpaceColour.paste(im_comic_colour, (centerPosX, comicCenterPosY))
|
||||
im_colour.paste(comicSpaceColour)
|
||||
|
||||
im.clear()
|
||||
logger.info(f'added comic image')
|
||||
|
||||
# Write the title on the black image
|
||||
write(im_black, (0, headerCenterPosY), (line_width, line_height),
|
||||
title_lines[0], font=self.font, alignment='center')
|
||||
|
||||
if self.alt == "yes":
|
||||
# write alt_text
|
||||
for _ in range(len(alt_lines)):
|
||||
write(im_black, (0, altCenterPosY + _ * line_height + altOffset), (line_width, line_height),
|
||||
alt_lines[_], font=self.font, alignment='left')
|
||||
|
||||
# Save image of black and colour channel in image-folder
|
||||
return im_black, im_colour
|
@@ -1 +0,0 @@
|
||||
from config import Config
|
@@ -1,34 +0,0 @@
|
||||
#!python
|
||||
"""
|
||||
Tests config
|
||||
"""
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# relative import
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# load config from corresponding file
|
||||
load_dotenv(os.path.join(basedir, '.env'))
|
||||
|
||||
|
||||
class Config:
|
||||
get = os.environ.get
|
||||
|
||||
# show generated images via preview?
|
||||
USE_PREVIEW = False
|
||||
|
||||
# ical_parser_test
|
||||
OPENWEATHERMAP_API_KEY = get("OPENWEATHERMAP_API_KEY")
|
||||
TEST_ICAL_URL = get("TEST_ICAL_URL")
|
||||
|
||||
# inkycal_agenda_test & inkycal_calendar_test
|
||||
SAMPLE_ICAL_URL = get("SAMPLE_ICAL_URL")
|
||||
|
||||
# inkycal_todoist_test
|
||||
TODOIST_API_KEY = get("TODOIST_API_KEY")
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -1,56 +0,0 @@
|
||||
#!python3
|
||||
"""
|
||||
iCalendar parser test (ical_parser)
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from urllib.request import urlopen
|
||||
|
||||
import arrow
|
||||
from inkycal.modules.ical_parser import iCalendar
|
||||
from inkycal.tests import Config
|
||||
|
||||
ical = iCalendar()
|
||||
test_ical = Config.TEST_ICAL_URL
|
||||
|
||||
|
||||
class ical_parser_test(unittest.TestCase):
|
||||
|
||||
def test_load_url(self):
|
||||
print('testing loading via URL...', end="")
|
||||
ical.load_url(test_ical)
|
||||
print('OK')
|
||||
|
||||
def test_get_events(self):
|
||||
print('testing parsing of events...', end="")
|
||||
ical.get_events(arrow.now(), arrow.now().shift(weeks=30))
|
||||
print('OK')
|
||||
|
||||
def test_sorting(self):
|
||||
print('testing sorting of events...', end="")
|
||||
ical.sort()
|
||||
print('OK')
|
||||
|
||||
def test_show_events(self):
|
||||
print('testing if events can be shown...', end="")
|
||||
ical.show_events()
|
||||
print('OK')
|
||||
|
||||
def test_laod_from_file(self):
|
||||
print('testing loading from file...', end="")
|
||||
dummy = str(urlopen(test_ical, timeout=10).read().decode())
|
||||
with open('dummy.ical', mode="w", encoding="utf-8") as file:
|
||||
file.write(dummy)
|
||||
ical.load_from_file('dummy.ical')
|
||||
print('OK')
|
||||
os.remove('dummy.ical')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,85 +0,0 @@
|
||||
#!python3
|
||||
"""
|
||||
inkycal_agenda unittest
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
from inkycal.modules import Agenda as Module
|
||||
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
sample_url = Config.SAMPLE_ICAL_URL
|
||||
|
||||
tests = [
|
||||
{
|
||||
"name": "Agenda",
|
||||
"config": {
|
||||
"size": [400, 200],
|
||||
"ical_urls": sample_url,
|
||||
"ical_files": None,
|
||||
"date_format": "ddd D MMM",
|
||||
"time_format": "HH:mm",
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Agenda",
|
||||
"config": {
|
||||
"size": [500, 800],
|
||||
"ical_urls": sample_url,
|
||||
"ical_files": None,
|
||||
"date_format": "ddd D MMM",
|
||||
"time_format": "HH:mm",
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Agenda",
|
||||
"config": {
|
||||
"size": [300, 800],
|
||||
"ical_urls": sample_url,
|
||||
"ical_files": None,
|
||||
"date_format": "ddd D MMM",
|
||||
"time_format": "HH:mm",
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class module_test(unittest.TestCase):
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..')
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,91 +0,0 @@
|
||||
#!python3
|
||||
"""
|
||||
inkycal_calendar unittest
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from inkycal.modules import Calendar as Module
|
||||
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
sample_url = Config.SAMPLE_ICAL_URL
|
||||
|
||||
tests = [
|
||||
{
|
||||
"name": "Calendar",
|
||||
"config": {
|
||||
"size": [500, 500],
|
||||
"week_starts_on": "Monday",
|
||||
"show_events": True,
|
||||
"ical_urls": sample_url,
|
||||
"ical_files": None,
|
||||
"date_format": "D MMM", "time_format": "HH:mm",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Calendar",
|
||||
"config": {
|
||||
"size": [400, 800],
|
||||
"week_starts_on": "Sunday",
|
||||
"show_events": True,
|
||||
"ical_urls": sample_url,
|
||||
"ical_files": None,
|
||||
"date_format": "D MMM", "time_format": "HH:mm",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Calendar",
|
||||
"config": {
|
||||
"size": [400, 800],
|
||||
"week_starts_on": "Monday",
|
||||
"show_events": False,
|
||||
"ical_urls": sample_url,
|
||||
"ical_files": None,
|
||||
"date_format": "D MMM", "time_format": "HH:mm",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Calendar",
|
||||
"config": {
|
||||
"size": [400, 800],
|
||||
"week_starts_on": "Monday",
|
||||
"show_events": True,
|
||||
"ical_urls": None,
|
||||
"ical_files": None,
|
||||
"date_format": "D MMM", "time_format": "HH:mm",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class module_test(unittest.TestCase):
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..', end="")
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,69 +0,0 @@
|
||||
#!python3
|
||||
"""
|
||||
inkycal_feeds unittest
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
from inkycal.modules import Feeds as Module
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
tests = [
|
||||
{
|
||||
"name": "Feeds",
|
||||
"config": {
|
||||
"size": [400, 200],
|
||||
"feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#",
|
||||
"shuffle_feeds": True,
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Feeds",
|
||||
"config": {
|
||||
"size": [400, 800],
|
||||
"feed_urls": "https://www.foodandco.fi/modules/MenuRss/MenuRss/CurrentDay?costNumber=3003&language=en",
|
||||
"shuffle_feeds": False,
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 14, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Feeds",
|
||||
"config": {
|
||||
"size": [400, 100],
|
||||
"feed_urls": "https://www.anekdot.ru/rss/export_top.xml",
|
||||
"shuffle_feeds": False,
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class module_test(unittest.TestCase):
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..')
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
im = merge(im_black, im_colour)
|
||||
im.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,128 +0,0 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
inkycal_image unittest
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
from PIL import Image
|
||||
|
||||
from inkycal.modules import Inkyimage as Module
|
||||
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
url = "https://github.com/aceinnolab/Inkycal/raw/assets/Repo/coffee.png"
|
||||
|
||||
im = Image.open(requests.get(url, stream=True).raw)
|
||||
im.save("test.png", "PNG")
|
||||
test_path = "test.png"
|
||||
|
||||
tests = [
|
||||
{
|
||||
"name": "Inkyimage",
|
||||
"config": {
|
||||
"size": [400, 200],
|
||||
"path": test_path,
|
||||
"palette": "bwr",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Inkyimage",
|
||||
"config": {
|
||||
"size": [800, 500],
|
||||
"path": test_path,
|
||||
"palette": "bwy",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Inkyimage",
|
||||
"config": {
|
||||
"size": [400, 100],
|
||||
"path": test_path,
|
||||
"palette": "bw",
|
||||
"autoflip": False,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Inkyimage",
|
||||
"config": {
|
||||
"size": [400, 100],
|
||||
"path": test_path,
|
||||
"palette": "bwr",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Inkyimage",
|
||||
"config": {
|
||||
"size": [400, 100],
|
||||
"path": test_path,
|
||||
"palette": "bwy",
|
||||
"autoflip": True,
|
||||
"orientation": "horizontal",
|
||||
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Inkyimage",
|
||||
"config": {
|
||||
"size": [500, 800],
|
||||
"path": test_path,
|
||||
"palette": "bw",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 0, "padding_y": 0, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Inkyimage",
|
||||
"config": {
|
||||
"size": [500, 800],
|
||||
"path": test_path,
|
||||
"palette": "bwr",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 20, "padding_y": 20, "fontsize": 12, "language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class module_test(unittest.TestCase):
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..')
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,70 +0,0 @@
|
||||
#!python3
|
||||
"""
|
||||
inkycal_jokes unittest
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
from inkycal.modules import Jokes as Module
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
tests = [
|
||||
{
|
||||
"name": "Jokes",
|
||||
"config": {
|
||||
"size": [300, 60],
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Jokes",
|
||||
"config": {
|
||||
"size": [300, 30],
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Jokes",
|
||||
"config": {
|
||||
"size": [100, 800],
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 18,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class module_test(unittest.TestCase):
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..')
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,175 +0,0 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Slideshow test (inkycal_slideshow)
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import requests
|
||||
from PIL import Image
|
||||
|
||||
from inkycal.modules import Slideshow as Module
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
if not os.path.exists("tmp"):
|
||||
os.mkdir("tmp")
|
||||
|
||||
im_urls = [
|
||||
"https://github.com/aceinnolab/Inkycal/raw/assets/Repo/coffee.png",
|
||||
"https://github.com/aceinnolab/Inkycal/raw/assets/Repo/coffee.png"
|
||||
]
|
||||
|
||||
for count, url in enumerate(im_urls):
|
||||
im = Image.open(requests.get(url, stream=True).raw)
|
||||
im.save(f"tmp/{count}.png", "PNG")
|
||||
|
||||
test_path = "tmp"
|
||||
|
||||
tests = [
|
||||
{
|
||||
"name": "Slideshow",
|
||||
"config": {
|
||||
"size": [400, 200],
|
||||
"path": test_path,
|
||||
"palette": "bwy",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Slideshow",
|
||||
"config": {
|
||||
"size": [800, 500],
|
||||
"path": test_path,
|
||||
"palette": "bw",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Slideshow",
|
||||
"config": {
|
||||
"size": [400, 100],
|
||||
"path": test_path,
|
||||
"palette": "bwr",
|
||||
"autoflip": False,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Slideshow",
|
||||
"config": {
|
||||
"size": [400, 100],
|
||||
"path": test_path,
|
||||
"palette": "bwy",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Slideshow",
|
||||
"config": {
|
||||
"size": [400, 100],
|
||||
"path": test_path,
|
||||
"palette": "bwy",
|
||||
"autoflip": True,
|
||||
"orientation": "horizontal",
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Slideshow",
|
||||
"config": {
|
||||
"size": [500, 800],
|
||||
"path": test_path,
|
||||
"palette": "bw",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 0,
|
||||
"padding_y": 0,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Slideshow",
|
||||
"config": {
|
||||
"size": [500, 800],
|
||||
"path": test_path,
|
||||
"palette": "bwr",
|
||||
"autoflip": True,
|
||||
"orientation": "vertical",
|
||||
"padding_x": 20,
|
||||
"padding_y": 20,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class module_test(unittest.TestCase):
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..')
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
def test_switch_to_next_image(self):
|
||||
print(f'testing switching to next images..')
|
||||
module = Module(tests[0])
|
||||
im_black, im_colour = module.generate_image()
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
im_black, im_colour = module.generate_image()
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
im_black, im_colour = module.generate_image()
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
print('OK')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,56 +0,0 @@
|
||||
# #!python3
|
||||
# """
|
||||
# inkycal_stocks unittest
|
||||
# """
|
||||
# import logging
|
||||
# import sys
|
||||
# import unittest
|
||||
# from inkycal.modules import Stocks as Module
|
||||
#
|
||||
# from inkycal.modules.inky_image import Inkyimage
|
||||
# from inkycal.tests import Config
|
||||
# preview = Inkyimage.preview
|
||||
# merge = Inkyimage.merge
|
||||
#
|
||||
# tests = [
|
||||
# {
|
||||
# "name": "Stocks",
|
||||
# "config": {
|
||||
# "size": [528, 30],
|
||||
# "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
|
||||
# "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
# }
|
||||
# },
|
||||
# {
|
||||
# "name": "Stocks",
|
||||
# "config": {
|
||||
# "size": [528, 50],
|
||||
# "tickers": [],
|
||||
# "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
|
||||
# }
|
||||
# }
|
||||
# ]
|
||||
#
|
||||
#
|
||||
# class module_test(unittest.TestCase):
|
||||
# def test_get_config(self):
|
||||
# print('getting data for web-ui...', end="")
|
||||
# Module.get_config()
|
||||
# print('OK')
|
||||
#
|
||||
# def test_generate_image(self):
|
||||
# for test in tests:
|
||||
# print(f'test {tests.index(test) + 1} generating image..')
|
||||
# module = Module(test)
|
||||
# im_black, im_colour = module.generate_image()
|
||||
# print('OK')
|
||||
# if Config.USE_PREVIEW:
|
||||
# preview(merge(im_black, im_colour))
|
||||
#
|
||||
#
|
||||
# if __name__ == '__main__':
|
||||
# logger = logging.getLogger()
|
||||
# logger.level = logging.DEBUG
|
||||
# logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
#
|
||||
# unittest.main()
|
@@ -1,59 +0,0 @@
|
||||
#!python3
|
||||
"""
|
||||
inkycal_todoist unittest
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
from inkycal.modules import Todoist as Module
|
||||
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
api_key = Config.TODOIST_API_KEY
|
||||
|
||||
tests = [
|
||||
{
|
||||
"name": "Todoist",
|
||||
"config": {
|
||||
"size": [400, 1000],
|
||||
"api_key": api_key,
|
||||
"project_filter": None,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class module_test(unittest.TestCase):
|
||||
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
if api_key:
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..')
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
merge(im_black, im_colour).show()
|
||||
else:
|
||||
print('No api key given, omitting test')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,196 +0,0 @@
|
||||
#!python3
|
||||
"""
|
||||
inkycal_weather unittest
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
from inkycal.modules import Weather as Module
|
||||
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
owm_api_key = Config.OPENWEATHERMAP_API_KEY
|
||||
location = 'Stuttgart, DE'
|
||||
|
||||
tests = [
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Weather",
|
||||
"config": {
|
||||
"size": [500, 100],
|
||||
"api_key": owm_api_key,
|
||||
"location": location,
|
||||
"round_temperature": True,
|
||||
"round_windspeed": True,
|
||||
"forecast_interval": "daily",
|
||||
"units": "metric",
|
||||
"hour_format": "12",
|
||||
"use_beaufort": True,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Weather",
|
||||
"config": {
|
||||
"size": [500, 150],
|
||||
"api_key": owm_api_key,
|
||||
"location": "2643123",
|
||||
"round_temperature": True,
|
||||
"round_windspeed": True,
|
||||
"forecast_interval": "daily",
|
||||
"units": "metric",
|
||||
"hour_format": "12",
|
||||
"use_beaufort": True,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Weather",
|
||||
"config": {
|
||||
"size": [500, 200],
|
||||
"api_key": owm_api_key,
|
||||
"location": location,
|
||||
"round_temperature": False,
|
||||
"round_windspeed": True,
|
||||
"forecast_interval": "daily",
|
||||
"units": "metric",
|
||||
"hour_format": "12",
|
||||
"use_beaufort": True,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Weather",
|
||||
"config": {
|
||||
"size": [500, 100],
|
||||
"api_key": owm_api_key,
|
||||
"location": location,
|
||||
"round_temperature": True,
|
||||
"round_windspeed": False,
|
||||
"forecast_interval": "daily",
|
||||
"units": "metric",
|
||||
"hour_format": "12",
|
||||
"use_beaufort": True,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Weather",
|
||||
"config": {
|
||||
"size": [500, 150],
|
||||
"api_key": owm_api_key,
|
||||
"location": location,
|
||||
"round_temperature": True,
|
||||
"round_windspeed": True,
|
||||
"forecast_interval": "hourly",
|
||||
"units": "metric",
|
||||
"hour_format": "12",
|
||||
"use_beaufort": True,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Weather",
|
||||
"config": {
|
||||
"size": [500, 150],
|
||||
"api_key": owm_api_key,
|
||||
"location": location,
|
||||
"round_temperature": True,
|
||||
"round_windspeed": True,
|
||||
"forecast_interval": "daily",
|
||||
"units": "imperial",
|
||||
"hour_format": "12",
|
||||
"use_beaufort": True,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Weather",
|
||||
"config": {
|
||||
"size": [500, 100],
|
||||
"api_key": owm_api_key,
|
||||
"location": location,
|
||||
"round_temperature": True,
|
||||
"round_windspeed": True,
|
||||
"forecast_interval": "daily",
|
||||
"units": "metric",
|
||||
"hour_format": "24",
|
||||
"use_beaufort": True,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Weather",
|
||||
"config": {
|
||||
"size": [500, 100],
|
||||
"api_key": owm_api_key,
|
||||
"location": location,
|
||||
"round_temperature": True,
|
||||
"round_windspeed": True,
|
||||
"forecast_interval": "daily",
|
||||
"units": "metric",
|
||||
"hour_format": "12",
|
||||
"use_beaufort": False,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class module_test(unittest.TestCase):
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..')
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
@@ -1,49 +0,0 @@
|
||||
|
||||
{
|
||||
"model": "epd_7_in_5_v3_colour",
|
||||
"update_interval": 5,
|
||||
"orientation": 0,
|
||||
"info_section": true,
|
||||
"info_section_height": 70,
|
||||
"calibration_hours": [0, 12, 18],
|
||||
"border_around_modules": true,
|
||||
"modules": [
|
||||
{
|
||||
"position": 1,
|
||||
"name": "Jokes",
|
||||
"config": {
|
||||
"size": [528, 80],
|
||||
"padding_x": 10,"padding_y": 10,"fontsize": 14,"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"name": "Calendar",
|
||||
"config": {
|
||||
"size": [
|
||||
528,
|
||||
343
|
||||
],
|
||||
"week_starts_on": "Monday",
|
||||
"show_events": true,
|
||||
"ical_urls": "https://www.officeholidays.com/ics-fed/usa",
|
||||
"ical_files": null,
|
||||
"date_format": "D MMM",
|
||||
"time_format": "HH:mm",
|
||||
"padding_x": 10,"padding_y": 10,"fontsize": 14,"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"name": "Feeds",
|
||||
"config": {
|
||||
"size": [528,132],
|
||||
"feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#",
|
||||
"shuffle_feeds": true,
|
||||
"padding_x": 10,"padding_y": 10,"fontsize": 14,"language": "en"
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -1,128 +0,0 @@
|
||||
#!python3
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from inkycal.modules import TextToDisplay as Module
|
||||
|
||||
from inkycal.modules.inky_image import Inkyimage
|
||||
from inkycal.tests import Config
|
||||
preview = Inkyimage.preview
|
||||
merge = Inkyimage.merge
|
||||
|
||||
file_path = None
|
||||
|
||||
dummy_data = [
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', ' Donec feugiat facilisis neque vel blandit.',
|
||||
'Integer viverra dolor risus.', ' Etiam neque tellus, sollicitudin at nisi a, mollis ornare enim.',
|
||||
'Quisque sed ante eu leo dictum sagittis quis nec nisi.',
|
||||
'Suspendisse id nulla dictum, sollicitudin urna id, iaculis elit.',
|
||||
'Nulla luctus pellentesque diam, ac consequat urna molestie vitae.',
|
||||
'Donec elementum turpis eget augue laoreet, nec maximus lacus malesuada.', '\n\nEtiam eu nunc mauris.',
|
||||
'Nullam aliquam tristique leo, at dignissim turpis sodales vitae.',
|
||||
'Aenean cursus laoreet neque, sit amet semper orci tincidunt et.',
|
||||
'Proin orci urna, efficitur malesuada mattis at, pretium commodo odio.',
|
||||
'Maecenas in ante id eros aliquam porttitor quis eget est.',
|
||||
'Duis ex urna, porta nec semper nec, dignissim eu urna.', ' Quisque eleifend non magna at rutrum.',
|
||||
'\nSed at eros blandit, tempor quam et, mollis ante.', ' Etiam fringilla euismod gravida.',
|
||||
'Curabitur facilisis consectetur luctus.',
|
||||
'Integer lectus augue, convallis a consequat id, sollicitudin eget lorem.',
|
||||
'Curabitur tincidunt suscipit nibh quis mollis.',
|
||||
'Fusce cursus, orci ut maximus fringilla, velit mauris dictum est, sed ultricies ante orci viverra erat.',
|
||||
'Quisque pellentesque, mauris nec vulputate commodo, risus libero volutpat nibh, vel tristique mi neque id quam.',
|
||||
'\nVivamus blandit, dolor ut interdum sagittis, arcu tortor finibus nibh, ornare convallis dui velit quis nunc.',
|
||||
'Sed turpis justo, pellentesque eu risus scelerisque, vestibulum vulputate magna.',
|
||||
'Vivamus tincidunt sollicitudin nisl, feugiat euismod nulla consequat ut.',
|
||||
'Praesent bibendum, sapien sit amet aliquet posuere, tellus purus porta lectus, ut volutpat purus tellus tempus est.',
|
||||
'Maecenas condimentum lobortis erat nec dignissim', ' Nulla molestie posuere est',
|
||||
'Proin ultrices, nisl id luctus lacinia, augue ipsum pharetra leo, quis commodo augue dui varius urna.',
|
||||
'Morbi ultrices turpis malesuada tellus fermentum vulputate.',
|
||||
'Aliquam viverra nulla aliquam viverra gravida.', ' Pellentesque eu viverra massa.',
|
||||
'Vestibulum id nisl vehicula, aliquet dui sed, venenatis eros.',
|
||||
'Nunc iaculis, neque vitae euismod viverra, nisl mauris luctus velit, a aliquam turpis erat fringilla libero.',
|
||||
'Ut ligula elit, lacinia convallis tempus interdum, finibus ut ex.',
|
||||
'Nulla efficitur ac ligula sit amet dignissim.', ' Donec sed mi et justo venenatis faucibus.',
|
||||
'Sed tincidunt nibh erat, in vestibulum purus consequat eget.',
|
||||
'\nNulla iaculis volutpat orci id efficitur.', ' Vivamus vehicula sit amet augue tristique dignissim.',
|
||||
'Praesent eget nulla est.', ' Integer nec diam fermentum, convallis metus lacinia, lobortis mauris.',
|
||||
'Nulla venenatis metus fringilla, lacinia sem nec, pharetra sapien.',
|
||||
'Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.',
|
||||
'Duis facilisis sapien est, a elementum lorem maximus ut.'
|
||||
]
|
||||
|
||||
tests = [
|
||||
{
|
||||
"position": 1,
|
||||
"name": "TextToFile",
|
||||
"config": {
|
||||
"size": [500, 100],
|
||||
"filepath": file_path,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"name": "TextToFile",
|
||||
"config": {
|
||||
"size": [500, 400],
|
||||
"filepath": file_path,
|
||||
"padding_x": 10,
|
||||
"padding_y": 10,
|
||||
"fontsize": 12,
|
||||
"language": "en"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class TestTextToDisplay(unittest.TestCase):
|
||||
|
||||
def test_get_config(self):
|
||||
print('getting data for web-ui...', end="")
|
||||
Module.get_config()
|
||||
print('OK')
|
||||
|
||||
def test_generate_image(self):
|
||||
delete_file_after_parse = False
|
||||
|
||||
if not file_path:
|
||||
delete_file_after_parse = True
|
||||
print("Filepath does not exist. Creating dummy file")
|
||||
|
||||
tmp_path = "tmp.txt"
|
||||
with open(tmp_path, mode="w", encoding="utf-8") as file:
|
||||
file.writelines(dummy_data)
|
||||
|
||||
# update tests with new temp path
|
||||
for test in tests:
|
||||
test["config"]["filepath"] = tmp_path
|
||||
|
||||
else:
|
||||
make_request = bool(file_path.startswith("https://"))
|
||||
if not make_request and not os.path.exists(file_path):
|
||||
raise FileNotFoundError("Your text file could not be found")
|
||||
|
||||
for test in tests:
|
||||
print(f'test {tests.index(test) + 1} generating image..')
|
||||
module = Module(test)
|
||||
im_black, im_colour = module.generate_image()
|
||||
print('OK')
|
||||
if Config.USE_PREVIEW:
|
||||
preview(merge(im_black, im_colour))
|
||||
im = merge(im_black, im_colour)
|
||||
im.show()
|
||||
|
||||
if delete_file_after_parse:
|
||||
print("cleaning up temp file")
|
||||
os.remove("tmp.txt")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
unittest.main()
|
Reference in New Issue
Block a user