From f6741eb7acfc2bc41510527d958f99af3ead041c Mon Sep 17 00:00:00 2001
From: Bernhard Sessner
Date: Tue, 19 Sep 2023 14:25:03 +0200
Subject: [PATCH 01/67] add devcontainer config to repo
---
.devcontainer/devcontainer.json | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 .devcontainer/devcontainer.json
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..094eb68
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,21 @@
+// For format details, see https://aka.ms/devcontainer.json.
+{
+ "name": "Python 3",
+ "image": "python:3.9-bullseye",
+
+ // This is the settings.json mount
+ "mounts": ["source=/c/temp/settings_test.json,target=/boot/settings.json,type=bind,consistency=cached"],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ "postCreateCommand": "pip3 install --user -r requirements.txt",
+
+ // We want to connect as root. More info: https://aka.ms/dev-containers-non-root.
+ "remoteUser": "root",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "ms-python.python"
+ ]
+ }
+ }
+}
From 7cfce8b6f85d003da41c3bba226f9ee350f01e20 Mon Sep 17 00:00:00 2001
From: Bernhard Sessner
Date: Tue, 19 Sep 2023 14:52:24 +0200
Subject: [PATCH 02/67] small devcontainer cleanup
---
.devcontainer/devcontainer.json | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 094eb68..1b65fe2 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,16 +1,14 @@
// For format details, see https://aka.ms/devcontainer.json.
{
- "name": "Python 3",
+ "name": "Inkycal-dev",
"image": "python:3.9-bullseye",
// This is the settings.json mount
"mounts": ["source=/c/temp/settings_test.json,target=/boot/settings.json,type=bind,consistency=cached"],
// Use 'postCreateCommand' to run commands after the container is created.
- "postCreateCommand": "pip3 install --user -r requirements.txt",
+ "postCreateCommand": "pip3 install --upgrade pip && pip3 install --user -r requirements.txt",
- // We want to connect as root. More info: https://aka.ms/dev-containers-non-root.
- "remoteUser": "root",
"customizations": {
"vscode": {
"extensions": [
From 5508274df75e15290eb50ef5c9406f5034575be5 Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 25 Sep 2023 13:36:39 +0200
Subject: [PATCH 03/67] fix issue on no title
---
inkycal/modules/ical_parser.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/inkycal/modules/ical_parser.py b/inkycal/modules/ical_parser.py
index 6c66876..9fd8530 100755
--- a/inkycal/modules/ical_parser.py
+++ b/inkycal/modules/ical_parser.py
@@ -119,7 +119,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')
From 50cc405df566282f52d5cf6cffbe3ee705a32868 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 26 Sep 2023 21:46:19 +0200
Subject: [PATCH 04/67] Update update-os.yml
---
.github/workflows/update-os.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/update-os.yml b/.github/workflows/update-os.yml
index 07ccd4b..083f42b 100644
--- a/.github/workflows/update-os.yml
+++ b/.github/workflows/update-os.yml
@@ -23,7 +23,7 @@ jobs:
with:
# Set the base_image to the desired Raspberry Pi OS version
base_image: https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2023-05-03/2023-05-03-raspios-bullseye-armhf-lite.img.xz
- image_additional_mb: 1500 # enlarge free space to 1.5 GB
+ image_additional_mb: 1750 # enlarge free space to 1.5 GB
optimize_image: true
user: inky
commands: |
From 42a0ae942daec383947b4971582992fc5bf68a7b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Oct 2023 23:52:41 +0000
Subject: [PATCH 05/67] Bump urllib3 from 2.0.3 to 2.0.6
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.3 to 2.0.6.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.3...2.0.6)
---
updated-dependencies:
- dependency-name: urllib3
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index be79521..e865ee9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,7 +24,7 @@ sgmllib3k==1.0.0
six==1.16.0
todoist-api-python==2.0.2
typing_extensions==4.6.3
-urllib3==2.0.3
+urllib3==2.0.6
yfinance==0.2.21
python-dotenv==1.0.0
setuptools==68.0.0
From 6da4a9e83312eeb33ffc7437fbe1765c7f77e1d2 Mon Sep 17 00:00:00 2001
From: Ace
Date: Thu, 12 Oct 2023 15:57:55 +0200
Subject: [PATCH 06/67] fix sponsor button link
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0aae4b8..72cbb2d 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,7 @@ These commands expand the filesystem, enable SPI and set up the correct timezone
## Installing Inkycal
⚠️ Please note that although the developers try to keep the installation as simple as possible, the full installation can sometimes take hours on the Raspberry Pi Zero W and is not guaranteed to go smoothly each time. This is because installing dependencies on the zero w takes a long time and is prone to copy-paste-, permission- and configuration errors.
-ℹ️ **Looking for a shortcut to safe a few hours?** We know about this problem and have spent a signifcant amount of time to prepare a pre-configured image with the latest version of Inkycal for the Raspberry Pi Zero. It comes with the latest version of Inkycal, is fully tested and uses the Raspberry Pi OS Lite as it's base image. You only need to copy your settings.json file, we already took care of the rest, including auto-start at boot, enabling spi and installing all dependencies in advance. Pretty neat right? Check the [sponsor button]() at the very top of the repo to get access to Inkycal-OS-Lite. This will help keep this project growing and cover the ongoing expenses too! Win-win for everyone! 🎊
+ℹ️ **Looking for a shortcut to safe a few hours?** We know about this problem and have spent a signifcant amount of time to prepare a pre-configured image with the latest version of Inkycal for the Raspberry Pi Zero. It comes with the latest version of Inkycal, is fully tested and uses the Raspberry Pi OS Lite as it's base image. You only need to copy your settings.json file, we already took care of the rest, including auto-start at boot, enabling spi and installing all dependencies in advance. Pretty neat right? Check the [sponsor button](https://github.com/sponsors/aceisace) at the very top of the repo to get access to Inkycal-OS-Lite. This will help keep this project growing and cover the ongoing expenses too! Win-win for everyone! 🎊
### Manual installation
From 2c3fcd630c07bff6e15d25d5a82f3069f82f1623 Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 16 Oct 2023 12:19:26 +0200
Subject: [PATCH 07/67] test on bookworm
---
.github/workflows/test-on-rpi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 730afc2..cb4bdf5 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -24,7 +24,7 @@ jobs:
TODOIST_API_KEY: ${{ secrets.TODOIST_API_KEY }}
with:
# Set the base_image to the desired Raspberry Pi OS version
- base_image: https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2023-05-03/2023-05-03-raspios-bullseye-armhf-lite.img.xz
+ base_image: https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2023-10-10/2023-10-10-raspios-bookworm-armhf-lite.img.xz
image_additional_mb: 1500 # enlarge free space to 1.5 GB
optimize_image: true
user: inky
From d740032989e814df931685cad789d5007508d044 Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 16 Oct 2023 13:33:11 +0200
Subject: [PATCH 08/67] Update test-on-rpi.yml
fix space issue
---
.github/workflows/test-on-rpi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index cb4bdf5..3006362 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -25,7 +25,7 @@ jobs:
with:
# Set the base_image to the desired Raspberry Pi OS version
base_image: https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2023-10-10/2023-10-10-raspios-bookworm-armhf-lite.img.xz
- image_additional_mb: 1500 # enlarge free space to 1.5 GB
+ image_additional_mb: 2000 # enlarge free space to 2 GB
optimize_image: true
user: inky
commands: |
From ea2a6051c418f8f553a115f258ad0b83d9ed1888 Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 16 Oct 2023 21:28:25 +0200
Subject: [PATCH 09/67] add note about debian bookworm
---
README.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/README.md b/README.md
index 72cbb2d..9662e11 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,10 @@
Inkycal is a software written in python for selected E-Paper displays. It converts these displays into useful information dashboards. It's open-source, free for personal use, fully modular and user-friendly. Despite all this, Inkycal can run well even on the Raspberry Pi Zero. Oh, and it's open for third-party modules! Hooray!
+## Important info about debian bookworm
+Starting october 2023, Raspberry Pi OS is now based on Debian bookworm. At this moment in time, there are several projects expierencing compatability issues because of this change, including Inkycal. Please either use the last [compatible version of Raspberry Pi OS](https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2023-05-03/2023-05-03-raspios-bullseye-armhf-lite.img.xz) or get a ready-to-flash tested OS image (aka. Inkycal OS Lite) with Inkycal pre-installed when sponsoring this project. Please hold on until a fix has been found, tested and is published.
+
+
## Main features
Inkycal is fully modular, you can mix and match any modules you like and configure them on the web-ui. For now, these following built-in modules are supported:
* Calendar - Monthly Calendar with option to sync events from iCalendars, e.g. Google.
From c6cbf2d74f4486635e8d5882e50c64aa99d8417e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 17 Oct 2023 21:09:53 +0000
Subject: [PATCH 10/67] Bump urllib3 from 2.0.6 to 2.0.7
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.6...2.0.7)
---
updated-dependencies:
- dependency-name: urllib3
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index e865ee9..5186aa2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,7 +24,7 @@ sgmllib3k==1.0.0
six==1.16.0
todoist-api-python==2.0.2
typing_extensions==4.6.3
-urllib3==2.0.6
+urllib3==2.0.7
yfinance==0.2.21
python-dotenv==1.0.0
setuptools==68.0.0
From 4f2dacc35a1ff3ba83e6535a10c3e88df5c23cd4 Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 23 Oct 2023 03:18:39 +0200
Subject: [PATCH 11/67] added quickstart video
---
README.md | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 9662e11..9502284 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
Inkycal is a software written in python for selected E-Paper displays. It converts these displays into useful information dashboards. It's open-source, free for personal use, fully modular and user-friendly. Despite all this, Inkycal can run well even on the Raspberry Pi Zero. Oh, and it's open for third-party modules! Hooray!
-## Important info about debian bookworm
+## ⚠️ Important info about debian bookworm
Starting october 2023, Raspberry Pi OS is now based on Debian bookworm. At this moment in time, there are several projects expierencing compatability issues because of this change, including Inkycal. Please either use the last [compatible version of Raspberry Pi OS](https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2023-05-03/2023-05-03-raspios-bullseye-armhf-lite.img.xz) or get a ready-to-flash tested OS image (aka. Inkycal OS Lite) with Inkycal pre-installed when sponsoring this project. Please hold on until a fix has been found, tested and is published.
@@ -34,8 +34,13 @@ Inkycal is fully modular, you can mix and match any modules you like and configu
* iCanHazDad - Display a random joke from [iCanHazDad.com](iCanhazdad.com).
+## Quickstart
+Watch the one-minute video on getting started with Inkycal:
+
+[](https://www.youtube.com/watch?v=IiIv_nWE5KI)
+
## Hardware guide
-Before you can start, please ensure you have one of the supported displays and of the supported Raspberry Pi: `|4|3A|3B|3B+|0W|0WH|`. We personally recommend the Raspberry Pi Zero W as this is relatively cheaper, uses less power and is perfect to fit in a small photo frame once you have assembled everything.
+Before you can start, please ensure you have one of the supported displays and of the supported Raspberry Pi: `|4|3A|3B|3B+|2B|0W|0WH|02W|`. We personally recommend the Raspberry Pi Zero W as this is relatively cheaper, uses less power and is perfect to fit in a small photo frame once you have assembled everything.
**Serial** displays are usually cheaper, but slower. Their main advantage is ease of use, like being able to communicate via SPI. A single update will cause flickering (fully normal on e-paper displays) ranging from a few seconds to half an minute. We recommend these for users who want to get started quickly and for more compact setups, e.g. fitting inside a photo frame. The resolution of these displays ranges from low to medium. Usually, these displays support 2-3 colours, but no colours in between, e.g. fully black, fully red/yellow and fully-white.
From 93c968da53dfd8537d004e7dd943493192750a77 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 7 Nov 2023 22:49:48 +0100
Subject: [PATCH 12/67] python 3.11 & code quality improvements
---
inkycal/custom/__init__.py | 1 +
inkycal/custom/functions.py | 43 +++--
inkycal/custom/openweathermap_wrapper.py | 43 +++++
inkycal/modules/inkycal_agenda.py | 13 +-
inkycal/modules/inkycal_calendar.py | 59 +++----
inkycal/modules/inkycal_feeds.py | 6 +-
inkycal/modules/inkycal_jokes.py | 7 +-
inkycal/modules/inkycal_stocks.py | 5 +-
.../modules/inkycal_textfile_to_display.py | 7 +-
inkycal/modules/inkycal_todoist.py | 5 +-
inkycal/modules/inkycal_weather.py | 155 +++++++++++-------
inkycal/tests/__init__.py | 2 +-
...cal_parser_test.py => test_ical_parser.py} | 0
..._agenda_test.py => test_inkycal_agenda.py} | 0
...endar_test.py => test_inkycal_calendar.py} | 0
...al_feeds_test.py => test_inkycal_feeds.py} | 2 -
...al_image_test.py => test_inkycal_image.py} | 0
...al_jokes_test.py => test_inkycal_jokes.py} | 0
...show_test.py => test_inkycal_slideshow.py} | 0
..._stocks_test.py => test_inkycal_stocks.py} | 0
.../tests/test_inkycal_textfile_to_display.py | 2 -
...odoist_test.py => test_inkycal_todoist.py} | 1 -
...eather_test.py => test_inkycal_weather.py} | 5 +-
requirements.txt | 38 ++---
24 files changed, 243 insertions(+), 151 deletions(-)
create mode 100644 inkycal/custom/openweathermap_wrapper.py
rename inkycal/tests/{ical_parser_test.py => test_ical_parser.py} (100%)
rename inkycal/tests/{inkycal_agenda_test.py => test_inkycal_agenda.py} (100%)
rename inkycal/tests/{inkycal_calendar_test.py => test_inkycal_calendar.py} (100%)
rename inkycal/tests/{inkycal_feeds_test.py => test_inkycal_feeds.py} (96%)
rename inkycal/tests/{inkycal_image_test.py => test_inkycal_image.py} (100%)
rename inkycal/tests/{inkycal_jokes_test.py => test_inkycal_jokes.py} (100%)
rename inkycal/tests/{inkycal_slideshow_test.py => test_inkycal_slideshow.py} (100%)
rename inkycal/tests/{inkycal_stocks_test.py => test_inkycal_stocks.py} (100%)
rename inkycal/tests/{inkycal_todoist_test.py => test_inkycal_todoist.py} (96%)
rename inkycal/tests/{inkycal_weather_test.py => test_inkycal_weather.py} (98%)
diff --git a/inkycal/custom/__init__.py b/inkycal/custom/__init__.py
index b447171..f9d1efb 100644
--- a/inkycal/custom/__init__.py
+++ b/inkycal/custom/__init__.py
@@ -1,2 +1,3 @@
from .functions import *
from .inkycal_exceptions import *
+from .openweathermap_wrapper import OpenWeatherMap
\ No newline at end of file
diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py
index 2a588f8..046e8fd 100644
--- a/inkycal/custom/functions.py
+++ b/inkycal/custom/functions.py
@@ -6,8 +6,10 @@ Inkycal custom-functions for ease-of-use
Copyright by aceinnolab
"""
import logging
+import traceback
+
from PIL import Image, ImageDraw, ImageFont, ImageColor
-from urllib.request import urlopen
+import requests
import os
import time
@@ -98,11 +100,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] - text_bbox[1]
+ fontsize = text_height
+ while text_height <= (max_height * 0.80):
fontsize += 1
font = ImageFont.truetype(font.path, fontsize)
+ text_height = text_bbox[3] - text_bbox[1]
return font
@@ -154,21 +158,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 +232,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:
@@ -249,9 +269,10 @@ def internet_available():
"""
try:
- urlopen('https://google.com', timeout=5)
+ requests.get('https://google.com', timeout=5)
return True
except:
+ print(f"Network could not be reached: {traceback.print_exc()}")
return False
diff --git a/inkycal/custom/openweathermap_wrapper.py b/inkycal/custom/openweathermap_wrapper.py
new file mode 100644
index 0000000..20c050f
--- /dev/null
+++ b/inkycal/custom/openweathermap_wrapper.py
@@ -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
+
diff --git a/inkycal/modules/inkycal_agenda.py b/inkycal/modules/inkycal_agenda.py
index f4ddbd6..b5adbd2 100755
--- a/inkycal/modules/inkycal_agenda.py
+++ b/inkycal/modules/inkycal_agenda.py
@@ -98,7 +98,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] - text_bbox_height[1] + line_spacing
line_width = im_width
max_lines = im_height // line_height
logger.debug(f'max lines: {max_lines}')
@@ -133,8 +135,8 @@ 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]
+ 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}')
@@ -147,8 +149,9 @@ 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]
+
+ 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}')
diff --git a/inkycal/modules/inkycal_calendar.py b/inkycal/modules/inkycal_calendar.py
index c6dfea5..65012a1 100755
--- a/inkycal/modules/inkycal_calendar.py
+++ b/inkycal/modules/inkycal_calendar.py
@@ -110,7 +110,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 +183,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 +208,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 +220,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 +265,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] - text_bbox_height[1] + line_spacing
+ max_event_lines = events_height // (line_height + line_spacing)
# generate list of coordinates for each line
events_offset = im_height - events_height
@@ -329,31 +332,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] - text_bbox_height[1] + line_spacing
event_width_s = im_width - date_width - time_width
event_width_l = im_width - date_width
@@ -411,12 +401,13 @@ 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,
)
diff --git a/inkycal/modules/inkycal_feeds.py b/inkycal/modules/inkycal_feeds.py
index 3e3b054..e354b89 100644
--- a/inkycal/modules/inkycal_feeds.py
+++ b/inkycal/modules/inkycal_feeds.py
@@ -91,9 +91,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] - text_bbox_height[1] + 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)
diff --git a/inkycal/modules/inkycal_jokes.py b/inkycal/modules/inkycal_jokes.py
index e7e1b39..deb4a41 100755
--- a/inkycal/modules/inkycal_jokes.py
+++ b/inkycal/modules/inkycal_jokes.py
@@ -54,10 +54,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] - text_bbox[1] + 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}")
diff --git a/inkycal/modules/inkycal_stocks.py b/inkycal/modules/inkycal_stocks.py
index 39002b2..e91e9f0 100755
--- a/inkycal/modules/inkycal_stocks.py
+++ b/inkycal/modules/inkycal_stocks.py
@@ -96,9 +96,10 @@ class Stocks(inkycal_module):
# Set some parameters for formatting feeds
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] - text_bbox_height[1] + line_spacing
line_width = im_width
- max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
+ max_lines = (im_height // line_height)
logger.debug(f"max_lines: {max_lines}")
diff --git a/inkycal/modules/inkycal_textfile_to_display.py b/inkycal/modules/inkycal_textfile_to_display.py
index cf3eb4d..c31baf5 100644
--- a/inkycal/modules/inkycal_textfile_to_display.py
+++ b/inkycal/modules/inkycal_textfile_to_display.py
@@ -73,10 +73,11 @@ class TextToDisplay(inkycal_module):
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] - text_bbox_height[1] + 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)
diff --git a/inkycal/modules/inkycal_todoist.py b/inkycal/modules/inkycal_todoist.py
index 1995d9b..f290253 100644
--- a/inkycal/modules/inkycal_todoist.py
+++ b/inkycal/modules/inkycal_todoist.py
@@ -86,9 +86,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] - text_bbox_height[1] + 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)
diff --git a/inkycal/modules/inkycal_weather.py b/inkycal/modules/inkycal_weather.py
index ec639bf..cbb2cc8 100644
--- a/inkycal/modules/inkycal_weather.py
+++ b/inkycal/modules/inkycal_weather.py
@@ -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))
diff --git a/inkycal/tests/__init__.py b/inkycal/tests/__init__.py
index 34e05a5..cca5d9b 100644
--- a/inkycal/tests/__init__.py
+++ b/inkycal/tests/__init__.py
@@ -1 +1 @@
-from config import Config
+from .config import Config
diff --git a/inkycal/tests/ical_parser_test.py b/inkycal/tests/test_ical_parser.py
similarity index 100%
rename from inkycal/tests/ical_parser_test.py
rename to inkycal/tests/test_ical_parser.py
diff --git a/inkycal/tests/inkycal_agenda_test.py b/inkycal/tests/test_inkycal_agenda.py
similarity index 100%
rename from inkycal/tests/inkycal_agenda_test.py
rename to inkycal/tests/test_inkycal_agenda.py
diff --git a/inkycal/tests/inkycal_calendar_test.py b/inkycal/tests/test_inkycal_calendar.py
similarity index 100%
rename from inkycal/tests/inkycal_calendar_test.py
rename to inkycal/tests/test_inkycal_calendar.py
diff --git a/inkycal/tests/inkycal_feeds_test.py b/inkycal/tests/test_inkycal_feeds.py
similarity index 96%
rename from inkycal/tests/inkycal_feeds_test.py
rename to inkycal/tests/test_inkycal_feeds.py
index dfdab9b..30f9613 100755
--- a/inkycal/tests/inkycal_feeds_test.py
+++ b/inkycal/tests/test_inkycal_feeds.py
@@ -57,8 +57,6 @@ class module_test(unittest.TestCase):
print('OK')
if Config.USE_PREVIEW:
preview(merge(im_black, im_colour))
- im = merge(im_black, im_colour)
- im.show()
if __name__ == '__main__':
diff --git a/inkycal/tests/inkycal_image_test.py b/inkycal/tests/test_inkycal_image.py
similarity index 100%
rename from inkycal/tests/inkycal_image_test.py
rename to inkycal/tests/test_inkycal_image.py
diff --git a/inkycal/tests/inkycal_jokes_test.py b/inkycal/tests/test_inkycal_jokes.py
similarity index 100%
rename from inkycal/tests/inkycal_jokes_test.py
rename to inkycal/tests/test_inkycal_jokes.py
diff --git a/inkycal/tests/inkycal_slideshow_test.py b/inkycal/tests/test_inkycal_slideshow.py
similarity index 100%
rename from inkycal/tests/inkycal_slideshow_test.py
rename to inkycal/tests/test_inkycal_slideshow.py
diff --git a/inkycal/tests/inkycal_stocks_test.py b/inkycal/tests/test_inkycal_stocks.py
similarity index 100%
rename from inkycal/tests/inkycal_stocks_test.py
rename to inkycal/tests/test_inkycal_stocks.py
diff --git a/inkycal/tests/test_inkycal_textfile_to_display.py b/inkycal/tests/test_inkycal_textfile_to_display.py
index 11c548a..92afb74 100644
--- a/inkycal/tests/test_inkycal_textfile_to_display.py
+++ b/inkycal/tests/test_inkycal_textfile_to_display.py
@@ -112,8 +112,6 @@ class TestTextToDisplay(unittest.TestCase):
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")
diff --git a/inkycal/tests/inkycal_todoist_test.py b/inkycal/tests/test_inkycal_todoist.py
similarity index 96%
rename from inkycal/tests/inkycal_todoist_test.py
rename to inkycal/tests/test_inkycal_todoist.py
index 63e579f..bf87caf 100644
--- a/inkycal/tests/inkycal_todoist_test.py
+++ b/inkycal/tests/test_inkycal_todoist.py
@@ -46,7 +46,6 @@ class module_test(unittest.TestCase):
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')
diff --git a/inkycal/tests/inkycal_weather_test.py b/inkycal/tests/test_inkycal_weather.py
similarity index 98%
rename from inkycal/tests/inkycal_weather_test.py
rename to inkycal/tests/test_inkycal_weather.py
index 8682bec..b6bde42 100755
--- a/inkycal/tests/inkycal_weather_test.py
+++ b/inkycal/tests/test_inkycal_weather.py
@@ -13,7 +13,7 @@ preview = Inkyimage.preview
merge = Inkyimage.merge
owm_api_key = Config.OPENWEATHERMAP_API_KEY
-location = 'Stuttgart, DE'
+location = '2825297'
tests = [
{
@@ -184,7 +184,8 @@ class module_test(unittest.TestCase):
im_black, im_colour = module.generate_image()
print('OK')
if Config.USE_PREVIEW:
- preview(merge(im_black, im_colour))
+ merged = merge(im_black, im_colour)
+ preview(merged)
diff --git a/requirements.txt b/requirements.txt
index 5186aa2..6eb2a21 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,30 +1,28 @@
-arrow==1.2.3
+arrow==1.3.0
certifi==2023.7.22
-cycler==0.11.0
+cycler==0.12.1
feedparser==6.0.10
-fonttools==4.40.0
-geojson==2.3.0
-icalendar==5.0.7
-kiwisolver==1.4.4
-lxml==4.9.2
-matplotlib==3.7.1
+fonttools==4.44.0
+icalendar==5.0.11
+kiwisolver==1.4.5
+lxml==4.9.3
+matplotlib==3.8.1
multitasking==0.0.11
-numpy==1.25.0
-packaging==23.1
-pandas==2.0.2
-Pillow==9.5.0
-pyowm==3.3.0
-pyparsing==3.1.0
+numpy==1.26.1
+packaging==23.2
+pandas==2.1.2
+Pillow==10.1.0
+pyparsing==3.1.1
PySocks==1.7.1
python-dateutil==2.8.2
-pytz==2023.3
-recurring-ical-events==2.0.2
+pytz==2023.3.post1
+recurring-ical-events==2.1.0
requests==2.31.0
sgmllib3k==1.0.0
six==1.16.0
-todoist-api-python==2.0.2
-typing_extensions==4.6.3
+todoist-api-python==2.1.3
+typing_extensions==4.8.0
urllib3==2.0.7
-yfinance==0.2.21
+yfinance==0.2.31
python-dotenv==1.0.0
-setuptools==68.0.0
+setuptools==68.2.2
From 2799b66f1d81e1c6b22942753aa1078441659433 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 7 Nov 2023 23:11:34 +0100
Subject: [PATCH 13/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 3006362..721d83b 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -3,7 +3,7 @@ name: Run tests on Raspberry Pi OS
on:
push:
branches:
- - main
+ - feature/#275
jobs:
test-on-rpi-os:
From a921a9c91792b8d77dcb4bf5e473640f3daa56df Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 7 Nov 2023 23:13:30 +0100
Subject: [PATCH 14/67] code quality improvements
---
inkycal/modules/inkycal_textfile_to_display.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/inkycal/modules/inkycal_textfile_to_display.py b/inkycal/modules/inkycal_textfile_to_display.py
index c31baf5..3d4617e 100644
--- a/inkycal/modules/inkycal_textfile_to_display.py
+++ b/inkycal/modules/inkycal_textfile_to_display.py
@@ -7,11 +7,11 @@ 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__)
@@ -44,7 +44,6 @@ class TextToDisplay(inkycal_module):
self.make_request = True if self.filepath.startswith("https://") else False
-
# give an OK message
print(f'{__name__} loaded')
From 0ea7b300c89b81ed98565e047f7555f1b101d9a4 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 7 Nov 2023 23:15:49 +0100
Subject: [PATCH 15/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 721d83b..4652ed2 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -3,7 +3,10 @@ name: Run tests on Raspberry Pi OS
on:
push:
branches:
- - feature/#275
+ - main
+ pull_request:
+ branches:
+ - main
jobs:
test-on-rpi-os:
From 8af21e49e361bfa2ef700453fa9c83d73e15d015 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 00:26:57 +0100
Subject: [PATCH 16/67] speed up installation time
---
requirements.txt | 3 ---
1 file changed, 3 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 6eb2a21..1d9ca8d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,10 +7,8 @@ icalendar==5.0.11
kiwisolver==1.4.5
lxml==4.9.3
matplotlib==3.8.1
-multitasking==0.0.11
numpy==1.26.1
packaging==23.2
-pandas==2.1.2
Pillow==10.1.0
pyparsing==3.1.1
PySocks==1.7.1
@@ -23,6 +21,5 @@ six==1.16.0
todoist-api-python==2.1.3
typing_extensions==4.8.0
urllib3==2.0.7
-yfinance==0.2.31
python-dotenv==1.0.0
setuptools==68.2.2
From e930579eab7cd5b47024b840864c9277b2e7a05e Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 01:00:48 +0100
Subject: [PATCH 17/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 4652ed2..5a4a82e 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -52,6 +52,6 @@ jobs:
pip install wheel
pip install -e ./
pip install RPi.GPIO==0.7.1 spidev==3.5
- cd inkycal/tests
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
- for f in *.py; do python3 "$f"; done
+ pip install pytest
+ python -m pytest
From f5d93c77ea5cae4442ac59c4afcb7fd7b31fbcd9 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 01:14:30 +0100
Subject: [PATCH 18/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 5a4a82e..37f1d25 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -42,7 +42,7 @@ jobs:
sudo apt-get update -y
python --version
sudo apt-get install -y python3-pip
- sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
+ sudo apt-get install python3-numpy zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
echo $PWD && ls
git clone --branch main --single-branch https://github.com/aceinnolab/Inkycal
cd Inkycal
@@ -51,7 +51,9 @@ jobs:
python -m pip install --upgrade pip
pip install wheel
pip install -e ./
+ pip uninstall numpy
pip install RPi.GPIO==0.7.1 spidev==3.5
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
+ cd tests
pip install pytest
python -m pytest
From 09ff7261c45aa4b8b585147ef1651b995e83d38d Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 01:23:18 +0100
Subject: [PATCH 19/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 37f1d25..f426833 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -51,7 +51,7 @@ jobs:
python -m pip install --upgrade pip
pip install wheel
pip install -e ./
- pip uninstall numpy
+ pip uninstall numpy -y
pip install RPi.GPIO==0.7.1 spidev==3.5
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
cd tests
From d3e3d03f40a8bad7c40bcf3813c254683455f6c8 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 01:32:26 +0100
Subject: [PATCH 20/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index f426833..1c0649a 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -54,6 +54,6 @@ jobs:
pip uninstall numpy -y
pip install RPi.GPIO==0.7.1 spidev==3.5
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
- cd tests
+ cd inkycal/tests
pip install pytest
python -m pytest
From 924ac95553438272905fd7e90ede9439c1b620b0 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 01:45:49 +0100
Subject: [PATCH 21/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 1c0649a..467ec57 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -42,7 +42,7 @@ jobs:
sudo apt-get update -y
python --version
sudo apt-get install -y python3-pip
- sudo apt-get install python3-numpy zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
+ sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
echo $PWD && ls
git clone --branch main --single-branch https://github.com/aceinnolab/Inkycal
cd Inkycal
@@ -51,7 +51,6 @@ jobs:
python -m pip install --upgrade pip
pip install wheel
pip install -e ./
- pip uninstall numpy -y
pip install RPi.GPIO==0.7.1 spidev==3.5
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
cd inkycal/tests
From f0f299cbd72b6482ff087d0b89c08f5f49acefc1 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 01:56:33 +0100
Subject: [PATCH 22/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 467ec57..a63f17d 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -42,7 +42,7 @@ jobs:
sudo apt-get update -y
python --version
sudo apt-get install -y python3-pip
- sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
+ sudo apt-get install libopenblas-base zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
echo $PWD && ls
git clone --branch main --single-branch https://github.com/aceinnolab/Inkycal
cd Inkycal
From 831086c8595c8919bba8759a2dad5adb113f9adf Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 02:05:32 +0100
Subject: [PATCH 23/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index a63f17d..5c89576 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -42,7 +42,7 @@ jobs:
sudo apt-get update -y
python --version
sudo apt-get install -y python3-pip
- sudo apt-get install libopenblas-base zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
+ sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
echo $PWD && ls
git clone --branch main --single-branch https://github.com/aceinnolab/Inkycal
cd Inkycal
@@ -52,6 +52,7 @@ jobs:
pip install wheel
pip install -e ./
pip install RPi.GPIO==0.7.1 spidev==3.5
+ pip install numpy==1.24.4 -y
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
cd inkycal/tests
pip install pytest
From c96e6614447141ee4972cca1c04f75e05d1776bd Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 02:18:19 +0100
Subject: [PATCH 24/67] Update test-on-rpi.yml
---
.github/workflows/test-on-rpi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 5c89576..c84d6bc 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -52,7 +52,7 @@ jobs:
pip install wheel
pip install -e ./
pip install RPi.GPIO==0.7.1 spidev==3.5
- pip install numpy==1.24.4 -y
+ pip install numpy==1.24.4
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
cd inkycal/tests
pip install pytest
From 701bfd8f9063281beb136139f139220fbb62768c Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 10:29:19 +0100
Subject: [PATCH 25/67] Update requirements.txt
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 1d9ca8d..81cc8bb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,7 +7,7 @@ icalendar==5.0.11
kiwisolver==1.4.5
lxml==4.9.3
matplotlib==3.8.1
-numpy==1.26.1
+numpy==1.24.4
packaging==23.2
Pillow==10.1.0
pyparsing==3.1.1
From 97296fc67622ebcd650fe49345560870323d6be8 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 10:30:03 +0100
Subject: [PATCH 26/67] cleanup
---
.github/workflows/test-on-rpi.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index c84d6bc..467ec57 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -52,7 +52,6 @@ jobs:
pip install wheel
pip install -e ./
pip install RPi.GPIO==0.7.1 spidev==3.5
- pip install numpy==1.24.4
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
cd inkycal/tests
pip install pytest
From 05baa1129546a63938f88700258285859c2e2dda Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 10:41:58 +0100
Subject: [PATCH 27/67] update instructions
The latest version of Raspberry Pi OS is now officially supported, however, the installation of numpy takes a very long time. A note about the long installation has been added
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 9502284..da3042b 100644
--- a/README.md
+++ b/README.md
@@ -17,8 +17,8 @@
Inkycal is a software written in python for selected E-Paper displays. It converts these displays into useful information dashboards. It's open-source, free for personal use, fully modular and user-friendly. Despite all this, Inkycal can run well even on the Raspberry Pi Zero. Oh, and it's open for third-party modules! Hooray!
-## ⚠️ Important info about debian bookworm
-Starting october 2023, Raspberry Pi OS is now based on Debian bookworm. At this moment in time, there are several projects expierencing compatability issues because of this change, including Inkycal. Please either use the last [compatible version of Raspberry Pi OS](https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2023-05-03/2023-05-03-raspios-bullseye-armhf-lite.img.xz) or get a ready-to-flash tested OS image (aka. Inkycal OS Lite) with Inkycal pre-installed when sponsoring this project. Please hold on until a fix has been found, tested and is published.
+## ⚠️ Warning: long installation time expected!
+Starting october 2023, Raspberry Pi OS is now based on Debian bookworm and uses python 3.11 instead of 3.9 as the default version. Inkycal has been updated to work with python3.11, but the installation of numpy can take a very long time, in some cases even hours. If you do not want to wait this long to install Inkycal, you can also get a ready-to-flash version of Inkycal called InkycalOS-Lite with everything pre-installed for you by sponsoring via [Github Sponsors](https://github.com/sponsors/aceisace). This helps keep up maintenance costs, implement new features and fixing bugs.
## Main features
From 30e2504f522ce21a8601be1e8c5f88714f33477c Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 10:47:20 +0100
Subject: [PATCH 28/67] python3.9 -> python 3.11
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index da3042b..64acce1 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
+
Inkycal is a software written in python for selected E-Paper displays. It converts these displays into useful information dashboards. It's open-source, free for personal use, fully modular and user-friendly. Despite all this, Inkycal can run well even on the Raspberry Pi Zero. Oh, and it's open for third-party modules! Hooray!
From b90d01a2ba5a611612536a05ddd1d554dad3e2c8 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 10:47:53 +0100
Subject: [PATCH 29/67] Update setup.py
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 02b8b97..9238cf5 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ with open('requirements.txt') as f:
required = [i.split(' ')[0] for i in required]
__project__ = "inkycal"
-__version__ = "2.0.0"
+__version__ = "2.0.2"
__description__ = "Inkycal is a python3 software for syncing icalendar events, weather and news on selected E-Paper displays"
__packages__ = ["inkycal"]
__author__ = "aceisace"
From e2616d3795bf5c818e21ee11a6abf8fcfe334d32 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 11:11:40 +0100
Subject: [PATCH 30/67] v2.0.3
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 9238cf5..b5b45bf 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ with open('requirements.txt') as f:
required = [i.split(' ')[0] for i in required]
__project__ = "inkycal"
-__version__ = "2.0.2"
+__version__ = "2.0.3"
__description__ = "Inkycal is a python3 software for syncing icalendar events, weather and news on selected E-Paper displays"
__packages__ = ["inkycal"]
__author__ = "aceisace"
From c02a300cf5b1f1a74d4c79c191fad426a730e668 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 11:12:54 +0100
Subject: [PATCH 31/67] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 64acce1..264dc4a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Welcome to inkycal v2.0.2!
+# Welcome to inkycal v2.0.3!
From 6e3534817172e0af12810db8e0d61e705ee462e7 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 18:01:55 +0100
Subject: [PATCH 32/67] added note about one-time sponsor
To avoid confusions
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 264dc4a..23895a9 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
Inkycal is a software written in python for selected E-Paper displays. It converts these displays into useful information dashboards. It's open-source, free for personal use, fully modular and user-friendly. Despite all this, Inkycal can run well even on the Raspberry Pi Zero. Oh, and it's open for third-party modules! Hooray!
## ⚠️ Warning: long installation time expected!
-Starting october 2023, Raspberry Pi OS is now based on Debian bookworm and uses python 3.11 instead of 3.9 as the default version. Inkycal has been updated to work with python3.11, but the installation of numpy can take a very long time, in some cases even hours. If you do not want to wait this long to install Inkycal, you can also get a ready-to-flash version of Inkycal called InkycalOS-Lite with everything pre-installed for you by sponsoring via [Github Sponsors](https://github.com/sponsors/aceisace). This helps keep up maintenance costs, implement new features and fixing bugs.
+Starting october 2023, Raspberry Pi OS is now based on Debian bookworm and uses python 3.11 instead of 3.9 as the default version. Inkycal has been updated to work with python3.11, but the installation of numpy can take a very long time, in some cases even hours. If you do not want to wait this long to install Inkycal, you can also get a ready-to-flash version of Inkycal called InkycalOS-Lite with everything pre-installed for you by sponsoring via [Github Sponsors](https://github.com/sponsors/aceisace). This helps keep up maintenance costs, implement new features and fixing bugs. Please choose the one-time sponsor option and select the one with the plug-and-play version of Inkycal. Then, send your email-address to which InkycalOS-Lite should be sent.
## Main features
From 3124ecd535f7f26c6ccd5174233f54112131c125 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 18:06:11 +0100
Subject: [PATCH 33/67] Update update-os.yml
---
.github/workflows/update-os.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/update-os.yml b/.github/workflows/update-os.yml
index 083f42b..3e87435 100644
--- a/.github/workflows/update-os.yml
+++ b/.github/workflows/update-os.yml
@@ -23,7 +23,7 @@ jobs:
with:
# Set the base_image to the desired Raspberry Pi OS version
base_image: https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2023-05-03/2023-05-03-raspios-bullseye-armhf-lite.img.xz
- image_additional_mb: 1750 # enlarge free space to 1.5 GB
+ image_additional_mb: 2000 # enlarge free space to 2 GB
optimize_image: true
user: inky
commands: |
@@ -49,7 +49,8 @@ jobs:
pip install RPi.GPIO==0.7.1 spidev==3.5
cd inkycal/tests
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
- for f in *.py; do python3 "$f"; done
+ pip install pytest
+ python -m pytest
# install deps for 12.48" display
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.71.tar.gz
From bebe60aef3f58868c801f7b42fe9b94fae9ee0fd Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 20:22:50 +0100
Subject: [PATCH 34/67] text-module enhancement This allows an improved
approach to load text from URL Also improved the function to check for a
connection by attempting three times before giving up
---
inkycal/custom/functions.py | 23 +++++-----
.../modules/inkycal_textfile_to_display.py | 11 +++--
inkycal/tests/config.py | 2 +
.../tests/test_inkycal_textfile_to_display.py | 43 ++++++++-----------
requirements.txt | 1 +
5 files changed, 37 insertions(+), 43 deletions(-)
diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py
index 046e8fd..a092007 100644
--- a/inkycal/custom/functions.py
+++ b/inkycal/custom/functions.py
@@ -6,12 +6,12 @@ Inkycal custom-functions for ease-of-use
Copyright by aceinnolab
"""
import logging
-import traceback
-
-from PIL import Image, ImageDraw, ImageFont, ImageColor
-import requests
import os
import time
+import traceback
+
+import requests
+from PIL import Image, ImageDraw, ImageFont
logs = logging.getLogger(__name__)
logs.setLevel(level=logging.INFO)
@@ -267,13 +267,14 @@ def internet_available():
>>> if internet_available():
>>> #...do something that requires internet connectivity
"""
-
- try:
- requests.get('https://google.com', timeout=5)
- return True
- except:
- print(f"Network could not be reached: {traceback.print_exc()}")
- 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)):
diff --git a/inkycal/modules/inkycal_textfile_to_display.py b/inkycal/modules/inkycal_textfile_to_display.py
index 3d4617e..cd522d9 100644
--- a/inkycal/modules/inkycal_textfile_to_display.py
+++ b/inkycal/modules/inkycal_textfile_to_display.py
@@ -65,12 +65,6 @@ 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 = 4
text_bbox_height = self.font.getbbox("hg")
@@ -87,6 +81,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
diff --git a/inkycal/tests/config.py b/inkycal/tests/config.py
index cddb6eb..3b846fc 100644
--- a/inkycal/tests/config.py
+++ b/inkycal/tests/config.py
@@ -28,6 +28,8 @@ class Config:
# inkycal_todoist_test
TODOIST_API_KEY = get("TODOIST_API_KEY")
+ TEMP_PATH = f"{basedir}/tmp"
+
diff --git a/inkycal/tests/test_inkycal_textfile_to_display.py b/inkycal/tests/test_inkycal_textfile_to_display.py
index 92afb74..a667af1 100644
--- a/inkycal/tests/test_inkycal_textfile_to_display.py
+++ b/inkycal/tests/test_inkycal_textfile_to_display.py
@@ -3,14 +3,16 @@ import logging
import os
import sys
import unittest
-from inkycal.modules import TextToDisplay as Module
+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
+
+temp_path = f"{Config.TEMP_PATH}/temp.txt"
dummy_data = [
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', ' Donec feugiat facilisis neque vel blandit.',
@@ -56,7 +58,7 @@ tests = [
"name": "TextToFile",
"config": {
"size": [500, 100],
- "filepath": file_path,
+ "filepath": temp_path,
"padding_x": 10,
"padding_y": 10,
"fontsize": 12,
@@ -68,7 +70,7 @@ tests = [
"name": "TextToFile",
"config": {
"size": [500, 400],
- "filepath": file_path,
+ "filepath": "https://de.wikipedia.org/wiki/Nationale_Rotkreuz-_und_Rothalbmond-Gesellschaft",
"padding_x": 10,
"padding_y": 10,
"fontsize": 12,
@@ -80,31 +82,19 @@ tests = [
class TestTextToDisplay(unittest.TestCase):
+ def setUp(self):
+ self.temp_path = temp_path
+ if not os.path.exists(self.temp_path):
+ print("could not find temporary file, creating now.")
+ with open(self.temp_path, encoding="utf-8", mode="w") as file:
+ file.writelines(dummy_data)
+
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)
@@ -113,9 +103,10 @@ class TestTextToDisplay(unittest.TestCase):
if Config.USE_PREVIEW:
preview(merge(im_black, im_colour))
- if delete_file_after_parse:
- print("cleaning up temp file")
- os.remove("tmp.txt")
+ def tearDown(self):
+ if os.path.exists(self.temp_path):
+ print("deleting temporary file.")
+ os.remove(self.temp_path)
if __name__ == '__main__':
diff --git a/requirements.txt b/requirements.txt
index 81cc8bb..2bdd099 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -23,3 +23,4 @@ typing_extensions==4.8.0
urllib3==2.0.7
python-dotenv==1.0.0
setuptools==68.2.2
+html2text==2020.1.16
From bb9257ee30b73b4630849f5436a86a974cae31af Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 8 Nov 2023 22:25:46 +0100
Subject: [PATCH 35/67] consistency
---
inkycal/main.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/inkycal/main.py b/inkycal/main.py
index 122a817..08ed6c5 100644
--- a/inkycal/main.py
+++ b/inkycal/main.py
@@ -74,7 +74,7 @@ class Inkycal:
def __init__(self, settings_path=None, render=True):
"""Initialise Inkycal"""
- self._release = '2.0.2'
+ self._release = '2.0.3'
# Check if render was set correctly
if render not in [True, False]:
From d79c65cdd57322db9e73296a2acbf2721f69b5ed Mon Sep 17 00:00:00 2001
From: mygrexit <33792951+mygrexit@users.noreply.github.com>
Date: Fri, 10 Nov 2023 13:39:08 +0100
Subject: [PATCH 36/67] Add localized formatting for multi-day event durations
This commit introduces a feature to format the names of multi-day events using Arrow's localization. Event titles now include the event duration in days.
---
inkycal/modules/inkycal_calendar.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/inkycal/modules/inkycal_calendar.py b/inkycal/modules/inkycal_calendar.py
index 65012a1..68ccb26 100755
--- a/inkycal/modules/inkycal_calendar.py
+++ b/inkycal/modules/inkycal_calendar.py
@@ -355,7 +355,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}")
From 084a44a5e5ab9cf2d02a0bbbcf4b4875293aa07a Mon Sep 17 00:00:00 2001
From: Ace
Date: Fri, 10 Nov 2023 14:49:42 +0100
Subject: [PATCH 37/67] Create pull_request_template.md
---
.../pull_request_template.md | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md
diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
new file mode 100644
index 0000000..45a3586
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
@@ -0,0 +1,29 @@
+# Pull Request
+
+## Description
+_Briefly describe the purpose of this pull request_
+
+## Changes Made
+_Describe the changes you made in this PR_
+
+## Related Issues
+_Reference any related issues here. Use the format "Fixes #" if this PR fixes an issue._
+
+## How to Test
+_Provide step-by-step instructions or commands on how to test your changes_
+
+## Screenshots (if applicable)
+_Include screenshots or GIFs that demonstrate the changes (if applicable)_
+
+## Checklist
+_Place an 'x' in the checkboxes that apply.
+If you're unsure about any of them, don't hesitate to ask._
+
+- [ ] I have read the [contribution guidelines](https://github.com/aceinnolab/Inkycal/blob/main/.github/CONTRIBUTING.md)
+- [ ] My code follows the project's coding standards
+- [ ] I have tested my changes
+- [ ] I have updated the documentation
+- [ ] My changes do not introduce new warnings or errors
+
+## Additional Notes
+_Any additional information or context you want to provide_
From 2dc945ebee3b92208b4ac79aa5968f6462235a23 Mon Sep 17 00:00:00 2001
From: mygrexit <33792951+mygrexit@users.noreply.github.com>
Date: Fri, 10 Nov 2023 14:57:47 +0100
Subject: [PATCH 38/67] Added support for multi-day events
Improved event handling in calendar: Enhanced the calendar module to handle events spanning multiple days.
---
inkycal/modules/inkycal_calendar.py | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/inkycal/modules/inkycal_calendar.py b/inkycal/modules/inkycal_calendar.py
index 68ccb26..a6d3154 100755
--- a/inkycal/modules/inkycal_calendar.py
+++ b/inkycal/modules/inkycal_calendar.py
@@ -296,14 +296,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
From 41ea2632571ba70f6f7ccebf030b2b4266b23c2c Mon Sep 17 00:00:00 2001
From: Ace
Date: Fri, 10 Nov 2023 15:06:15 +0100
Subject: [PATCH 39/67] Update CONTRIBUTING.md
---
.github/CONTRIBUTING.md | 60 ++++++++++++++++++++++++++---------------
1 file changed, 39 insertions(+), 21 deletions(-)
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index f9b084b..c4b9122 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -1,31 +1,49 @@
-# Inkycal Contribution Policy
+# Contributing to Inkycal
-Thanks for willing to contribute to Inkycal
-We welcome all sort of contributions, for example:
-* giving support via the Discord server
-* submitting hotfixes for existing bugs
-* giving ideas for new features
-* financial contributions (while Inkycal is still dependent on them. These go towards new hardware, displays and a bit of coffee)
+Welcome to Inkycal! We are excited that you are considering contributing to our project. Before you get started, please take a moment to read through our contribution guidelines.
-# Third party modules
-So you had a great idea for an inkycal-module? Awesome! In fact, there is already a repo sepcfifically created for that purpose: [inkycal-modules-template](https://github.com/aceisace/inkycal-modules-template). Just fork that repo, add your module and give me a shout via Discord, Github or Email. If it is really unique and convincing, chances are, if you agree, that it will be available as default module in a future release. Please do not attempt to have it merged straight into main. We try not to touch main except for new releases to keep things consistent, stable and easy-to-maintain.
+## Code of Conduct
-# Code contributions (PRs, hotfixes, Critical improvements)
-So you found a bug in Inkycal and tested out a bugfix? Kudos! Please fork the Inkycal repo, add your changes in there and create a PR targeting main. For all other PRs, please target a different branch.
+This project and everyone participating in it are governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report any unacceptable behavior.
-Don't forget to add your name in the file `CONTRIBUTORS.md` of the corresponding branch. Thank You!
+## How Can I Contribute?
-# Submitting Issues
+### Reporting Bugs
-Please only submit reproducible issues with clear instructions on how to reproduce them.
+Before submitting a bug report, check if the issue is already reported in the [Issues](https://github.com/aceinnolab/Inkycal/issues) section. If not, please open a new issue with a detailed description of the problem, including steps to reproduce it.
-When you are submitting a new issue, please supply the following information:
+### Suggesting Enhancements
-### Release version
-* are you using main or a different branch. In most cases, this is main
+We welcome suggestions for new features or enhancements. Use the [Issues](https://github.com/aceinnolab/Inkycal/issues) section to submit your ideas, and provide as much detail as possible.
-### Expected behavior and actual behavior
-* what were you expecting to happen and what did really happen?
+### Third party modules
+So you had a great idea for an inkycal-module? Awesome! In fact, there is already a repo sepcfifically created for that purpose: [inkycal-modules-template](https://github.com/aceisace/inkycal-modules-template). Just fork that repo, add your module and give me a shout via Discord, Github or Email.
+
+
+### Pull Requests
+
+1. Fork the repository and create a new branch for your feature or bug fix.
+2. Make your changes and test thoroughly.
+3. Ensure your code follows our coding standards.
+4. Update the documentation if necessary.
+5. Add your name in the file `CONTRIBUTORS.md`.
+6. Open a pull request, referencing any related issues.
+
+## Code Standards
+
+Follow our coding standards to maintain consistency across the project. Check the existing codebase to understand the style and conventions.
+
+## Testing
+
+Ensure that your changes are thoroughly tested. If applicable, provide test cases to cover your code.
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the [LICENSE](https://github.com/aceinnolab/Inkycal/blob/main/LICENSE) file of this project.
+
+## Thank You
+
+Thank you for considering contributing to Inkycal! Your help is invaluable, and we appreciate your time and effort.
+
+Happy coding!
-### Steps to reproduce the behavior
-* How can the devs re-create the same problem you were having?
From 18257066a6ac7b52b0b2431d8ee4d239b57c6ae5 Mon Sep 17 00:00:00 2001
From: Ace
Date: Fri, 10 Nov 2023 15:08:13 +0100
Subject: [PATCH 40/67] Update CODE_OF_CONDUCT.md
---
.github/CODE_OF_CONDUCT.md | 35 +++++++++++++++++------------------
1 file changed, 17 insertions(+), 18 deletions(-)
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index f1d8b91..5360538 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -1,26 +1,26 @@
-# Contributor Covenant Code of Conduct
+# Code of Conduct
## Our Pledge
-In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
-Examples of unacceptable behavior by participants include:
+Examples of unacceptable behavior include:
-* The use of sexualized language or imagery and unwelcome sexual attention or advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a professional setting
+- The use of sexualized language or imagery and unwelcome sexual attention or advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic address, without explicit permission
+- Other conduct that could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
@@ -30,17 +30,16 @@ Project maintainers have the right and responsibility to remove, edit, or reject
## Scope
-This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at aceisace63@yahoo.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [this email](inkycal@aceinnolab.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
-[homepage]: http://contributor-covenant.org
-[version]: http://contributor-covenant.org/version/1/4/
+For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq.
From 126b3826e6b2892d6ae638ce5ef59191baa7b5a8 Mon Sep 17 00:00:00 2001
From: mygrexit <33792951+mygrexit@users.noreply.github.com>
Date: Fri, 10 Nov 2023 21:53:36 +0100
Subject: [PATCH 41/67] Add dotted and dashed lines (draw_boarder function)
First version of adding dotted and dashed line in order to be able to have more distinguishon between multi event, single event and mixed event days. This version is using the "arcs" from PIL ImageDraw which isn't pretty.
---
inkycal/custom/functions.py | 94 ++++++++++++++++++++++++-------------
1 file changed, 61 insertions(+), 33 deletions(-)
diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py
index a092007..ff3f16e 100644
--- a/inkycal/custom/functions.py
+++ b/inkycal/custom/functions.py
@@ -277,60 +277,88 @@ def internet_available():
return False
-def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)):
- """Draws a border at given coordinates.
+from PIL import Image, ImageDraw
+
+def draw_dotted_line(draw, start, end, colour, thickness):
+ """Draws a dotted line between start and end points using dots."""
+ delta_x = end[0] - start[0]
+ delta_y = end[1] - start[1]
+ distance = ((delta_x ** 2 + delta_y ** 2) ** 0.5)
+ dot_spacing = 6 # Distance between dots
+
+ for i in range(0, int(distance / dot_spacing), 1):
+ dot_position = (start[0] + (i * dot_spacing * delta_x / distance),
+ start[1] + (i * dot_spacing * delta_y / distance))
+ # Drawing a circle at each dot position to create a dotted effect
+ draw.ellipse([(dot_position[0] - thickness, dot_position[1] - thickness),
+ (dot_position[0] + thickness, dot_position[1] + thickness)],
+ fill=colour)
+
+def draw_dashed_line(draw, start, end, colour, thickness):
+ """Draws a dashed line between start and end points."""
+ delta_x = end[0] - start[0]
+ delta_y = end[1] - start[1]
+ distance = ((delta_x ** 2 + delta_y ** 2) ** 0.5)
+ step_size = 10
+ gap_size = 5
+
+ for i in range(0, int(distance / (step_size + gap_size)), 1):
+ segment_start = (start[0] + (i * (step_size + gap_size) * delta_x / distance),
+ start[1] + (i * (step_size + gap_size) * delta_y / distance))
+ segment_end = (segment_start[0] + (step_size * delta_x / distance),
+ segment_start[1] + (step_size * delta_y / distance))
+ draw.line((segment_start, segment_end), fill=colour, width=thickness)
+
+def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1), style='solid'):
+ """
+ Draws a border at given coordinates with specified styles (solid, dotted, dashed).
Args:
- - image: The image on which the border should be drawn (usually im_black or
- im_colour.
-
- - xy: Tuple representing the top-left corner of the border e.g. (32, 100)
- where 32 is the x co-ordinate and 100 is the y-coordinate.
-
- - size: Size of the border as a tuple -> (width, height).
-
- - radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners.
-
- - thickness: Thickness of the border in pixels.
-
- - shrinkage: A tuple containing decimals presenting a percentage of shrinking
- -> (width_shrink_percentage, height_shrink_percentage).
- e.g. (0.1, 0.2) ~ shrinks the width of border by 10%, shrinks height of
- border by 20%
+ - image: Image on which the border should be drawn.
+ - xy: Tuple for the top-left corner of the border.
+ - size: Size of the border as a tuple (width, height).
+ - radius: Radius of the corners.
+ - thickness: Thickness of the border in pixels.
+ - shrinkage: Tuple for width and height shrinkage percentages.
+ - style: Style of the border ('solid', 'dotted', 'dashed').
"""
colour = 'black'
-
- # size from function paramter
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
a, b = (width - diameter), (height - diameter)
- # Set coordinates for staright 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)
p7, p8 = (x, p4[1]), (x, p3[1])
+
+ draw = ImageDraw.Draw(image)
+
+ # Choose the appropriate line drawing function based on style
+ if style == 'solid':
+ line_drawer = draw.line
+ elif style == 'dotted':
+ line_drawer = lambda coords, fill, width: draw_dotted_line(draw, coords[0], coords[1], fill, width)
+ elif style == 'dashed':
+ line_drawer = lambda coords, fill, width: draw_dashed_line(draw, coords[0], coords[1], fill, width)
+ else:
+ raise ValueError(f"Unknown style: {style}")
+
+ # Draw lines according to the chosen style
+ line_drawer((p1, p2), fill=colour, width=thickness)
+ line_drawer((p3, p4), fill=colour, width=thickness)
+ line_drawer((p5, p6), fill=colour, width=thickness)
+ line_drawer((p7, p8), fill=colour, width=thickness)
+
if radius != 0:
- # Set coordinates for arcs
c1, c2 = (x, y), (x + diameter, y + diameter)
c3, c4 = ((x + width) - diameter, y), (x + width, y + diameter)
c5, c6 = ((x + width) - diameter, (y + height) - diameter), (x + width, y + height)
c7, c8 = (x, (y + height) - diameter), (x + diameter, y + height)
- # Draw lines and arcs, creating a square with round corners
- draw = ImageDraw.Draw(image)
- draw.line((p1, p2), fill=colour, width=thickness)
- draw.line((p3, p4), fill=colour, width=thickness)
- draw.line((p5, p6), fill=colour, width=thickness)
- draw.line((p7, p8), fill=colour, width=thickness)
-
- if radius != 0:
draw.arc((c1, c2), 180, 270, fill=colour, width=thickness)
draw.arc((c3, c4), 270, 360, fill=colour, width=thickness)
draw.arc((c5, c6), 0, 90, fill=colour, width=thickness)
From a937564ec9e08154d03ec0751889d45ae82c1707 Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 20 Nov 2023 17:21:18 +0100
Subject: [PATCH 42/67] re-activate unittests
---
inkycal/tests/test_inkycal_stocks.py | 112 +++++++++++++--------------
1 file changed, 56 insertions(+), 56 deletions(-)
diff --git a/inkycal/tests/test_inkycal_stocks.py b/inkycal/tests/test_inkycal_stocks.py
index e9ebaba..38a7b2e 100755
--- a/inkycal/tests/test_inkycal_stocks.py
+++ b/inkycal/tests/test_inkycal_stocks.py
@@ -1,56 +1,56 @@
-# #!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()
+#!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()
From 341a9cdc74cd72d3efb675207f8424f936cd939e Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 20 Nov 2023 18:57:10 +0100
Subject: [PATCH 43/67] Update test_inkycal_stocks.py
---
inkycal/tests/test_inkycal_stocks.py | 111 ++++++++++++++++-----------
1 file changed, 65 insertions(+), 46 deletions(-)
diff --git a/inkycal/tests/test_inkycal_stocks.py b/inkycal/tests/test_inkycal_stocks.py
index 38a7b2e..6ed6334 100755
--- a/inkycal/tests/test_inkycal_stocks.py
+++ b/inkycal/tests/test_inkycal_stocks.py
@@ -1,56 +1,75 @@
-#!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"
- }
- }
+{
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [528, 20],
+ "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+},
+{
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [528, 20],
+ "tickers": [],
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+},
+{
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [528, 200],
+ "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+},
+{
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [528, 800],
+ "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+},
+{
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [528, 100],
+ "tickers": "TSLA,AMD,NVDA,^DJI,BTC-USD,EURUSD=X",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+},
+{
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [528, 400],
+ "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
+ "padding_x": 10, "padding_y": 10, "fontsize": 14, "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_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)
+ module.generate_image()
+ print('OK')
if __name__ == '__main__':
- logger = logging.getLogger()
- logger.level = logging.DEBUG
- logger.addHandler(logging.StreamHandler(sys.stdout))
-
- unittest.main()
+ unittest.main()
From d53d167f74a0381459df18069d2c6974df06308b Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 20 Nov 2023 18:58:56 +0100
Subject: [PATCH 44/67] Update inkycal_stocks.py
---
inkycal/modules/inkycal_stocks.py | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/inkycal/modules/inkycal_stocks.py b/inkycal/modules/inkycal_stocks.py
index e91e9f0..9611739 100755
--- a/inkycal/modules/inkycal_stocks.py
+++ b/inkycal/modules/inkycal_stocks.py
@@ -1,4 +1,5 @@
-#!python3
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
"""
Stocks Module for Inkycal Project
@@ -10,14 +11,14 @@ 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 inkycal.custom import write, internet_available
+from inkycal.modules.template import inkycal_module
+
try:
import yfinance as yf
except ImportError:
@@ -82,11 +83,11 @@ class Stocks(inkycal_module):
tmpPath = '/tmp/inkycal_stocks/'
try:
- if not os.path.exists(tmpPath):
- os.mkdir(tmpPath)
- print(f"Successfully created tmp directory {tmpPath} ")
+ os.mkdir(tmpPath)
except OSError:
print(f"Creation of tmp directory {tmpPath} failed")
+ else:
+ print(f"Successfully created tmp directory {tmpPath} ")
# Check if internet is available
if internet_available() == True:
@@ -96,10 +97,9 @@ class Stocks(inkycal_module):
# Set some parameters for formatting feeds
line_spacing = 1
- text_bbox_height = self.font.getbbox("hg")
- line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
+ line_height = self.font.getsize('hg')[1] + line_spacing
line_width = im_width
- max_lines = (im_height // line_height)
+ max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
logger.debug(f"max_lines: {max_lines}")
From 386fccc796578402de293871d917a8d1503c1d2d Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 20 Nov 2023 19:00:17 +0100
Subject: [PATCH 45/67] Update requirements.txt
---
requirements.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/requirements.txt b/requirements.txt
index 2bdd099..1b0b392 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,3 +24,4 @@ urllib3==2.0.7
python-dotenv==1.0.0
setuptools==68.2.2
html2text==2020.1.16
+yfinance==0.2.32
From 823df7253a38c7064d6f004ef3ea49b6d319ee87 Mon Sep 17 00:00:00 2001
From: Ace
Date: Mon, 20 Nov 2023 20:15:16 +0100
Subject: [PATCH 46/67] adapt for Pillow10
---
inkycal/modules/inkycal_stocks.py | 20 ++++++--------------
1 file changed, 6 insertions(+), 14 deletions(-)
diff --git a/inkycal/modules/inkycal_stocks.py b/inkycal/modules/inkycal_stocks.py
index 9611739..a18ed34 100755
--- a/inkycal/modules/inkycal_stocks.py
+++ b/inkycal/modules/inkycal_stocks.py
@@ -19,18 +19,9 @@ from PIL import Image
from inkycal.custom import write, internet_available
from inkycal.modules.template import inkycal_module
-try:
- import yfinance as yf
-except ImportError:
- print('yfinance is not installed! Please install with:')
- print('pip3 install yfinance')
-
-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__)
@@ -97,9 +88,10 @@ class Stocks(inkycal_module):
# 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] - text_bbox[1] + 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}")
From e78fd0d4b5563ab25c56e81838ee47e3cab30da2 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 21 Nov 2023 13:28:56 +0100
Subject: [PATCH 47/67] Update v2 driver
---
inkycal/display/drivers/epd_7_in_5_v2.py | 72 +++++++++++++-----------
1 file changed, 40 insertions(+), 32 deletions(-)
diff --git a/inkycal/display/drivers/epd_7_in_5_v2.py b/inkycal/display/drivers/epd_7_in_5_v2.py
index 97c6f0e..92929b8 100644
--- a/inkycal/display/drivers/epd_7_in_5_v2.py
+++ b/inkycal/display/drivers/epd_7_in_5_v2.py
@@ -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 ###
\ No newline at end of file
From 59c59e80f511f48095be9c911e721757c3169b25 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 21 Nov 2023 15:18:19 +0100
Subject: [PATCH 48/67] tests best practices
---
.gitignore | 2 +-
inkycal/modules/inky_image.py | 11 ++-
inkycal/modules/inkycal_stocks.py | 26 +++----
inkycal/tests/test_inkycal_stocks.py | 75 -------------------
{inkycal/tests => tests}/__init__.py | 0
{inkycal/tests => tests}/config.py | 1 -
{inkycal/tests => tests}/settings.json | 0
{inkycal/tests => tests}/test_ical_parser.py | 36 ++++-----
.../tests => tests}/test_inkycal_agenda.py | 30 +++-----
.../tests => tests}/test_inkycal_calendar.py | 27 ++-----
.../tests => tests}/test_inkycal_feeds.py | 28 +++----
.../tests => tests}/test_inkycal_image.py | 28 ++-----
.../tests => tests}/test_inkycal_jokes.py | 30 +++-----
.../tests => tests}/test_inkycal_slideshow.py | 37 ++++-----
tests/test_inkycal_stocks.py | 57 ++++++++++++++
.../test_inkycal_textfile_to_display.py | 37 ++++-----
.../tests => tests}/test_inkycal_todoist.py | 25 ++-----
.../tests => tests}/test_inkycal_weather.py | 31 +++-----
18 files changed, 177 insertions(+), 304 deletions(-)
delete mode 100755 inkycal/tests/test_inkycal_stocks.py
rename {inkycal/tests => tests}/__init__.py (100%)
rename {inkycal/tests => tests}/config.py (98%)
rename {inkycal/tests => tests}/settings.json (100%)
rename {inkycal/tests => tests}/test_ical_parser.py (55%)
rename {inkycal/tests => tests}/test_inkycal_agenda.py (73%)
rename {inkycal/tests => tests}/test_inkycal_calendar.py (81%)
rename {inkycal/tests => tests}/test_inkycal_feeds.py (69%)
rename {inkycal/tests => tests}/test_inkycal_image.py (85%)
rename {inkycal/tests => tests}/test_inkycal_jokes.py (64%)
rename {inkycal/tests => tests}/test_inkycal_slideshow.py (84%)
create mode 100755 tests/test_inkycal_stocks.py
rename {inkycal/tests => tests}/test_inkycal_textfile_to_display.py (87%)
rename {inkycal/tests => tests}/test_inkycal_todoist.py (66%)
rename {inkycal/tests => tests}/test_inkycal_weather.py (89%)
diff --git a/.gitignore b/.gitignore
index c338aec..73b644a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -146,7 +146,7 @@ dmypy.json
/logs
# inkycal tests
-/inkycal/tests/tmp/
+/tests/tmp/
!/inkycal/tests/*.py
/docsource/._build/
diff --git a/inkycal/modules/inky_image.py b/inkycal/modules/inky_image.py
index e069114..fb6df4e 100755
--- a/inkycal/modules/inky_image.py
+++ b/inkycal/modules/inky_image.py
@@ -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"""
diff --git a/inkycal/modules/inkycal_stocks.py b/inkycal/modules/inkycal_stocks.py
index a18ed34..cf58a41 100755
--- a/inkycal/modules/inkycal_stocks.py
+++ b/inkycal/modules/inkycal_stocks.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python3
-# -*- coding: utf-8 -*-
"""
Stocks Module for Inkycal Project
@@ -15,6 +13,7 @@ import logging
import os
from PIL import Image
+from matplotlib import pyplot
from inkycal.custom import write, internet_available
from inkycal.modules.template import inkycal_module
@@ -71,17 +70,14 @@ 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):
+ print(f"Creating tmp directory {tmpPath}")
os.mkdir(tmpPath)
- except OSError:
- print(f"Creation of tmp directory {tmpPath} failed")
- else:
- print(f"Successfully created tmp directory {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 :/')
@@ -89,7 +85,7 @@ class Stocks(inkycal_module):
# Set some parameters for formatting feeds
line_spacing = 1
text_bbox = self.font.getbbox("hg")
- line_height = text_bbox[3] - text_bbox[1] + line_spacing
+ line_height = text_bbox[3] + line_spacing
line_width = im_width
max_lines = (im_height // (line_height + line_spacing))
@@ -204,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("")
@@ -225,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}')
@@ -258,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')
diff --git a/inkycal/tests/test_inkycal_stocks.py b/inkycal/tests/test_inkycal_stocks.py
deleted file mode 100755
index 6ed6334..0000000
--- a/inkycal/tests/test_inkycal_stocks.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import unittest
-from inkycal.modules import Stocks as Module
-
-tests = [
-{
- "position": 1,
- "name": "Stocks",
- "config": {
- "size": [528, 20],
- "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
- "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
- }
-},
-{
- "position": 1,
- "name": "Stocks",
- "config": {
- "size": [528, 20],
- "tickers": [],
- "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
- }
-},
-{
- "position": 1,
- "name": "Stocks",
- "config": {
- "size": [528, 200],
- "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
- "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
- }
-},
-{
- "position": 1,
- "name": "Stocks",
- "config": {
- "size": [528, 800],
- "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
- "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
- }
-},
-{
- "position": 1,
- "name": "Stocks",
- "config": {
- "size": [528, 100],
- "tickers": "TSLA,AMD,NVDA,^DJI,BTC-USD,EURUSD=X",
- "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
- }
-},
-{
- "position": 1,
- "name": "Stocks",
- "config": {
- "size": [528, 400],
- "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
- "padding_x": 10, "padding_y": 10, "fontsize": 14, "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)
- module.generate_image()
- print('OK')
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/inkycal/tests/__init__.py b/tests/__init__.py
similarity index 100%
rename from inkycal/tests/__init__.py
rename to tests/__init__.py
diff --git a/inkycal/tests/config.py b/tests/config.py
similarity index 98%
rename from inkycal/tests/config.py
rename to tests/config.py
index 3b846fc..e7ba725 100644
--- a/inkycal/tests/config.py
+++ b/tests/config.py
@@ -1,4 +1,3 @@
-#!python
"""
Tests config
"""
diff --git a/inkycal/tests/settings.json b/tests/settings.json
similarity index 100%
rename from inkycal/tests/settings.json
rename to tests/settings.json
diff --git a/inkycal/tests/test_ical_parser.py b/tests/test_ical_parser.py
similarity index 55%
rename from inkycal/tests/test_ical_parser.py
rename to tests/test_ical_parser.py
index f92c06a..b57b23a 100755
--- a/inkycal/tests/test_ical_parser.py
+++ b/tests/test_ical_parser.py
@@ -1,56 +1,50 @@
-#!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
+from tests import Config
ical = iCalendar()
test_ical = Config.TEST_ICAL_URL
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
-class ical_parser_test(unittest.TestCase):
+
+class TestIcalendar(unittest.TestCase):
def test_load_url(self):
- print('testing loading via URL...', end="")
+ logger.info('testing loading via URL...')
ical.load_url(test_ical)
- print('OK')
+ logger.info('OK')
def test_get_events(self):
- print('testing parsing of events...', end="")
+ logger.info('testing parsing of events...')
ical.get_events(arrow.now(), arrow.now().shift(weeks=30))
- print('OK')
+ logger.info('OK')
def test_sorting(self):
- print('testing sorting of events...', end="")
+ logger.info('testing sorting of events...')
ical.sort()
- print('OK')
+ logger.info('OK')
def test_show_events(self):
- print('testing if events can be shown...', end="")
+ logger.info('testing if events can be shown...')
ical.show_events()
- print('OK')
+ logger.info('OK')
def test_laod_from_file(self):
- print('testing loading from file...', end="")
+ logger.info('testing loading from file...')
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')
+ logger.info('OK')
os.remove('dummy.ical')
-
-if __name__ == '__main__':
- logger = logging.getLogger()
- logger.level = logging.DEBUG
- logger.addHandler(logging.StreamHandler(sys.stdout))
-
- unittest.main()
diff --git a/inkycal/tests/test_inkycal_agenda.py b/tests/test_inkycal_agenda.py
similarity index 73%
rename from inkycal/tests/test_inkycal_agenda.py
rename to tests/test_inkycal_agenda.py
index 7bfeacf..af002ec 100755
--- a/inkycal/tests/test_inkycal_agenda.py
+++ b/tests/test_inkycal_agenda.py
@@ -1,17 +1,19 @@
-#!python3
"""
inkycal_agenda unittest
"""
import logging
-import sys
import unittest
-from inkycal.modules import Agenda as Module
+from inkycal.modules import Agenda
from inkycal.modules.inky_image import Inkyimage
-from inkycal.tests import Config
+from tests import Config
+
preview = Inkyimage.preview
merge = Inkyimage.merge
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
sample_url = Config.SAMPLE_ICAL_URL
tests = [
@@ -61,25 +63,13 @@ tests = [
]
-class module_test(unittest.TestCase):
- def test_get_config(self):
- print('getting data for web-ui...', end="")
- Module.get_config()
- print('OK')
+class TestAgenda(unittest.TestCase):
def test_generate_image(self):
for test in tests:
- print(f'test {tests.index(test) + 1} generating image..')
- module = Module(test)
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ module = Agenda(test)
im_black, im_colour = module.generate_image()
- print('OK')
+ logger.info('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()
diff --git a/inkycal/tests/test_inkycal_calendar.py b/tests/test_inkycal_calendar.py
similarity index 81%
rename from inkycal/tests/test_inkycal_calendar.py
rename to tests/test_inkycal_calendar.py
index a8438f8..cb28b9a 100755
--- a/inkycal/tests/test_inkycal_calendar.py
+++ b/tests/test_inkycal_calendar.py
@@ -1,20 +1,21 @@
-#!python3
"""
inkycal_calendar unittest
"""
import logging
-import sys
import unittest
-from inkycal.modules import Calendar as Module
-
+from inkycal.modules import Calendar
from inkycal.modules.inky_image import Inkyimage
-from inkycal.tests import Config
+from tests import Config
+
preview = Inkyimage.preview
merge = Inkyimage.merge
sample_url = Config.SAMPLE_ICAL_URL
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
tests = [
{
"name": "Calendar",
@@ -67,25 +68,13 @@ tests = [
]
-class module_test(unittest.TestCase):
- def test_get_config(self):
- print('getting data for web-ui...', end="")
- Module.get_config()
- print('OK')
+class TestCalendar(unittest.TestCase):
def test_generate_image(self):
for test in tests:
print(f'test {tests.index(test) + 1} generating image..', end="")
- module = Module(test)
+ module = Calendar(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()
diff --git a/inkycal/tests/test_inkycal_feeds.py b/tests/test_inkycal_feeds.py
similarity index 69%
rename from inkycal/tests/test_inkycal_feeds.py
rename to tests/test_inkycal_feeds.py
index 30f9613..ccf4481 100755
--- a/inkycal/tests/test_inkycal_feeds.py
+++ b/tests/test_inkycal_feeds.py
@@ -1,17 +1,18 @@
-#!python3
"""
inkycal_feeds unittest
"""
import logging
-import sys
import unittest
-from inkycal.modules import Feeds as Module
+from inkycal.modules import Feeds
from inkycal.modules.inky_image import Inkyimage
-from inkycal.tests import Config
+from tests import Config
preview = Inkyimage.preview
merge = Inkyimage.merge
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
tests = [
{
"name": "Feeds",
@@ -43,25 +44,14 @@ tests = [
]
-class module_test(unittest.TestCase):
- def test_get_config(self):
- print('getting data for web-ui...', end="")
- Module.get_config()
- print('OK')
+class TestFeeds(unittest.TestCase):
def test_generate_image(self):
for test in tests:
- print(f'test {tests.index(test) + 1} generating image..')
- module = Module(test)
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ module = Feeds(test)
im_black, im_colour = module.generate_image()
- print('OK')
+ logger.info('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()
diff --git a/inkycal/tests/test_inkycal_image.py b/tests/test_inkycal_image.py
similarity index 85%
rename from inkycal/tests/test_inkycal_image.py
rename to tests/test_inkycal_image.py
index 873d604..4ee3e49 100755
--- a/inkycal/tests/test_inkycal_image.py
+++ b/tests/test_inkycal_image.py
@@ -1,19 +1,16 @@
-#!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
+from tests import Config
+
preview = Inkyimage.preview
merge = Inkyimage.merge
@@ -23,6 +20,9 @@ im = Image.open(requests.get(url, stream=True).raw)
im.save("test.png", "PNG")
test_path = "test.png"
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
tests = [
{
"name": "Inkyimage",
@@ -104,25 +104,13 @@ tests = [
]
-class module_test(unittest.TestCase):
- def test_get_config(self):
- print('getting data for web-ui...', end="")
- Module.get_config()
- print('OK')
+class TestInkyImage(unittest.TestCase):
def test_generate_image(self):
for test in tests:
- print(f'test {tests.index(test) + 1} generating image..')
+ logger.info(f'test {tests.index(test) + 1} generating image..')
module = Module(test)
im_black, im_colour = module.generate_image()
- print('OK')
+ logger.info('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()
diff --git a/inkycal/tests/test_inkycal_jokes.py b/tests/test_inkycal_jokes.py
similarity index 64%
rename from inkycal/tests/test_inkycal_jokes.py
rename to tests/test_inkycal_jokes.py
index 547f12f..a4455ef 100755
--- a/inkycal/tests/test_inkycal_jokes.py
+++ b/tests/test_inkycal_jokes.py
@@ -1,17 +1,19 @@
-#!python3
"""
inkycal_jokes unittest
"""
import logging
-import sys
import unittest
-from inkycal.modules import Jokes as Module
+
+from inkycal.modules import Jokes
from inkycal.modules.inky_image import Inkyimage
-from inkycal.tests import Config
+from tests import Config
preview = Inkyimage.preview
merge = Inkyimage.merge
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
tests = [
{
"name": "Jokes",
@@ -46,25 +48,13 @@ tests = [
]
-class module_test(unittest.TestCase):
- def test_get_config(self):
- print('getting data for web-ui...', end="")
- Module.get_config()
- print('OK')
+class TestJokes(unittest.TestCase):
def test_generate_image(self):
for test in tests:
- print(f'test {tests.index(test) + 1} generating image..')
- module = Module(test)
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ module = Jokes(test)
im_black, im_colour = module.generate_image()
- print('OK')
+ logger.info('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()
diff --git a/inkycal/tests/test_inkycal_slideshow.py b/tests/test_inkycal_slideshow.py
similarity index 84%
rename from inkycal/tests/test_inkycal_slideshow.py
rename to tests/test_inkycal_slideshow.py
index 834f406..9c811b9 100755
--- a/inkycal/tests/test_inkycal_slideshow.py
+++ b/tests/test_inkycal_slideshow.py
@@ -1,18 +1,16 @@
-#!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 import Slideshow
from inkycal.modules.inky_image import Inkyimage
-from inkycal.tests import Config
+from tests import Config
preview = Inkyimage.preview
merge = Inkyimage.merge
@@ -31,6 +29,9 @@ for count, url in enumerate(im_urls):
test_path = "tmp"
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
tests = [
{
"name": "Slideshow",
@@ -134,24 +135,20 @@ tests = [
]
-class module_test(unittest.TestCase):
- def test_get_config(self):
- print('getting data for web-ui...', end="")
- Module.get_config()
- print('OK')
+class TestSlideshow(unittest.TestCase):
def test_generate_image(self):
for test in tests:
- print(f'test {tests.index(test) + 1} generating image..')
- module = Module(test)
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ module = Slideshow(test)
im_black, im_colour = module.generate_image()
- print('OK')
+ logger.info('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])
+ logger.info(f'testing switching to next images..')
+ module = Slideshow(tests[0])
im_black, im_colour = module.generate_image()
if Config.USE_PREVIEW:
preview(merge(im_black, im_colour))
@@ -164,12 +161,4 @@ class module_test(unittest.TestCase):
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()
+ logger.info('OK')
diff --git a/tests/test_inkycal_stocks.py b/tests/test_inkycal_stocks.py
new file mode 100755
index 0000000..6a6b224
--- /dev/null
+++ b/tests/test_inkycal_stocks.py
@@ -0,0 +1,57 @@
+import logging
+import unittest
+
+from inkycal.modules import Stocks
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
+tests = [
+ {
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [400, 100],
+ "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [400, 200],
+ "tickers": [],
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [400, 300],
+ "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "Stocks",
+ "config": {
+ "size": [400, 400],
+ "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'],
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ }
+]
+
+
+class TestStocks(unittest.TestCase):
+
+ def test_generate_image(self):
+ for test in tests:
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ module = Stocks(test)
+ module.generate_image()
+ logger.info('OK')
+
diff --git a/inkycal/tests/test_inkycal_textfile_to_display.py b/tests/test_inkycal_textfile_to_display.py
similarity index 87%
rename from inkycal/tests/test_inkycal_textfile_to_display.py
rename to tests/test_inkycal_textfile_to_display.py
index a667af1..223e9a5 100644
--- a/inkycal/tests/test_inkycal_textfile_to_display.py
+++ b/tests/test_inkycal_textfile_to_display.py
@@ -1,19 +1,23 @@
-#!python3
+"""
+Inkycal Text module
+"""
+
import logging
import os
-import sys
import unittest
-from inkycal.modules import TextToDisplay as Module
+from inkycal.modules import TextToDisplay
from inkycal.modules.inky_image import Inkyimage
-from inkycal.tests import Config
+from tests import Config
preview = Inkyimage.preview
merge = Inkyimage.merge
-
temp_path = f"{Config.TEMP_PATH}/temp.txt"
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
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.',
@@ -85,33 +89,20 @@ class TestTextToDisplay(unittest.TestCase):
def setUp(self):
self.temp_path = temp_path
if not os.path.exists(self.temp_path):
- print("could not find temporary file, creating now.")
+ logger.info("could not find temporary file, creating now.")
with open(self.temp_path, encoding="utf-8", mode="w") as file:
file.writelines(dummy_data)
- 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)
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ module = TextToDisplay(test)
im_black, im_colour = module.generate_image()
- print('OK')
+ logger.info('OK')
if Config.USE_PREVIEW:
preview(merge(im_black, im_colour))
def tearDown(self):
if os.path.exists(self.temp_path):
- print("deleting temporary file.")
+ logger.info("deleting temporary file.")
os.remove(self.temp_path)
-
-
-if __name__ == '__main__':
- logger = logging.getLogger()
- logger.level = logging.DEBUG
- logger.addHandler(logging.StreamHandler(sys.stdout))
-
- unittest.main()
diff --git a/inkycal/tests/test_inkycal_todoist.py b/tests/test_inkycal_todoist.py
similarity index 66%
rename from inkycal/tests/test_inkycal_todoist.py
rename to tests/test_inkycal_todoist.py
index bf87caf..44c3041 100644
--- a/inkycal/tests/test_inkycal_todoist.py
+++ b/tests/test_inkycal_todoist.py
@@ -1,19 +1,21 @@
-#!python3
"""
inkycal_todoist unittest
"""
import logging
import sys
import unittest
-from inkycal.modules import Todoist as Module
+from inkycal.modules import Todoist
from inkycal.modules.inky_image import Inkyimage
-from inkycal.tests import Config
+from tests import Config
preview = Inkyimage.preview
merge = Inkyimage.merge
api_key = Config.TODOIST_API_KEY
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
tests = [
{
"name": "Todoist",
@@ -30,29 +32,16 @@ tests = [
]
-class module_test(unittest.TestCase):
-
- def test_get_config(self):
- print('getting data for web-ui...', end="")
- Module.get_config()
- print('OK')
+class TestTodoist(unittest.TestCase):
def test_generate_image(self):
if api_key:
for test in tests:
print(f'test {tests.index(test) + 1} generating image..')
- module = Module(test)
+ module = Todoist(test)
im_black, im_colour = module.generate_image()
print('OK')
if Config.USE_PREVIEW:
preview(merge(im_black, im_colour))
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()
diff --git a/inkycal/tests/test_inkycal_weather.py b/tests/test_inkycal_weather.py
similarity index 89%
rename from inkycal/tests/test_inkycal_weather.py
rename to tests/test_inkycal_weather.py
index b6bde42..bcc50ce 100755
--- a/inkycal/tests/test_inkycal_weather.py
+++ b/tests/test_inkycal_weather.py
@@ -1,20 +1,22 @@
-#!python3
"""
inkycal_weather unittest
"""
import logging
-import sys
import unittest
-from inkycal.modules import Weather as Module
+from inkycal.modules import Weather
from inkycal.modules.inky_image import Inkyimage
-from inkycal.tests import Config
+from tests import Config
+
preview = Inkyimage.preview
merge = Inkyimage.merge
owm_api_key = Config.OPENWEATHERMAP_API_KEY
location = '2825297'
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
tests = [
{
"position": 1,
@@ -171,27 +173,14 @@ tests = [
]
-class module_test(unittest.TestCase):
- def test_get_config(self):
- print('getting data for web-ui...', end="")
- Module.get_config()
- print('OK')
+class TestWeather(unittest.TestCase):
def test_generate_image(self):
for test in tests:
- print(f'test {tests.index(test) + 1} generating image..')
- module = Module(test)
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ module = Weather(test)
im_black, im_colour = module.generate_image()
- print('OK')
+ logger.info('OK')
if Config.USE_PREVIEW:
merged = merge(im_black, im_colour)
preview(merged)
-
-
-
-if __name__ == '__main__':
- logger = logging.getLogger()
- logger.level = logging.DEBUG
- logger.addHandler(logging.StreamHandler(sys.stdout))
-
- unittest.main()
From d441b1a7ef8996d22f67eaa0b80df96cfea67f4d Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 21 Nov 2023 15:56:28 +0100
Subject: [PATCH 49/67] unittest improvements
---
.github/workflows/test-on-rpi.yml | 3 +--
.github/workflows/update-os.yml | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 467ec57..3693efd 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -42,7 +42,7 @@ jobs:
sudo apt-get update -y
python --version
sudo apt-get install -y python3-pip
- sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
+ sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev wkhtmltopdf -y
echo $PWD && ls
git clone --branch main --single-branch https://github.com/aceinnolab/Inkycal
cd Inkycal
@@ -53,6 +53,5 @@ jobs:
pip install -e ./
pip install RPi.GPIO==0.7.1 spidev==3.5
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
- cd inkycal/tests
pip install pytest
python -m pytest
diff --git a/.github/workflows/update-os.yml b/.github/workflows/update-os.yml
index 3e87435..15f98ef 100644
--- a/.github/workflows/update-os.yml
+++ b/.github/workflows/update-os.yml
@@ -37,7 +37,7 @@ jobs:
sudo apt-get update -y
python --version
sudo apt-get install -y python3-pip
- sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev -y
+ sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev wkhtmltopdf -y
echo $PWD && ls
git clone --branch main --single-branch https://github.com/aceinnolab/Inkycal
cd Inkycal
@@ -47,7 +47,6 @@ jobs:
pip install wheel
pip install -e ./
pip install RPi.GPIO==0.7.1 spidev==3.5
- cd inkycal/tests
wget https://raw.githubusercontent.com/aceinnolab/Inkycal/assets/tests/settings.json
pip install pytest
python -m pytest
From 50f11dc2a4706caf404af714d12cc648d14bd618 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 21 Nov 2023 16:03:09 +0100
Subject: [PATCH 50/67] fix too small line-height
---
inkycal/custom/functions.py | 13 ++++++------
inkycal/modules/inkycal_agenda.py | 20 ++++++++-----------
inkycal/modules/inkycal_calendar.py | 10 ++--------
inkycal/modules/inkycal_feeds.py | 8 +-------
inkycal/modules/inkycal_jokes.py | 8 +-------
.../modules/inkycal_textfile_to_display.py | 7 +------
inkycal/modules/inkycal_todoist.py | 8 +-------
7 files changed, 21 insertions(+), 53 deletions(-)
diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py
index ff3f16e..debe21e 100644
--- a/inkycal/custom/functions.py
+++ b/inkycal/custom/functions.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python3
-# -*- coding: utf-8 -*-
"""
Inkycal custom-functions for ease-of-use
@@ -11,7 +9,7 @@ import time
import traceback
import requests
-from PIL import Image, ImageDraw, ImageFont
+from PIL import ImageFont
logs = logging.getLogger(__name__)
logs.setLevel(level=logging.INFO)
@@ -101,12 +99,12 @@ def auto_fontsize(font, max_height):
A PIL font object with modified height.
"""
text_bbox = font.getbbox("hg")
- text_height = text_bbox[3] - text_bbox[1]
+ 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] - text_bbox[1]
+ text_height = text_bbox[3]
return font
@@ -279,6 +277,7 @@ def internet_available():
from PIL import Image, ImageDraw
+
def draw_dotted_line(draw, start, end, colour, thickness):
"""Draws a dotted line between start and end points using dots."""
delta_x = end[0] - start[0]
@@ -292,7 +291,8 @@ def draw_dotted_line(draw, start, end, colour, thickness):
# Drawing a circle at each dot position to create a dotted effect
draw.ellipse([(dot_position[0] - thickness, dot_position[1] - thickness),
(dot_position[0] + thickness, dot_position[1] + thickness)],
- fill=colour)
+ fill=colour)
+
def draw_dashed_line(draw, start, end, colour, thickness):
"""Draws a dashed line between start and end points."""
@@ -309,6 +309,7 @@ def draw_dashed_line(draw, start, end, colour, thickness):
segment_start[1] + (step_size * delta_y / distance))
draw.line((segment_start, segment_end), fill=colour, width=thickness)
+
def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1), style='solid'):
"""
Draws a border at given coordinates with specified styles (solid, dotted, dashed).
diff --git a/inkycal/modules/inkycal_agenda.py b/inkycal/modules/inkycal_agenda.py
index b5adbd2..e1ccba5 100755
--- a/inkycal/modules/inkycal_agenda.py
+++ b/inkycal/modules/inkycal_agenda.py
@@ -1,5 +1,3 @@
-#!python3
-
"""
Inkycal Agenda Module
Copyright by aceinnolab
@@ -100,7 +98,7 @@ class Agenda(inkycal_module):
line_spacing = 1
text_bbox_height = self.font.getbbox("hg")
- line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
+ 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}')
@@ -111,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
@@ -137,7 +137,7 @@ class Agenda(inkycal_module):
# Set the width for date, time and event titles
date_width = int(max([self.font.getlength(
dates['begin'].format(self.date_format, locale=self.language))
- for dates in agenda_events]) * 1.2)
+ for dates in agenda_events]) * 1.2)
logger.debug(f'date_width: {date_width}')
# Calculate positions for each line
@@ -152,7 +152,7 @@ class Agenda(inkycal_module):
time_width = int(max([self.font.getlength(
events['begin'].format(self.time_format, locale=self.language))
- for events in upcoming_events]) * 1.2)
+ for events in upcoming_events]) * 1.2)
logger.debug(f'time_width: {time_width}')
# Calculate x-pos for time
@@ -227,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')
diff --git a/inkycal/modules/inkycal_calendar.py b/inkycal/modules/inkycal_calendar.py
index a6d3154..9c85984 100755
--- a/inkycal/modules/inkycal_calendar.py
+++ b/inkycal/modules/inkycal_calendar.py
@@ -1,5 +1,3 @@
-#!python3
-
"""
Inkycal Calendar Module
Copyright by aceinnolab
@@ -267,7 +265,7 @@ class Calendar(inkycal_module):
# find out how many lines can fit at max in the event section
line_spacing = 2
text_bbox_height = self.font.getbbox("hg")
- line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
+ line_height = text_bbox_height[3] + line_spacing
max_event_lines = events_height // (line_height + line_spacing)
# generate list of coordinates for each line
@@ -356,7 +354,7 @@ class Calendar(inkycal_module):
)
text_bbox_height = self.font.getbbox("hg")
- line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
+ line_height = text_bbox_height[3] + line_spacing
event_width_s = im_width - date_width - time_width
event_width_l = im_width - date_width
@@ -433,7 +431,3 @@ class Calendar(inkycal_module):
# return the images ready for the display
return im_black, im_colour
-
-
-if __name__ == '__main__':
- print(f'running {__name__} in standalone mode')
diff --git a/inkycal/modules/inkycal_feeds.py b/inkycal/modules/inkycal_feeds.py
index e354b89..d7bdde3 100644
--- a/inkycal/modules/inkycal_feeds.py
+++ b/inkycal/modules/inkycal_feeds.py
@@ -1,5 +1,3 @@
-#!python3
-
"""
Feeds module for InkyCal Project
Copyright by aceinnolab
@@ -94,7 +92,7 @@ class Feeds(inkycal_module):
line_width = im_width
text_bbox_height = self.font.getbbox("hg")
- line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
+ 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
@@ -151,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')
diff --git a/inkycal/modules/inkycal_jokes.py b/inkycal/modules/inkycal_jokes.py
index deb4a41..5f0085e 100755
--- a/inkycal/modules/inkycal_jokes.py
+++ b/inkycal/modules/inkycal_jokes.py
@@ -1,5 +1,3 @@
-#!python3
-
"""
iCanHazDadJoke module for InkyCal Project
Special thanks to Erik Fredericks (@efredericks) for the template!
@@ -56,7 +54,7 @@ class Jokes(inkycal_module):
# Set some parameters for formatting feeds
line_spacing = 5
text_bbox = self.font.getbbox("hg")
- line_height = text_bbox[3] - text_bbox[1] + line_spacing
+ line_height = text_bbox[3] + line_spacing
line_width = im_width
max_lines = (im_height // (line_height + line_spacing))
@@ -98,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')
diff --git a/inkycal/modules/inkycal_textfile_to_display.py b/inkycal/modules/inkycal_textfile_to_display.py
index cd522d9..0680cdf 100644
--- a/inkycal/modules/inkycal_textfile_to_display.py
+++ b/inkycal/modules/inkycal_textfile_to_display.py
@@ -1,4 +1,3 @@
-#!python3
"""
Textfile module for InkyCal Project
@@ -68,7 +67,7 @@ class TextToDisplay(inkycal_module):
# Set some parameters for formatting feeds
line_spacing = 4
text_bbox_height = self.font.getbbox("hg")
- line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
+ line_height = text_bbox_height[3] + line_spacing
line_width = im_width
max_lines = im_height // line_height
@@ -110,7 +109,3 @@ class TextToDisplay(inkycal_module):
# return images
return im_black, im_colour
-
-
-if __name__ == '__main__':
- print(f'running {__name__} in standalone/debug mode')
diff --git a/inkycal/modules/inkycal_todoist.py b/inkycal/modules/inkycal_todoist.py
index f290253..55e725e 100644
--- a/inkycal/modules/inkycal_todoist.py
+++ b/inkycal/modules/inkycal_todoist.py
@@ -1,5 +1,3 @@
-#!python3
-
"""
Inkycal Todoist Module
Copyright by aceinnolab
@@ -87,7 +85,7 @@ class Todoist(inkycal_module):
# Set some parameters for formatting todos
line_spacing = 1
text_bbox_height = self.font.getbbox("hg")
- line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
+ line_height = text_bbox_height[3] + line_spacing
line_width = im_width
max_lines = im_height // line_height
@@ -198,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')
From 53df3ccddb9bcd09d6d7bb3448a457406970a269 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 21 Nov 2023 16:26:37 +0100
Subject: [PATCH 51/67] increase available space
---
.github/workflows/test-on-rpi.yml | 2 +-
.github/workflows/update-os.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/test-on-rpi.yml b/.github/workflows/test-on-rpi.yml
index 3693efd..52b9dc1 100644
--- a/.github/workflows/test-on-rpi.yml
+++ b/.github/workflows/test-on-rpi.yml
@@ -28,7 +28,7 @@ jobs:
with:
# Set the base_image to the desired Raspberry Pi OS version
base_image: https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2023-10-10/2023-10-10-raspios-bookworm-armhf-lite.img.xz
- image_additional_mb: 2000 # enlarge free space to 2 GB
+ image_additional_mb: 2560 # enlarge free space to 2.5 GB
optimize_image: true
user: inky
commands: |
diff --git a/.github/workflows/update-os.yml b/.github/workflows/update-os.yml
index 15f98ef..af658d8 100644
--- a/.github/workflows/update-os.yml
+++ b/.github/workflows/update-os.yml
@@ -23,7 +23,7 @@ jobs:
with:
# Set the base_image to the desired Raspberry Pi OS version
base_image: https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2023-05-03/2023-05-03-raspios-bullseye-armhf-lite.img.xz
- image_additional_mb: 2000 # enlarge free space to 2 GB
+ image_additional_mb: 2560 # enlarge free space to 2 GB
optimize_image: true
user: inky
commands: |
From 2868b389b660f1b343fe14284d0a8cfd8b4cffa0 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 21 Nov 2023 16:58:16 +0100
Subject: [PATCH 52/67] fix unittest
---
tests/test_inkycal_textfile_to_display.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tests/test_inkycal_textfile_to_display.py b/tests/test_inkycal_textfile_to_display.py
index 223e9a5..557b4d3 100644
--- a/tests/test_inkycal_textfile_to_display.py
+++ b/tests/test_inkycal_textfile_to_display.py
@@ -13,11 +13,11 @@ from tests import Config
preview = Inkyimage.preview
merge = Inkyimage.merge
-temp_path = f"{Config.TEMP_PATH}/temp.txt"
-
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
+temp_path = f"{Config.TEMP_PATH}/temp.txt"
+
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.',
@@ -88,7 +88,7 @@ class TestTextToDisplay(unittest.TestCase):
def setUp(self):
self.temp_path = temp_path
- if not os.path.exists(self.temp_path):
+ if not os.path.exists(Config.TEMP_PATH):
logger.info("could not find temporary file, creating now.")
with open(self.temp_path, encoding="utf-8", mode="w") as file:
file.writelines(dummy_data)
From e093a134770dc984342b2e6bd6a8d682c3033aea Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 21 Nov 2023 18:14:19 +0100
Subject: [PATCH 53/67] fix unittest
---
tests/test_inkycal_textfile_to_display.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/test_inkycal_textfile_to_display.py b/tests/test_inkycal_textfile_to_display.py
index 557b4d3..ac5e2b6 100644
--- a/tests/test_inkycal_textfile_to_display.py
+++ b/tests/test_inkycal_textfile_to_display.py
@@ -89,6 +89,7 @@ class TestTextToDisplay(unittest.TestCase):
def setUp(self):
self.temp_path = temp_path
if not os.path.exists(Config.TEMP_PATH):
+ os.mkdir(Config.TEMP_PATH)
logger.info("could not find temporary file, creating now.")
with open(self.temp_path, encoding="utf-8", mode="w") as file:
file.writelines(dummy_data)
From f0cb8528b8d20ff68b25fa8830f84520c9455d83 Mon Sep 17 00:00:00 2001
From: Ace
Date: Tue, 21 Nov 2023 20:16:10 +0100
Subject: [PATCH 54/67] add webshot module
---
README.md | 70 ++++++------
inkycal/custom/functions.py | 9 +-
inkycal/modules/__init__.py | 1 +
inkycal/modules/inkycal_webshot.py | 164 +++++++++++++++++++++++++++++
requirements.txt | 13 +--
tests/test_inkycal_webshot.py | 65 ++++++++++++
6 files changed, 277 insertions(+), 45 deletions(-)
create mode 100644 inkycal/modules/inkycal_webshot.py
create mode 100755 tests/test_inkycal_webshot.py
diff --git a/README.md b/README.md
index 23895a9..fdb8718 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
# Welcome to inkycal v2.0.3!
-
+
-
+
@@ -48,44 +48,44 @@ Before you can start, please ensure you have one of the supported displays and o
**❗️Important note: e-paper displays cannot be simply connected to the Raspberry Pi, but require a driver board. The links below may or may not contain the required driver board. Please ensure you get the correct driver board for the display!**
-| type | vendor | affiliate links to product |
-| -- | -- | -- |
-| 7.5" Inkycal (plug-and-play) | Author of Inkycal | [Buy on Tindie](https://www.tindie.com/products/aceisace4444/inkycal-build-v1/) Pre-configured version of Inkycal with custom frame and a web-ui. You do not need to buy anything extra. Includes Raspberry Pi Zero W, 7.5" e-paper, microSD card, driver board, custom packaging and 1m of cable. Comes pre-assembled for plug-and-play. |
-| Inkycal frame | Author of Inkycal | coming soon (ultraslim frame with custom-made front and backcover inkl. ultraslim driver board). You will need a Raspberry Pi and a 7.5" e-paper display |
-| `[serial]` 12.48" (1304×984px) display | waveshare / gooddisplay | Waveshare 12.48 Inch E-Paper
-| `[serial]` 7.5" (640x384px) -> v1 display | waveshare / gooddisplay | Waveshare 7.5 Inch E-Paper |
-| `[serial]` 7.5" (800x400px) -> v2 display| waveshare / gooddisplay | Waveshare 7.5 Inch E-Paper |
-| `[serial]` 7.5" (880x528px) -> v3 display | waveshare / gooddisplay | Waveshare 7.5 Inch E-Paper |
-| `[serial]` 5.83" (400x300px) display | waveshare / gooddisplay | Waveshare 5.83 Inch E-Paper |
-| `[serial]` 4.2" (400x300px)display | waveshare / gooddisplay | Waveshare 4.2 Inch E-Paper |
-| `[parallel]` 10.3" (1872×1404px) display | waveshare / gooddisplay | Waveshare 10.3 Inch E-Paper |
-| `[parallel]` 9.7" (1200×825px) display | waveshare / gooddisplay | Waveshare 9.7 Inch E-Paper |
-| `[parallel]` 7.8" (1872×1404px) display | waveshare / gooddisplay | Waveshare 7.8" E-Paper |
-| Raspberry Pi Zero W | Raspberry Pi | Raspberry Pi Zero W |
-| MicroSD card | Sandisk | MicroSD card (8GB) |
+| type | vendor | affiliate links to product |
+|-------------------------------------------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 7.5" Inkycal (plug-and-play) | Author of Inkycal | [Buy on Tindie](https://www.tindie.com/products/aceisace4444/inkycal-build-v1/) Pre-configured version of Inkycal with custom frame and a web-ui. You do not need to buy anything extra. Includes Raspberry Pi Zero W, 7.5" e-paper, microSD card, driver board, custom packaging and 1m of cable. Comes pre-assembled for plug-and-play. |
+| Inkycal frame | Author of Inkycal | coming soon (ultraslim frame with custom-made front and backcover inkl. ultraslim driver board). You will need a Raspberry Pi and a 7.5" e-paper display |
+| `[serial]` 12.48" (1304×984px) display | waveshare / gooddisplay | Waveshare 12.48 Inch E-Paper |
+| `[serial]` 7.5" (640x384px) -> v1 display | waveshare / gooddisplay | Waveshare 7.5 Inch E-Paper |
+| `[serial]` 7.5" (800x400px) -> v2 display | waveshare / gooddisplay | Waveshare 7.5 Inch E-Paper |
+| `[serial]` 7.5" (880x528px) -> v3 display | waveshare / gooddisplay | Waveshare 7.5 Inch E-Paper |
+| `[serial]` 5.83" (400x300px) display | waveshare / gooddisplay | Waveshare 5.83 Inch E-Paper |
+| `[serial]` 4.2" (400x300px)display | waveshare / gooddisplay | Waveshare 4.2 Inch E-Paper |
+| `[parallel]` 10.3" (1872×1404px) display | waveshare / gooddisplay | Waveshare 10.3 Inch E-Paper |
+| `[parallel]` 9.7" (1200×825px) display | waveshare / gooddisplay | Waveshare 9.7 Inch E-Paper |
+| `[parallel]` 7.8" (1872×1404px) display | waveshare / gooddisplay | Waveshare 7.8" E-Paper |
+| Raspberry Pi Zero W | Raspberry Pi | Raspberry Pi Zero W |
+| MicroSD card | Sandisk | MicroSD card (8GB) |
## Configuring the Raspberry Pi
-1. Flash Raspberry Pi OS on your microSD card (min. 4GB) with [Raspberry Pi Imager](https://rptl.io/imager). Use the following settings:
+Flash Raspberry Pi OS on your microSD card (min. 4GB) with [Raspberry Pi Imager](https://rptl.io/imager). Use the following settings:
-| option | value |
-| :-- | :--: |
-| hostname | inkycal |
-| enable ssh | yes |
-| set username and password | yes |
-| username | a username you like |
-| password | a password you can remember |
-| set Wi-Fi | yes |
-| Wi-Fi SSID | your Wi-Fi name |
-| Wi-Fi password | your Wi-Fi password |
+| option | value |
+|:---------------------------|:----------------------------:|
+| hostname | inkycal |
+| enable ssh | yes |
+| set username and password | yes |
+| username | a username you like |
+| password | a password you can remember |
+| set Wi-Fi | yes |
+| Wi-Fi SSID | your Wi-Fi name |
+| Wi-Fi password | your Wi-Fi password |
-2. Create and download `settings.json` file for Inkycal from the [WEB-UI](https://aceisace.eu.pythonanywhere.com/inkycal-config-v2-0-0). Add the modules you want with the add module button.
-3. Copy the `settings.json` to the flashed microSD card in the `/boot` folder of microSD card. On Windows, this is the only visible directory on the SD card. On Linux, copy these files to `/boot` of the microSD card.
-4. Eject the microSD card from your computer now, insert it in the Raspberry Pi and power the Raspberry Pi.
-5. Once the green LED has stopped blinking after ~3 minutes, you can connect to your Raspberry Pi via SSH using a SSH Client. We suggest [Termius](https://termius.com/download/windows)
+1. Create and download `settings.json` file for Inkycal from the [WEB-UI](https://aceisace.eu.pythonanywhere.com/inkycal-config-v2-0-0). Add the modules you want with the add module button.
+2. Copy the `settings.json` to the flashed microSD card in the `/boot` folder of microSD card. On Windows, this is the only visible directory on the SD card. On Linux, copy these files to `/boot` of the microSD card.
+3. Eject the microSD card from your computer now, insert it in the Raspberry Pi and power the Raspberry Pi.
+4. Once the green LED has stopped blinking after ~3 minutes, you can connect to your Raspberry Pi via SSH using a SSH Client. We suggest [Termius](https://termius.com/download/windows)
on your smartphone. Use the address: `inkycal.local` with the username and password you set earlier. For more detailed instructions, check out the page from the [Raspberry Pi website](https://www.raspberrypi.org/documentation/remote-access/ssh/)
-6. After connecting via SSH, run the following commands, line by line:
+5. After connecting via SSH, run the following commands, line by line:
```bash
sudo raspi-config --expand-rootfs
sudo sed -i s/#dtparam=spi=on/dtparam=spi=on/ /boot/config.txt
@@ -118,9 +118,9 @@ These commands expand the filesystem, enable SPI and set up the correct timezone
Run the following steps to install Inkycal. Do **not** use sudo for this, except where explicitly specified.
```bash
# the next line is for the Raspberry Pi only
-sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev
+sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev wkhtmltopdf
cd $HOME
-git clone --branch main --single-branch https://github.com/aceisace/Inkycal
+git clone --branch main --single-branch https://github.com/aceinnolab/Inkycal
cd Inkycal
python3 -m venv venv
source venv/bin/activate
diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py
index ff3f16e..47fd682 100644
--- a/inkycal/custom/functions.py
+++ b/inkycal/custom/functions.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python3
-# -*- coding: utf-8 -*-
"""
Inkycal custom-functions for ease-of-use
@@ -11,7 +9,7 @@ import time
import traceback
import requests
-from PIL import Image, ImageDraw, ImageFont
+from PIL import ImageFont
logs = logging.getLogger(__name__)
logs.setLevel(level=logging.INFO)
@@ -279,6 +277,7 @@ def internet_available():
from PIL import Image, ImageDraw
+
def draw_dotted_line(draw, start, end, colour, thickness):
"""Draws a dotted line between start and end points using dots."""
delta_x = end[0] - start[0]
@@ -292,7 +291,8 @@ def draw_dotted_line(draw, start, end, colour, thickness):
# Drawing a circle at each dot position to create a dotted effect
draw.ellipse([(dot_position[0] - thickness, dot_position[1] - thickness),
(dot_position[0] + thickness, dot_position[1] + thickness)],
- fill=colour)
+ fill=colour)
+
def draw_dashed_line(draw, start, end, colour, thickness):
"""Draws a dashed line between start and end points."""
@@ -309,6 +309,7 @@ def draw_dashed_line(draw, start, end, colour, thickness):
segment_start[1] + (step_size * delta_y / distance))
draw.line((segment_start, segment_end), fill=colour, width=thickness)
+
def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1), style='solid'):
"""
Draws a border at given coordinates with specified styles (solid, dotted, dashed).
diff --git a/inkycal/modules/__init__.py b/inkycal/modules/__init__.py
index 21df220..6a23eac 100755
--- a/inkycal/modules/__init__.py
+++ b/inkycal/modules/__init__.py
@@ -8,3 +8,4 @@ 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
diff --git a/inkycal/modules/inkycal_webshot.py b/inkycal/modules/inkycal_webshot.py
new file mode 100644
index 0000000..20a3ce7
--- /dev/null
+++ b/inkycal/modules/inkycal_webshot.py
@@ -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
diff --git a/requirements.txt b/requirements.txt
index 1b0b392..2dbb567 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,12 @@
arrow==1.3.0
-certifi==2023.7.22
+certifi==2023.11.17
cycler==0.12.1
feedparser==6.0.10
-fonttools==4.44.0
+fonttools==4.45.0
icalendar==5.0.11
kiwisolver==1.4.5
lxml==4.9.3
-matplotlib==3.8.1
+matplotlib==3.8.2
numpy==1.24.4
packaging==23.2
Pillow==10.1.0
@@ -14,14 +14,15 @@ pyparsing==3.1.1
PySocks==1.7.1
python-dateutil==2.8.2
pytz==2023.3.post1
-recurring-ical-events==2.1.0
+recurring-ical-events==2.1.1
requests==2.31.0
sgmllib3k==1.0.0
six==1.16.0
todoist-api-python==2.1.3
typing_extensions==4.8.0
-urllib3==2.0.7
+urllib3==2.1.0
python-dotenv==1.0.0
-setuptools==68.2.2
+setuptools==69.0.1
html2text==2020.1.16
yfinance==0.2.32
+htmlwebshot~=0.1.2
\ No newline at end of file
diff --git a/tests/test_inkycal_webshot.py b/tests/test_inkycal_webshot.py
new file mode 100755
index 0000000..84c665d
--- /dev/null
+++ b/tests/test_inkycal_webshot.py
@@ -0,0 +1,65 @@
+"""
+Test Inkycal Webshot Module
+"""
+
+import logging
+import unittest
+
+from inkycal.modules import Webshot
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
+tests = [
+ {
+ "position": 1,
+ "name": "Webshot",
+ "config": {
+ "size": [400, 100],
+ "url": "https://google.com",
+ "palette": "bwr",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "Webshot",
+ "config": {
+ "size": [400, 200],
+ "url": "https://google.com",
+ "palette": "bwy",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "Webshot",
+ "config": {
+ "size": [400, 300],
+ "url": "https://google.com",
+ "palette": "bw",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "Webshot",
+ "config": {
+ "size": [400, 400],
+ "url": "https://google.com",
+ "palette": "bwr",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ }
+]
+
+
+class TestWebshot(unittest.TestCase):
+
+ def test_generate_image(self):
+ for test in tests:
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ module = Webshot(test)
+ module.generate_image()
+ logger.info('OK')
+
From a1eb8aeaae74937d76a53e777ebb9e3d8a0faf66 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 22 Nov 2023 12:35:40 +0100
Subject: [PATCH 55/67] Create feature_suggestion.md
---
.github/ISSUE_TEMPLATE/feature_suggestion.md | 21 ++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/feature_suggestion.md
diff --git a/.github/ISSUE_TEMPLATE/feature_suggestion.md b/.github/ISSUE_TEMPLATE/feature_suggestion.md
new file mode 100644
index 0000000..4af77a1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_suggestion.md
@@ -0,0 +1,21 @@
+### Feature Request
+
+**Describe the problem you are facing or the enhancement you would like to see**
+
+A clear and concise description of what the problem is or the enhancement you are suggesting.
+
+**Is your feature request related to a problem? Please describe.**
+
+If your feature request is related to a problem you're facing, provide details about the problem.
+
+**Describe the solution you'd like**
+
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+
+Add any other context, screenshots, or examples that can help in understanding the feature request better.
From 1e95a2e4c474504c1af64551e607a8912f6e0038 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 22 Nov 2023 12:38:05 +0100
Subject: [PATCH 56/67] Update issue templates
---
.github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..e1b95fa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: "[FEATURE]: "
+labels: ''
+assignees: aceisace
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
From dae4ce31b2cef311f11d7dbfa9d3f52dd7021013 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 22 Nov 2023 12:38:26 +0100
Subject: [PATCH 57/67] Delete
---
.github/ISSUE_TEMPLATE/feature_suggestion.md | 21 --------------------
1 file changed, 21 deletions(-)
delete mode 100644 .github/ISSUE_TEMPLATE/feature_suggestion.md
diff --git a/.github/ISSUE_TEMPLATE/feature_suggestion.md b/.github/ISSUE_TEMPLATE/feature_suggestion.md
deleted file mode 100644
index 4af77a1..0000000
--- a/.github/ISSUE_TEMPLATE/feature_suggestion.md
+++ /dev/null
@@ -1,21 +0,0 @@
-### Feature Request
-
-**Describe the problem you are facing or the enhancement you would like to see**
-
-A clear and concise description of what the problem is or the enhancement you are suggesting.
-
-**Is your feature request related to a problem? Please describe.**
-
-If your feature request is related to a problem you're facing, provide details about the problem.
-
-**Describe the solution you'd like**
-
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-
-Add any other context, screenshots, or examples that can help in understanding the feature request better.
From f4d08c64a23c00e80e81df8158ffb1d6b65de368 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 22 Nov 2023 12:45:07 +0100
Subject: [PATCH 58/67] test async
---
inky_run.py | 6 +-
inkycal/display/display.py | 24 ++--
inkycal/main.py | 221 ++++---------------------------------
3 files changed, 34 insertions(+), 217 deletions(-)
diff --git a/inky_run.py b/inky_run.py
index 50cf8fa..e2fff6a 100644
--- a/inky_run.py
+++ b/inky_run.py
@@ -1,7 +1,7 @@
-#!python3
-from inkycal import Inkycal # Import Inkycal
+import asyncio
+from inkycal import Inkycal
inky = Inkycal(render=True) # Initialise Inkycal
# If your settings.json file is not in /boot, use the full path: inky = Inkycal('path/to/settings.json', render=True)
inky.test() # test if Inkycal can be run correctly, running this will show a bit of info for each module
-inky.run() # If there were no issues, you can run Inkycal nonstop
+asyncio.run(inky.run()) # If there were no issues, you can run Inkycal nonstop
diff --git a/inkycal/display/display.py b/inkycal/display/display.py
index e24b355..51652a2 100644
--- a/inkycal/display/display.py
+++ b/inkycal/display/display.py
@@ -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:
diff --git a/inkycal/main.py b/inkycal/main.py
index 08ed6c5..2b83dc8 100644
--- a/inkycal/main.py
+++ b/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.3'
+ # 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].strip().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:
@@ -121,7 +119,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 +166,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
@@ -259,12 +257,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
@@ -327,7 +325,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 +363,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 +534,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 +545,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')
From f1bcec9074e7a90caba01ca5a46d27532490673e Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 22 Nov 2023 13:26:22 +0100
Subject: [PATCH 59/67] fix certifi
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 2dbb567..0b6c540 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
arrow==1.3.0
-certifi==2023.11.17
+certifi==2023.7.22
cycler==0.12.1
feedparser==6.0.10
fonttools==4.45.0
From 03fa62cdc20c94fd66838ca7edae6a91dabd3562 Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 22 Nov 2023 16:34:28 +0100
Subject: [PATCH 60/67] fix release not found
---
inkycal/main.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/inkycal/main.py b/inkycal/main.py
index 2b83dc8..6c6f4f7 100644
--- a/inkycal/main.py
+++ b/inkycal/main.py
@@ -72,8 +72,8 @@ class Inkycal:
# 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].strip().replace("'", "")
+ if line.startswith('__version__'):
+ self._release = line.split("=")[-1].replace("'", "").replace('"', "").replace(" ", "")
break
self.render = render
From b44177130235d10c1f952298539c76b24fde309e Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 22 Nov 2023 16:49:55 +0100
Subject: [PATCH 61/67] format readme
---
README.md | 144 ++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 108 insertions(+), 36 deletions(-)
diff --git a/README.md b/README.md
index fdb8718..d2e91ff 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# Welcome to inkycal v2.0.3!
+
@@ -15,14 +16,25 @@
-Inkycal is a software written in python for selected E-Paper displays. It converts these displays into useful information dashboards. It's open-source, free for personal use, fully modular and user-friendly. Despite all this, Inkycal can run well even on the Raspberry Pi Zero. Oh, and it's open for third-party modules! Hooray!
+Inkycal is a software written in python for selected E-Paper displays. It converts these displays into useful
+information dashboards. It's open-source, free for personal use, fully modular and user-friendly. Despite all this,
+Inkycal can run well even on the Raspberry Pi Zero. Oh, and it's open for third-party modules! Hooray!
## ⚠️ Warning: long installation time expected!
-Starting october 2023, Raspberry Pi OS is now based on Debian bookworm and uses python 3.11 instead of 3.9 as the default version. Inkycal has been updated to work with python3.11, but the installation of numpy can take a very long time, in some cases even hours. If you do not want to wait this long to install Inkycal, you can also get a ready-to-flash version of Inkycal called InkycalOS-Lite with everything pre-installed for you by sponsoring via [Github Sponsors](https://github.com/sponsors/aceisace). This helps keep up maintenance costs, implement new features and fixing bugs. Please choose the one-time sponsor option and select the one with the plug-and-play version of Inkycal. Then, send your email-address to which InkycalOS-Lite should be sent.
+Starting october 2023, Raspberry Pi OS is now based on Debian bookworm and uses python 3.11 instead of 3.9 as the
+default version. Inkycal has been updated to work with python3.11, but the installation of numpy can take a very long
+time, in some cases even hours. If you do not want to wait this long to install Inkycal, you can also get a
+ready-to-flash version of Inkycal called InkycalOS-Lite with everything pre-installed for you by sponsoring
+via [Github Sponsors](https://github.com/sponsors/aceisace). This helps keep up maintenance costs, implement new
+features and fixing bugs. Please choose the one-time sponsor option and select the one with the plug-and-play version of
+Inkycal. Then, send your email-address to which InkycalOS-Lite should be sent.
## Main features
-Inkycal is fully modular, you can mix and match any modules you like and configure them on the web-ui. For now, these following built-in modules are supported:
+
+Inkycal is fully modular, you can mix and match any modules you like and configure them on the web-ui. For now, these
+following built-in modules are supported:
+
* Calendar - Monthly Calendar with option to sync events from iCalendars, e.g. Google.
* Agenda - Agenda showing upcoming events from given iCalendar URLs.
* Image - Display an Image from URL or local file path.
@@ -33,20 +45,32 @@ Inkycal is fully modular, you can mix and match any modules you like and configu
* Todoist - Synchronise with Todoist app or website to show todos.
* iCanHazDad - Display a random joke from [iCanHazDad.com](iCanhazdad.com).
-
## Quickstart
+
Watch the one-minute video on getting started with Inkycal:
[](https://www.youtube.com/watch?v=IiIv_nWE5KI)
## Hardware guide
-Before you can start, please ensure you have one of the supported displays and of the supported Raspberry Pi: `|4|3A|3B|3B+|2B|0W|0WH|02W|`. We personally recommend the Raspberry Pi Zero W as this is relatively cheaper, uses less power and is perfect to fit in a small photo frame once you have assembled everything.
-**Serial** displays are usually cheaper, but slower. Their main advantage is ease of use, like being able to communicate via SPI. A single update will cause flickering (fully normal on e-paper displays) ranging from a few seconds to half an minute. We recommend these for users who want to get started quickly and for more compact setups, e.g. fitting inside a photo frame. The resolution of these displays ranges from low to medium. Usually, these displays support 2-3 colours, but no colours in between, e.g. fully black, fully red/yellow and fully-white.
+Before you can start, please ensure you have one of the supported displays and of the supported Raspberry
+Pi: `|4|3A|3B|3B+|2B|0W|0WH|02W|`. We personally recommend the Raspberry Pi Zero W as this is relatively cheaper, uses
+less power and is perfect to fit in a small photo frame once you have assembled everything.
-**Parallel** displays on the other hand do not understand SPI and require their own dedicated driver boards individually configured for these displays. Flickering also takes place here, but an update only takes about one to a few seconds. The resolution is much better than serial e-paper displays, but the cost is also higher. These also have 16 different grayscale levels, which does not compare to the 256 grayscales of LCDs, but far better than serial displays.
+**Serial** displays are usually cheaper, but slower. Their main advantage is ease of use, like being able to communicate
+via SPI. A single update will cause flickering (fully normal on e-paper displays) ranging from a few seconds to half an
+minute. We recommend these for users who want to get started quickly and for more compact setups, e.g. fitting inside a
+photo frame. The resolution of these displays ranges from low to medium. Usually, these displays support 2-3 colours,
+but no colours in between, e.g. fully black, fully red/yellow and fully-white.
-**❗️Important note: e-paper displays cannot be simply connected to the Raspberry Pi, but require a driver board. The links below may or may not contain the required driver board. Please ensure you get the correct driver board for the display!**
+**Parallel** displays on the other hand do not understand SPI and require their own dedicated driver boards individually
+configured for these displays. Flickering also takes place here, but an update only takes about one to a few seconds.
+The resolution is much better than serial e-paper displays, but the cost is also higher. These also have 16 different
+grayscale levels, which does not compare to the 256 grayscales of LCDs, but far better than serial displays.
+
+**❗️Important note: e-paper displays cannot be simply connected to the Raspberry Pi, but require a driver board. The
+links below may or may not contain the required driver board. Please ensure you get the correct driver board for the
+display!**
| type | vendor | affiliate links to product |
|-------------------------------------------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -64,28 +88,36 @@ Before you can start, please ensure you have one of the supported displays and o
| Raspberry Pi Zero W | Raspberry Pi | Raspberry Pi Zero W |
| MicroSD card | Sandisk | MicroSD card (8GB) |
-
## Configuring the Raspberry Pi
-Flash Raspberry Pi OS on your microSD card (min. 4GB) with [Raspberry Pi Imager](https://rptl.io/imager). Use the following settings:
-| option | value |
-|:---------------------------|:----------------------------:|
-| hostname | inkycal |
-| enable ssh | yes |
-| set username and password | yes |
-| username | a username you like |
-| password | a password you can remember |
-| set Wi-Fi | yes |
-| Wi-Fi SSID | your Wi-Fi name |
-| Wi-Fi password | your Wi-Fi password |
+Flash Raspberry Pi OS on your microSD card (min. 4GB) with [Raspberry Pi Imager](https://rptl.io/imager). Use the
+following settings:
+| option | value |
+|:--------------------------|:---------------------------:|
+| hostname | inkycal |
+| enable ssh | yes |
+| set username and password | yes |
+| username | a username you like |
+| password | a password you can remember |
+| set Wi-Fi | yes |
+| Wi-Fi SSID | your Wi-Fi name |
+| Wi-Fi password | your Wi-Fi password |
+| set timezone | your local timezone |
-1. Create and download `settings.json` file for Inkycal from the [WEB-UI](https://aceisace.eu.pythonanywhere.com/inkycal-config-v2-0-0). Add the modules you want with the add module button.
-2. Copy the `settings.json` to the flashed microSD card in the `/boot` folder of microSD card. On Windows, this is the only visible directory on the SD card. On Linux, copy these files to `/boot` of the microSD card.
+1. Create and download `settings.json` file for Inkycal from
+ the [WEB-UI](https://aceisace.eu.pythonanywhere.com/inkycal-config-v2-0-0). Add the modules you want with the add
+ module button.
+2. Copy the `settings.json` to the flashed microSD card in the `/boot` folder of microSD card. On Windows, this is the
+ only visible directory on the SD card. On Linux, copy these files to `/boot` of the microSD card.
3. Eject the microSD card from your computer now, insert it in the Raspberry Pi and power the Raspberry Pi.
-4. Once the green LED has stopped blinking after ~3 minutes, you can connect to your Raspberry Pi via SSH using a SSH Client. We suggest [Termius](https://termius.com/download/windows)
-on your smartphone. Use the address: `inkycal.local` with the username and password you set earlier. For more detailed instructions, check out the page from the [Raspberry Pi website](https://www.raspberrypi.org/documentation/remote-access/ssh/)
+4. Once the green LED has stopped blinking after ~3 minutes, you can connect to your Raspberry Pi via SSH using a SSH
+ Client. We suggest [Termius](https://termius.com/download/windows)
+ on your smartphone. Use the address: `inkycal.local` with the username and password you set earlier. For more
+ detailed instructions, check out the page from
+ the [Raspberry Pi website](https://www.raspberrypi.org/documentation/remote-access/ssh/)
5. After connecting via SSH, run the following commands, line by line:
+
```bash
sudo raspi-config --expand-rootfs
sudo sed -i s/#dtparam=spi=on/dtparam=spi=on/ /boot/config.txt
@@ -105,17 +137,32 @@ sudo sed -i -E '/^CONF_SWAPSIZE=/s/=.*/=256/' /etc/dphys-swapfile
sudo dphys-swapfile setup
sudo dphys-swapfile swapon
```
-These commands expand the filesystem, enable SPI and set up the correct timezone on the Raspberry Pi. When running the last command, please select the continent you live in, press enter and then select the capital of the country you live in. Lastly, press enter.
+
+These commands expand the filesystem, enable SPI and set up the correct timezone on the Raspberry Pi. When running the
+last command, please select the continent you live in, press enter and then select the capital of the country you live
+in. Lastly, press enter.
+
7. Follow the steps in `Installation` (see below) on how to install Inkycal.
## Installing Inkycal
-⚠️ Please note that although the developers try to keep the installation as simple as possible, the full installation can sometimes take hours on the Raspberry Pi Zero W and is not guaranteed to go smoothly each time. This is because installing dependencies on the zero w takes a long time and is prone to copy-paste-, permission- and configuration errors.
-ℹ️ **Looking for a shortcut to safe a few hours?** We know about this problem and have spent a signifcant amount of time to prepare a pre-configured image with the latest version of Inkycal for the Raspberry Pi Zero. It comes with the latest version of Inkycal, is fully tested and uses the Raspberry Pi OS Lite as it's base image. You only need to copy your settings.json file, we already took care of the rest, including auto-start at boot, enabling spi and installing all dependencies in advance. Pretty neat right? Check the [sponsor button](https://github.com/sponsors/aceisace) at the very top of the repo to get access to Inkycal-OS-Lite. This will help keep this project growing and cover the ongoing expenses too! Win-win for everyone! 🎊
+⚠️ Please note that although the developers try to keep the installation as simple as possible, the full installation
+can sometimes take hours on the Raspberry Pi Zero W and is not guaranteed to go smoothly each time. This is because
+installing dependencies on the zero w takes a long time and is prone to copy-paste-, permission- and configuration
+errors.
+ℹ️ **Looking for a shortcut to safe a few hours?** We know about this problem and have spent a signifcant amount of time
+to prepare a pre-configured image with the latest version of Inkycal for the Raspberry Pi Zero. It comes with the latest
+version of Inkycal, is fully tested and uses the Raspberry Pi OS Lite as it's base image. You only need to copy your
+settings.json file, we already took care of the rest, including auto-start at boot, enabling spi and installing all
+dependencies in advance. Pretty neat right? Check the [sponsor button](https://github.com/sponsors/aceisace) at the very
+top of the repo to get access to Inkycal-OS-Lite. This will help keep this project growing and cover the ongoing
+expenses too! Win-win for everyone! 🎊
### Manual installation
+
Run the following steps to install Inkycal. Do **not** use sudo for this, except where explicitly specified.
+
```bash
# the next line is for the Raspberry Pi only
sudo apt-get install zlib1g libjpeg-dev libatlas-base-dev rustc libopenjp2-7 python3-dev scons libssl-dev python3-venv python3-pip git libfreetype6-dev wkhtmltopdf
@@ -133,7 +180,9 @@ pip install RPi.GPIO==0.7.1 spidev==3.5
```
## Running Inkycal
+
To run Inkycal, type in the following command in the terminal:
+
```bash
cd $HOME/Inkycal
source venv/bin/activate
@@ -141,18 +190,23 @@ python3 inky_run.py
```
## Running on each boot
+
To make inkycal run on each boot automatically, you can use crontab. Do not use sudo for this
+
```bash
(crontab -l ; echo "@reboot sleep 60 && cd $HOME/Inkycal && venv/bin/python inky_run.py &")| crontab -
```
## Updating Inkycal
+
To update Inkycal to the latest version, navigate to the Inkycal folder, then run:
+
```bash
git pull
```
+
Yep. It's actually that simple!
-But, if you have made changes to Inkycal, those will be overwritten.
+But, if you have made changes to Inkycal, those will be overwritten.
If that is the case, backup your modified files somewhere else if you need them. Then run:
```bash
@@ -161,51 +215,69 @@ git pull
```
## Uninstalling Inkycal
+
We'll miss you, but we don't want to make it hard for you to leave.
Just delete the Inkycal folder, and you're good to go!
Additionally, if you want to reset your crontab file, which runs inkycal at boot, run:
+
```bash
crontab -r
```
## Modifying Inkycal
-Inkycal now runs in a virtual environment to support more devices than just the Raspberry Pi. Therefore, to make changes to Inkycal, navigate to Inkycal, then run:
+
+Inkycal now runs in a virtual environment to support more devices than just the Raspberry Pi. Therefore, to make changes
+to Inkycal, navigate to Inkycal, then run:
+
```bash
cd $HOME/Inkycal && source venv/bin/activate
```
+
Then modify the files as needed and experiment with Inkycal.
To deactivate the virtual environment, simply run:
+
```bash
deactivate
```
## 3D printed frames
-With your setup being complete at this stage, you may want to 3d-print a case. The following files were shared by our friendly community:
+
+With your setup being complete at this stage, you may want to 3d-print a case. The following files were shared by our
+friendly community:
[3D-printable case](https://github.com/aceinnolab/Inkycal/wiki/3D-printable-files)
## Contributing
-All sorts of contributions are most welcome and appreciated. To start contributing, please follow the [Contribution Guidelines](https://github.com/aceisace/Inkycal/blob/main/.github/CONTRIBUTING.md)
-The average response time for issues, PRs and emails is usually 24 hours. In some cases, it might be longer. If you want to have some faster responses, please use Discord (link below)
+All sorts of contributions are most welcome and appreciated. To start contributing, please follow
+the [Contribution Guidelines](https://github.com/aceisace/Inkycal/blob/main/.github/CONTRIBUTING.md)
+The average response time for issues, PRs and emails is usually 24 hours. In some cases, it might be longer. If you want
+to have some faster responses, please use Discord (link below)
**P.S:** Don't forget to star and/or watch the repo. For those who have done so already, thank you very much!
## Join us on Discord!
-We're happy to help, to beginners and developers alike. In fact, you are more likely to get faster support on Discord than on Github.
+
+We're happy to help, to beginners and developers alike. In fact, you are more likely to get faster support on Discord
+than on Github.
## Sponsoring
-Inkycal relies on sponsors to keep up maintainance, development and bug-fixing. Please consider sponsoring Inkycal via the sponsor button if you are happy with Inkycal.
-We now offer perks depending on the amount contributed for sponsoring, ranging from pre-configured OS images for plug-and-play to development of user-suggested modules. Check out the sponsor page to find out more.
-If you have been a previous sponsor, please let us know on our Dicord server or by sending an email. We'll send you the perks after confirming 💯
+Inkycal relies on sponsors to keep up maintainance, development and bug-fixing. Please consider sponsoring Inkycal via
+the sponsor button if you are happy with Inkycal.
+
+We now offer perks depending on the amount contributed for sponsoring, ranging from pre-configured OS images for
+plug-and-play to development of user-suggested modules. Check out the sponsor page to find out more.
+If you have been a previous sponsor, please let us know on our Dicord server or by sending an email. We'll send you the
+perks after confirming 💯
## As featured on
+
* [makeuseof - fantastic projects using an eink display](http://makeuseof.com/fantastic-projects-using-an-e-ink-display/)
* [magpi.de](https://www.magpi.de/news/maginkcal-ein-kalender-mit-epaper-display-und-raspberry-pi)
* [reddit - Inkycal](https://www.reddit.com/r/InkyCal/)
From 032b674af963e4afaed95b593112e877aff7b2ed Mon Sep 17 00:00:00 2001
From: Ace
Date: Wed, 22 Nov 2023 19:02:11 +0100
Subject: [PATCH 62/67] add xkcd module
---
inkycal/__init__.py | 4 +-
inkycal/modules/__init__.py | 1 +
inkycal/modules/inkycal_xkcd.py | 202 ++++++++++++++++++++++++++++++++
requirements.txt | 3 +-
tests/test_inkycal_xkcd.py | 72 ++++++++++++
5 files changed, 280 insertions(+), 2 deletions(-)
create mode 100644 inkycal/modules/inkycal_xkcd.py
create mode 100644 tests/test_inkycal_xkcd.py
diff --git a/inkycal/__init__.py b/inkycal/__init__.py
index f642561..1ae709a 100644
--- a/inkycal/__init__.py
+++ b/inkycal/__init__.py
@@ -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
diff --git a/inkycal/modules/__init__.py b/inkycal/modules/__init__.py
index 6a23eac..3c0d809 100755
--- a/inkycal/modules/__init__.py
+++ b/inkycal/modules/__init__.py
@@ -9,3 +9,4 @@ 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
diff --git a/inkycal/modules/inkycal_xkcd.py b/inkycal/modules/inkycal_xkcd.py
new file mode 100644
index 0000000..f486cca
--- /dev/null
+++ b/inkycal/modules/inkycal_xkcd.py
@@ -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
diff --git a/requirements.txt b/requirements.txt
index 0b6c540..6e5aa6e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -25,4 +25,5 @@ python-dotenv==1.0.0
setuptools==69.0.1
html2text==2020.1.16
yfinance==0.2.32
-htmlwebshot~=0.1.2
\ No newline at end of file
+htmlwebshot~=0.1.2
+xkcd==2.4.2
\ No newline at end of file
diff --git a/tests/test_inkycal_xkcd.py b/tests/test_inkycal_xkcd.py
new file mode 100644
index 0000000..8256cf4
--- /dev/null
+++ b/tests/test_inkycal_xkcd.py
@@ -0,0 +1,72 @@
+"""
+Test Inkycal XKCD Module
+"""
+
+import logging
+import unittest
+
+from inkycal.modules.inkycal_xkcd import Xkcd
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
+tests = [
+ {
+ "position": 1,
+ "name": "XKCD",
+ "config": {
+ "size": [400, 300],
+ "mode": "latest",
+ "palette": "bwr",
+ "alt": "no",
+ "filter": "yes",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "XKCD",
+ "config": {
+ "size": [400, 300],
+ "mode": "random",
+ "palette": "bw",
+ "alt": "no",
+ "filter": "no",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "XKCD",
+ "config": {
+ "size": [400, 400],
+ "mode": "latest",
+ "palette": "bwy",
+ "alt": "no",
+ "filter": "yes",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ },
+ {
+ "position": 1,
+ "name": "XKCD",
+ "config": {
+ "size": [400, 500],
+ "mode": "random",
+ "palette": "bwr",
+ "alt": "yes",
+ "filter": "no",
+ "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
+ }
+ }
+]
+
+
+class TestXkcd(unittest.TestCase):
+
+ def test_generate_image(self):
+ for test in tests:
+ logger.info(f'test {tests.index(test) + 1} generating image..')
+ xkcd = Xkcd(test)
+ xkcd.generate_image()
+ logger.info('OK')
From 145da4de1ca1a43d9e2fd8fd180a6880b4f1870d Mon Sep 17 00:00:00 2001
From: mygrexit
Date: Thu, 23 Nov 2023 22:08:33 +0100
Subject: [PATCH 63/67] Revert dotted and dashed lines
Removing the approach for dotted and dashed lines since it's a dead end.
---
inkycal/custom/functions.py | 97 +++++++++++++------------------------
1 file changed, 33 insertions(+), 64 deletions(-)
diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py
index debe21e..bb0e9fc 100644
--- a/inkycal/custom/functions.py
+++ b/inkycal/custom/functions.py
@@ -275,91 +275,60 @@ def internet_available():
return False
-from PIL import Image, ImageDraw
-
-
-def draw_dotted_line(draw, start, end, colour, thickness):
- """Draws a dotted line between start and end points using dots."""
- delta_x = end[0] - start[0]
- delta_y = end[1] - start[1]
- distance = ((delta_x ** 2 + delta_y ** 2) ** 0.5)
- dot_spacing = 6 # Distance between dots
-
- for i in range(0, int(distance / dot_spacing), 1):
- dot_position = (start[0] + (i * dot_spacing * delta_x / distance),
- start[1] + (i * dot_spacing * delta_y / distance))
- # Drawing a circle at each dot position to create a dotted effect
- draw.ellipse([(dot_position[0] - thickness, dot_position[1] - thickness),
- (dot_position[0] + thickness, dot_position[1] + thickness)],
- fill=colour)
-
-
-def draw_dashed_line(draw, start, end, colour, thickness):
- """Draws a dashed line between start and end points."""
- delta_x = end[0] - start[0]
- delta_y = end[1] - start[1]
- distance = ((delta_x ** 2 + delta_y ** 2) ** 0.5)
- step_size = 10
- gap_size = 5
-
- for i in range(0, int(distance / (step_size + gap_size)), 1):
- segment_start = (start[0] + (i * (step_size + gap_size) * delta_x / distance),
- start[1] + (i * (step_size + gap_size) * delta_y / distance))
- segment_end = (segment_start[0] + (step_size * delta_x / distance),
- segment_start[1] + (step_size * delta_y / distance))
- draw.line((segment_start, segment_end), fill=colour, width=thickness)
-
-
-def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1), style='solid'):
- """
- Draws a border at given coordinates with specified styles (solid, dotted, dashed).
+def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)):
+ """Draws a border at given coordinates.
Args:
- - image: Image on which the border should be drawn.
- - xy: Tuple for the top-left corner of the border.
- - size: Size of the border as a tuple (width, height).
- - radius: Radius of the corners.
- - thickness: Thickness of the border in pixels.
- - shrinkage: Tuple for width and height shrinkage percentages.
- - style: Style of the border ('solid', 'dotted', 'dashed').
+ - image: The image on which the border should be drawn (usually im_black or
+ im_colour.
+
+ - xy: Tuple representing the top-left corner of the border e.g. (32, 100)
+ where 32 is the x co-ordinate and 100 is the y-coordinate.
+
+ - size: Size of the border as a tuple -> (width, height).
+
+ - radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners.
+
+ - thickness: Thickness of the border in pixels.
+
+ - shrinkage: A tuple containing decimals presenting a percentage of shrinking
+ -> (width_shrink_percentage, height_shrink_percentage).
+ e.g. (0.1, 0.2) ~ shrinks the width of border by 10%, shrinks height of
+ border by 20%
"""
colour = 'black'
+
+ # size from function paramter
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
a, b = (width - diameter), (height - diameter)
+ # Set coordinates for staright 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)
p7, p8 = (x, p4[1]), (x, p3[1])
-
- draw = ImageDraw.Draw(image)
-
- # Choose the appropriate line drawing function based on style
- if style == 'solid':
- line_drawer = draw.line
- elif style == 'dotted':
- line_drawer = lambda coords, fill, width: draw_dotted_line(draw, coords[0], coords[1], fill, width)
- elif style == 'dashed':
- line_drawer = lambda coords, fill, width: draw_dashed_line(draw, coords[0], coords[1], fill, width)
- else:
- raise ValueError(f"Unknown style: {style}")
-
- # Draw lines according to the chosen style
- line_drawer((p1, p2), fill=colour, width=thickness)
- line_drawer((p3, p4), fill=colour, width=thickness)
- line_drawer((p5, p6), fill=colour, width=thickness)
- line_drawer((p7, p8), fill=colour, width=thickness)
-
if radius != 0:
+ # Set coordinates for arcs
c1, c2 = (x, y), (x + diameter, y + diameter)
c3, c4 = ((x + width) - diameter, y), (x + width, y + diameter)
c5, c6 = ((x + width) - diameter, (y + height) - diameter), (x + width, y + height)
c7, c8 = (x, (y + height) - diameter), (x + diameter, y + height)
+ # Draw lines and arcs, creating a square with round corners
+ draw = ImageDraw.Draw(image)
+ draw.line((p1, p2), fill=colour, width=thickness)
+ draw.line((p3, p4), fill=colour, width=thickness)
+ draw.line((p5, p6), fill=colour, width=thickness)
+ draw.line((p7, p8), fill=colour, width=thickness)
+
+ if radius != 0:
draw.arc((c1, c2), 180, 270, fill=colour, width=thickness)
draw.arc((c3, c4), 270, 360, fill=colour, width=thickness)
draw.arc((c5, c6), 0, 90, fill=colour, width=thickness)
From b08cf00661b858e5826d37e7aea3c1ed84b031bf Mon Sep 17 00:00:00 2001
From: Ace
Date: Fri, 24 Nov 2023 00:43:57 +0100
Subject: [PATCH 64/67] downgrade matplotlib due to an issue with numpy during
installation
---
requirements.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 6e5aa6e..95bfc78 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,11 +2,11 @@ arrow==1.3.0
certifi==2023.7.22
cycler==0.12.1
feedparser==6.0.10
-fonttools==4.45.0
+fonttools==4.45.1
icalendar==5.0.11
kiwisolver==1.4.5
lxml==4.9.3
-matplotlib==3.8.2
+matplotlib==3.8.0
numpy==1.24.4
packaging==23.2
Pillow==10.1.0
@@ -22,7 +22,7 @@ todoist-api-python==2.1.3
typing_extensions==4.8.0
urllib3==2.1.0
python-dotenv==1.0.0
-setuptools==69.0.1
+setuptools==69.0.2
html2text==2020.1.16
yfinance==0.2.32
htmlwebshot~=0.1.2
From d1c4b89fd4c9b7c2c0e62e962766dd3dd58dd745 Mon Sep 17 00:00:00 2001
From: Ace
Date: Fri, 24 Nov 2023 02:36:25 +0100
Subject: [PATCH 65/67] use dummy driver
---
tests/settings.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/settings.json b/tests/settings.json
index 043b9e8..49955d5 100644
--- a/tests/settings.json
+++ b/tests/settings.json
@@ -1,6 +1,6 @@
{
- "model": "epd_7_in_5_v3_colour",
+ "model": "image_file",
"update_interval": 5,
"orientation": 0,
"info_section": true,
From 60be01fbdb948ad9bca63d4d495c651c0bb860d3 Mon Sep 17 00:00:00 2001
From: Ace
Date: Fri, 24 Nov 2023 02:37:27 +0100
Subject: [PATCH 66/67] fix tests, add border feature
---
inkycal/custom/functions.py | 17 ++++++++---
inkycal/main.py | 6 ++++
inkycal/modules/ical_parser.py | 2 --
.../modules/inkycal_textfile_to_display.py | 16 +----------
tests/config.py | 4 ++-
tests/test_inkycal_textfile_to_display.py | 11 ++++----
tests/test_inkycal_webshot.py | 8 +++---
tests/test_main.py | 28 +++++++++++++++++++
8 files changed, 60 insertions(+), 32 deletions(-)
create mode 100644 tests/test_main.py
diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py
index bb0e9fc..ac1e24d 100644
--- a/inkycal/custom/functions.py
+++ b/inkycal/custom/functions.py
@@ -9,7 +9,7 @@ import time
import traceback
import requests
-from PIL import ImageFont
+from PIL import ImageFont, ImageDraw, Image
logs = logging.getLogger(__name__)
logs.setLevel(level=logging.INFO)
@@ -299,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)
@@ -333,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)
\ No newline at end of file
diff --git a/inkycal/main.py b/inkycal/main.py
index 6c6f4f7..b97e913 100644
--- a/inkycal/main.py
+++ b/inkycal/main.py
@@ -104,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
@@ -203,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!')
@@ -298,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 "
diff --git a/inkycal/modules/ical_parser.py b/inkycal/modules/ical_parser.py
index 9fd8530..3251355 100755
--- a/inkycal/modules/ical_parser.py
+++ b/inkycal/modules/ical_parser.py
@@ -1,5 +1,3 @@
-#!python3
-
"""
Inkycal iCalendar parsing module
Copyright by aceinnolab
diff --git a/inkycal/modules/inkycal_textfile_to_display.py b/inkycal/modules/inkycal_textfile_to_display.py
index 0680cdf..db28191 100644
--- a/inkycal/modules/inkycal_textfile_to_display.py
+++ b/inkycal/modules/inkycal_textfile_to_display.py
@@ -15,29 +15,15 @@ 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"]
diff --git a/tests/config.py b/tests/config.py
index e7ba725..c3d3b03 100644
--- a/tests/config.py
+++ b/tests/config.py
@@ -27,7 +27,9 @@ class Config:
# inkycal_todoist_test
TODOIST_API_KEY = get("TODOIST_API_KEY")
- TEMP_PATH = f"{basedir}/tmp"
+ TEMP_PATH = f"{basedir}/temp"
+
+ TEST_SETTINGS_PATH = f"{basedir}/settings.json"
diff --git a/tests/test_inkycal_textfile_to_display.py b/tests/test_inkycal_textfile_to_display.py
index ac5e2b6..7c99f8e 100644
--- a/tests/test_inkycal_textfile_to_display.py
+++ b/tests/test_inkycal_textfile_to_display.py
@@ -16,8 +16,6 @@ merge = Inkyimage.merge
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
-temp_path = f"{Config.TEMP_PATH}/temp.txt"
-
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.',
@@ -56,6 +54,8 @@ dummy_data = [
'Duis facilisis sapien est, a elementum lorem maximus ut.'
]
+temp_path = f"{Config.TEMP_PATH}/temp.txt"
+
tests = [
{
"position": 1,
@@ -74,7 +74,7 @@ tests = [
"name": "TextToFile",
"config": {
"size": [500, 400],
- "filepath": "https://de.wikipedia.org/wiki/Nationale_Rotkreuz-_und_Rothalbmond-Gesellschaft",
+ "filepath": "https://raw.githubusercontent.com/aceinnolab/Inkycal/main/setup.py",
"padding_x": 10,
"padding_y": 10,
"fontsize": 12,
@@ -90,9 +90,8 @@ class TestTextToDisplay(unittest.TestCase):
self.temp_path = temp_path
if not os.path.exists(Config.TEMP_PATH):
os.mkdir(Config.TEMP_PATH)
- logger.info("could not find temporary file, creating now.")
- with open(self.temp_path, encoding="utf-8", mode="w") as file:
- file.writelines(dummy_data)
+ with open(self.temp_path, encoding="utf-8", mode="w") as file:
+ file.writelines(dummy_data)
def test_generate_image(self):
for test in tests:
diff --git a/tests/test_inkycal_webshot.py b/tests/test_inkycal_webshot.py
index 84c665d..3efcb55 100755
--- a/tests/test_inkycal_webshot.py
+++ b/tests/test_inkycal_webshot.py
@@ -16,7 +16,7 @@ tests = [
"name": "Webshot",
"config": {
"size": [400, 100],
- "url": "https://google.com",
+ "url": "https://www.catsuthecat.com/blogs/comics/the-one-about-regeneration",
"palette": "bwr",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
}
@@ -26,7 +26,7 @@ tests = [
"name": "Webshot",
"config": {
"size": [400, 200],
- "url": "https://google.com",
+ "url": "https://www.catsuthecat.com/blogs/comics/the-one-about-crazy-friday-nights",
"palette": "bwy",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
}
@@ -36,7 +36,7 @@ tests = [
"name": "Webshot",
"config": {
"size": [400, 300],
- "url": "https://google.com",
+ "url": "https://www.catsuthecat.com/blogs/comics/the-one-about-teamwork",
"palette": "bw",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
}
@@ -46,7 +46,7 @@ tests = [
"name": "Webshot",
"config": {
"size": [400, 400],
- "url": "https://google.com",
+ "url": "https://www.catsuthecat.com/blogs/comics/the-one-about-addictions-1",
"palette": "bwr",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
}
diff --git a/tests/test_main.py b/tests/test_main.py
new file mode 100644
index 0000000..9cacfab
--- /dev/null
+++ b/tests/test_main.py
@@ -0,0 +1,28 @@
+"""
+Test main module
+"""
+import unittest
+
+from inkycal import Inkycal
+from tests import Config
+
+
+class TestMain(unittest.TestCase):
+
+ def setUp(self):
+ self.settings_path = Config.TEST_SETTINGS_PATH
+
+ def test_init(self):
+ inkycal = Inkycal(self.settings_path, render=False)
+ assert inkycal.settings["model"] == "image_file"
+ assert inkycal.settings["update_interval"] == 5
+ assert inkycal.settings["orientation"] == 0
+ assert inkycal.settings["info_section"] == True
+ assert inkycal.settings["info_section_height"] == 70
+ assert inkycal.settings["border_around_modules"] == True
+
+ def test_run(self):
+ inkycal = Inkycal(self.settings_path, render=False)
+ inkycal.test()
+
+
From 87e9b111f4a99a2d723711b4e84ee383d6654695 Mon Sep 17 00:00:00 2001
From: Ace
Date: Fri, 24 Nov 2023 15:46:06 +0100
Subject: [PATCH 67/67] update to new web-ui
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d2e91ff..4608ede 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ following settings:
| set timezone | your local timezone |
1. Create and download `settings.json` file for Inkycal from
- the [WEB-UI](https://aceisace.eu.pythonanywhere.com/inkycal-config-v2-0-0). Add the modules you want with the add
+ the [WEB-UI](https://aceinnolab.com/inkycal/ui). Add the modules you want with the add
module button.
2. Copy the `settings.json` to the flashed microSD card in the `/boot` folder of microSD card. On Windows, this is the
only visible directory on the SD card. On Linux, copy these files to `/boot` of the microSD card.