diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..c4f72ec --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim-bookworm as development +WORKDIR /app +RUN apt-get -y update && apt-get install -yqq dos2unix chromium chromium-driver \ + libxi6 libgconf-2-4 python3-selenium \ + tzdata git +RUN git config --global --add safe.directory /usr/src/app +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install --user virtualenv +ENV TZ=Europe/Berlin +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1b65fe2..1bf5540 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,18 +1,23 @@ // For format details, see https://aka.ms/devcontainer.json. { "name": "Inkycal-dev", - "image": "python:3.9-bullseye", + "build": { + "dockerfile": "Dockerfile", + "target": "development" + }, // 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 --upgrade pip && pip3 install --user -r requirements.txt", + "postCreateCommand": "dos2unix ./.devcontainer/postCreate.sh && chmod +x ./.devcontainer/postCreate.sh && ./.devcontainer/postCreate.sh", "customizations": { "vscode": { "extensions": [ - "ms-python.python" + "ms-python.python", + "ms-python.black-formatter", + "ms-azuretools.vscode-docker" ] } } diff --git a/.devcontainer/postCreate.sh b/.devcontainer/postCreate.sh new file mode 100644 index 0000000..6041923 --- /dev/null +++ b/.devcontainer/postCreate.sh @@ -0,0 +1,2 @@ +#!/bin/bash +source ./venv/Scripts/activate && pip3 install --upgrade pip && pip3 install --user -r requirements.txt \ No newline at end of file diff --git a/README.md b/README.md index 4608ede..8a482a2 100644 --- a/README.md +++ b/README.md @@ -1,287 +1,9 @@ -# Welcome to inkycal v2.0.3! +# Inkycal-based full screen weather display (scraper solution) -

- aceinnolab logo -

-

- featured-image -

+This fork of was used to try out a webscraper approach for a openweathermap-based full screen weather display on a 7in5 waveshare v2 colour epd. +It didn't prove particularly robust, so I've decided to not further develop it. +I'll leave it here in case someone wants to try and play around with it a little. -

- - - Version - Licence - GitHub stars - python -

