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') +