+Since Selenium doesn't run on Pi Zero, I had to run the scraper part as a cron job on my Pi 4 and scp the resulting image to the Pi Zero that's connected to the e-paper display. -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. - -## 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. -* Agenda - Agenda showing upcoming events from given iCalendar URLs. -* Image - Display an Image from URL or local file path. -* Slideshow - Cycle through images in a given folder and show them on the E-Paper. -* Feeds - Synchronise RSS/ATOM feeds from your favorite providers. -* Stocks - Display stocks using Tickers from Yahoo! Finance. -* Weather - Show current weather, daily or hourly weather forecasts from openweathermap. -* 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: - -[![Inkycal quickstart](https://img.youtube.com/vi/IiIv_nWE5KI/0.jpg)](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. - -**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 | -|-------------------------------------------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 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 - -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://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. -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/) -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 -sudo dpkg-reconfigure tzdata - -# If you have the 12.48" display, these steps are also required: -wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.71.tar.gz -tar zxvf bcm2835-1.71.tar.gz -cd bcm2835-1.71/ -sudo ./configure && sudo make && sudo make check && sudo make install -wget https://project-downloads.drogon.net/wiringpi-latest.deb -sudo dpkg -i wiringpi-latest.deb - -# If you are using the Raspberry Pi Zero models, you may need to increase the swapfile size to be able to install Inkycal: -sudo dphys-swapfile swapoff -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. - -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! 🎊 - -### 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 -cd $HOME -git clone --branch main --single-branch https://github.com/aceinnolab/Inkycal -cd Inkycal -python3 -m venv venv -source venv/bin/activate -python -m pip install --upgrade pip -pip install wheel -pip install -e ./ - -# If you are running on the Raspberry Pi, please install the following too to allow rendering on the display -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 -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. -If that is the case, backup your modified files somewhere else if you need them. Then run: - -```bash -git reset --hard -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: - -```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: -[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) - -**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. - - - Inkycal chatroom Discord - - -## 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 💯 - -## 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/) -* [schuemann.it](https://schuemann.it/2019/09/11/e-ink-calendar-with-a-raspberry-pi/) -* [huernerfuerst.de](https://www.huenerfuerst.de/archives/e-ink-kalender-mit-einem-raspberry-pi-zero-teil-1-was-wird-benoetigt) -* [raspberrypi.com](https://www.raspberrypi.com/news/ashleys-top-five-projects-for-raspberry-pi-first-timers/) -* [canox.net](https://canox.net/2019/06/raspberry-pi-als-e-ink-kalender/) +For further information see the official Inkycal repo. diff --git a/inkycal/main.py b/inkycal/main.py index b97e913..078530f 100644 --- a/inkycal/main.py +++ b/inkycal/main.py @@ -522,12 +522,12 @@ class Inkycal: def _optimize_im(image, threshold=220): """Optimize the image for rendering on ePaper displays""" - buffer = numpy.array(image.convert('RGB')) - red, green = buffer[:, :, 0], buffer[:, :, 1] + #buffer = numpy.array(image.convert('RGB')) + #red, green = buffer[:, :, 0], buffer[:, :, 1] # grey->black - buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0, 0, 0] - image = Image.fromarray(buffer) + #buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0, 0, 0] + #image = Image.fromarray(buffer) return image def calibrate(self): diff --git a/inkycal/modules/inky_image.py b/inkycal/modules/inky_image.py index fb6df4e..836f259 100755 --- a/inkycal/modules/inky_image.py +++ b/inkycal/modules/inky_image.py @@ -55,7 +55,7 @@ class Inkyimage: image = Image.open(path) except FileNotFoundError: logger.error('No image file found', exc_info=True) - raise Exception('Your file could not be found. Please check the filepath') + raise Exception(f'Your file could not be found. Please check the filepath: {path}') except OSError: logger.error('Invalid Image file provided', exc_info=True) diff --git a/inkycal/modules/inkycal_openweather_scrape.py b/inkycal/modules/inkycal_openweather_scrape.py new file mode 100644 index 0000000..e6a4f74 --- /dev/null +++ b/inkycal/modules/inkycal_openweather_scrape.py @@ -0,0 +1,104 @@ +import time + +from PIL import Image +from PIL import ImageEnhance +from selenium import webdriver +from selenium.common.exceptions import ElementClickInterceptedException +from selenium.common.exceptions import TimeoutException +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait + + +def get_scraped_weatherforecast_image() -> Image: + # Set the desired viewport size (width, height) + my_width = 480 + my_height = 850 # will later be cropped + mobile_emulation = { + "deviceMetrics": {"width": my_width, "height": my_height, "pixelRatio": 1.0}, + "userAgent": "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19", + } + + # Create an instance of the webdriver with some pre-configured options + options = Options() + options.add_argument("--no-sandbox") + options.add_argument("--headless=new") + options.add_argument("--disable-dev-shm-usage") + options.add_experimental_option("mobileEmulation", mobile_emulation) + service = Service("/usr/bin/chromedriver") + driver = webdriver.Chrome(service=service, options=options) + + # Navigate to webpage + driver.get("https://openweathermap.org/city/") + + # Wait and find the Cookie Button + login_button = driver.find_element(By.XPATH, '//*[@id="stick-footer-panel"]/div/div/div/div/div/button') + # Scroll to it + driver.execute_script("return arguments[0].scrollIntoView();", login_button) + # Click the button + button_clicked = False + while button_clicked == False: + try: + login_button.click() + button_clicked = True + print("All cookies successfully accepted!") + except ElementClickInterceptedException: + print("Couldn't click the cookie button, retrying...") + time.sleep(10) + + # hacky wait statement for all the page elements to load + page_loaded = False + while page_loaded == False: + try: + WebDriverWait(driver, timeout=45).until( + lambda d: d.find_element(By.XPATH, '//*[@id="weather-widget"]/div[2]/div[1]/div[1]/div[1]/h2').text + != "" + ) + page_loaded = True + except TimeoutException: + print("Couldn't get the page to load, retrying...") + time.sleep(60) + + # Scroll to the start of the forecast + forecast_element = driver.find_element(By.XPATH, '//*[@id="weather-widget"]/div[2]/div[1]/div[1]/div[1]') + driver.execute_script("return arguments[0].scrollIntoView();", forecast_element) + + # remove the map + map_element = driver.find_element(By.XPATH, '//*[@id="weather-widget"]/div[2]/div[1]/div[2]') + driver.execute_script("arguments[0].remove();", map_element) + + # optional: remove the hourly forecast + # map_element = driver.find_element(By.XPATH, '//*[@id="weather-widget"]/div[2]/div[2]/div[1]') + # driver.execute_script("arguments[0].remove();", map_element) + + # zoom in a little + driver.execute_script("document.body.style.zoom='110%'") + + html_element = driver.find_element(By.TAG_NAME, "html") + driver.execute_script("arguments[0].style.fontSize = '16px';", html_element) + + # Save as a screenshot + image_filename = "/tmp/openweather_scraped.png" + driver.save_screenshot(image_filename) + + # Close the WebDriver when done + driver.quit() + + # crop, resize, enhance & rotate the image for inky 7in5 v2 colour display + im = Image.open(image_filename, mode="r", formats=None) + im = im.crop((0, 100, (my_width - 50), (my_height - 50))) + im = im.resize((480, 800), resample=Image.LANCZOS) + im = ImageEnhance.Contrast(im).enhance(1.0) + im.save(image_filename) + return im, im + + +def main(): + _, _ = get_scraped_weatherforecast_image() + + +if __name__ == "__main__": + now = time.asctime() + print(f"It's {now} - running {__name__} in standalone/debug mode") + main() diff --git a/requirements.txt b/requirements.txt index 95bfc78..a21b8be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,29 +1,56 @@ -arrow==1.3.0 +appdirs==1.4.4 +arrow==1.2.3 +attrs==22.2.0 +beautifulsoup4==4.12.2 certifi==2023.7.22 -cycler==0.12.1 +charset-normalizer==3.2.0 +contourpy==1.1.1 +cycler==0.11.0 +distlib==0.3.7 +exceptiongroup==1.1.3 feedparser==6.0.10 -fonttools==4.45.1 -icalendar==5.0.11 -kiwisolver==1.4.5 -lxml==4.9.3 -matplotlib==3.8.0 -numpy==1.24.4 -packaging==23.2 -Pillow==10.1.0 -pyparsing==3.1.1 +filelock==3.12.4 +fonttools==4.40.0 +frozendict==2.3.8 +geojson==2.3.0 +h11==0.14.0 +html5lib==1.1 +icalendar==5.0.7 +idna==3.4 +importlib-resources==6.1.0 +kiwisolver==1.4.4 +lxml==4.9.2 +matplotlib==3.7.1 +multitasking==0.0.11 +numpy==1.25.0 +outcome==1.2.0 +packaging==23.1 +pandas==2.0.2 +Pillow==9.5.0 +platformdirs==3.10.0 +pyowm==3.3.0 +pyparsing==3.1.0 PySocks==1.7.1 python-dateutil==2.8.2 -pytz==2023.3.post1 -recurring-ical-events==2.1.1 +python-dotenv==1.0.0 +pytz==2023.3 +recurring-ical-events==2.0.2 requests==2.31.0 +selenium==4.10.0 sgmllib3k==1.0.0 six==1.16.0 -todoist-api-python==2.1.3 -typing_extensions==4.8.0 -urllib3==2.1.0 -python-dotenv==1.0.0 -setuptools==69.0.2 -html2text==2020.1.16 -yfinance==0.2.32 -htmlwebshot~=0.1.2 -xkcd==2.4.2 \ No newline at end of file +sniffio==1.3.0 +sortedcontainers==2.4.0 +soupsieve==2.5 +todoist-api-python==2.0.2 +trio==0.22.2 +trio-websocket==0.10.4 +typing_extensions==4.6.3 +tzdata==2023.3 +urllib3==2.0.3 +virtualenv==20.24.5 +webencodings==0.5.1 +wsproto==1.2.0 +x-wr-timezone==0.0.5 +yfinance==0.2.21 +zipp==3.17.0