• Stars
    star
    252
  • Rank 155,490 (Top 4 %)
  • Language
    C
  • License
    Apache License 2.0
  • Created over 1 year ago
  • Updated 12 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Battery-powered E-Ink weather display for our home.

eink-weather-display

Battery-powered E-Ink weather display for our home. The device wakes up early in the morning, fetches latest weather forecast information, updates the info to the E-Ink display, and goes back to deep sleep until tomorrow.

You can read more about the build in my blog post.

Picture of the display

Goals:

  • Easily glanceable weather forecast at the heart of our home. Ideally eliminates one more reason to pick up the phone.
  • Looks like a "real product". The housing should look professional.
  • Fully battery-powered. We didn't want a visible cable, and drilling the cable inside wall wasn't an option.
  • Always visible and doesn't light up the hallway during evening / night -> e-Ink display.
  • Primarily for our use case, but with reusability in mind. For example custom location and timezone (some tests too).

2023 UPDATE: Battery lasted roughly 4 months, when updating the screen 6 times a day. Updating the screen more times a day ended up making the UX better. The information is always recent enough. When updating only once in the morning, I sometimes ended up checking the latest weather status from a phone app later during the day.

Challenges:

  • Battery life.
    • PiJuice GitHub isses mention that the deep sleep consumtion should be ~1mA, which theoretically means 12000 hours of idle time with the 12000mAh battery. It remains to be seen how long it will actually last.
  • And due to battery constraints: low refresh speed. 1-2x per day is an interesting design challenge. How do you indicate that this data is not real time? What should we show as the day's temperature, average or maximum for the day?
  • Physical constraints by the frame. Ideally it would be flush to the wall behind.

Hardware

Hardware bought but not needed in the end:

How it works

The project has two separate parts: render and rasp.

render

Generates HTML that will be eventually rendered as PNG. The image contains the weather forecast. render is exposed via Google Cloud Function. It's the perfect tool for this type of task. The endpoint is quite rarely called and latencies don't matter that much.

  • Weather data is fetched from APIs by Finnish Meteorological Institute and Open Meteo. FMI's API had some limitations, which were covered by additional data from Meteo. For example daily weather symbols for the next 5 days.
  • HTML, CSS, and Headless Chrome are utilised to generate the PNG file. This part could be done with a lower-level approach, but using CSS for layouting is super convenient.
  • The view is a purposely dumb single HTML file, which has mock data to make development easy. The mock data will be replaced with real data using DOM ids. Not having a build tool removes a lot of unnecessary complexity.
  • All dates within the system are UTC, they are converted to local times on render. "Start of day" and "End of day" concepts are tricky.

rasp

Runs on Raspberry Pi Zero.

All code related to the hardware that will display the weather image. This part doesn't know anything about weather, it just downloads a PNG from given URL and renders it on e-Ink display.

  • Fetch PNG from given URL, render it to e-Ink display, and go back to idle. goes back to idle.
  • Consumes as little power as possible
  • Microcontroller could've been enough, but I also wanted to finish the project in a lifetime.
  • IT8951-ePaper code copied from https://github.com/waveshare/IT8951-ePaper/

Installation

Note: this is a rough guide written from memory.

Most of the software lives in Google Cloud. This off-loads a lot of processing away from the Raspberry Pi device, mostly to optimize battery-life. Deployment is done via GH actions, but the initial setup was roughly:

  • Create new GCP Project
  • Create deployment service account with Cloud Function deployment role. Set the JSON key as GCP_SERVICE_ACCOUNT_KEY secret.
  • Create another service account for Raspberry PI device. Add Cloud Logging write rights. This way the logs can be sent to GCP for debugging, because the Raspberry PI doesn't have power while sleeping.
  • Create the Google Cloud Function, with initial hello world code. To the environvment variables, add NODE_ENV=production and API_KEY=<new random key>. The API key is just there to prevent random http calls to consume GCP quota. Headless Chrome rendering seems to work well with 1GB of memory.

Raspberry PI setup

  • Download correct image from here: https://www.raspberrypi.com/software/operating-systems/b

  • Flash it to an SD card with balenaEtcher https://www.balena.io/etcher/ (or use RPIs own flasher)

  • Boot the raspberry, and do initial setup

  • sudo raspi-config

    • Setup Wifi SSID and password (System options)
    • Update locales, timezones, etc (Localisation options)
    • Enable SSH server (Interface options)
    • Enable overlayfs (Performance options) to make the FS read-only.
  • In your router, make sure to assign a static local IP address for the device

  • Install display updating code

    Download zip

    curl -H "Authorization: token <token>" -L https://api.github.com/repos/kimmobrunfeldt/eink-weather-display/zipball/main > main.zip

    or sudo apt install git and

    git clone https://<user>:<personal_access_token>@github.com/kimmobrunfeldt/eink-weather-display.git

    You can create a limited Github personal access token, which only can clone that repo. I found git to be the easiest, it was easy to just git pull any new changes.

  • sudo apt install python3-pip

  • pip install pipenv Edit: I wasn't able to get pipenv working due to pijuice being system-wide package. Ended up going with all-system-wide packages.

  • cd eink-weather-display && pip install Pillow==9.3.0 google-cloud-logging requests python-dotenv pytz Install Python deps

  • Setup env variables: cp .env.example .env and fill in the details

  • Follow installation guide from https://www.waveshare.com/wiki/10.3inch_e-Paper_HAT to get the E-Ink display working

  • After install, test that the demo software (in C) works

  • sudo apt install pijuice-base

  • Enable I2C interface

    More debugging info:

  • To allow PIJuice to turn on without a battery, go to general settings and enable "Turn on without battery" or similar option.

  • Make sure to use correct PIJuice battery profile (PJLIPO_12000 for me)

    If using pijuice_cli, remember to apply changes! It was quite hidden down below.

  • Test that the PIJuice works with battery too

  • cd rasp/IT8951 and follow install instructions (inside virtualenv if using one)

  • Pijuice setup using pijuice_cli. Remember to save the changes inside each setup screen!

    • System events
    • Wake-up alarm: every day at 04:00 UTC (6AM Helsinki time in the winter, 7AM in the summer) Edit: code will set these, edit main.py
  • After that's done, you can test the PiJuice + E-Ink display together.

  • Setup crontab. Run refresh on boot, and shutdown device if on battery.

    @reboot (cd /home/pi/eink-weather-display/rasp; ./wait-for-network.sh; python main.py;) >> /home/pi/cron.log 2>&1
    
    
    # Every minute
    * * * * * (cd /home/pi/eink-weather-display/rasp; python shutdown_if_on_battery.py;) >> /home/pi/cron.log 2>&1
    

Side note: I did all the steps until here using Raspberry PI GPIO headers. However they ended up being too tall for the frame. Instead of soldering GPIO pins to make everything fit, I checked if the IT8951 controller was possible to use via its USB interface.

And fortunately, it was!

  • cd rasp/usb-it8951/ and follow README.md instructions to get it build and working. Build it in Raspberry Pi.
    • Find which /dev/sdX your usb device is, and change all commands from main.py accordingly
  • sudo apt install imagemagick
  • Finally, edit main.py to have correct paddings. Due to the physical installation, not all pixels of the E-Ink display are visible.

Credits

Licenses

The following files are under Apache 2.0 license:

  • render/**/*
  • rasp/*.py (just top level python code, not IT8961 subdirs)

Development

Note! Since the display updates only once or twice a day, everything has been designed that in mind. The forecast always starts 9AM, and doesn't show any real observations during the day.

Developing with placeholder data

Rendering real values

Calling cloud function

The cloud function and CLI support basic image operations to offload that work from Raspberry: rotate, flip, flip, padding(Top|Right|Bottom|Left), resizeToWidth, resizeToHeight. See sharp for their docs. For example --flip with CLI or ?flip=true with CF.

LAT="60.222"
LON="24.83"
LOCATION="Espoo"
BATTERY="100"
TIMEZONE="Europe/Helsinki"

curl -vv -o weather.png \
  -H "x-api-key: $API_KEY" \
  "https://europe-west3-weather-display-367406.cloudfunctions.net/weather-display?lat=$LAT&lon=$LON&locationName=$LOCATION&batteryLevel=$BATTERY&timezone=$TIMEZONE"

Random notes

Links

All fields for fmi::forecast::harmonie::surface::point::simple

The model can return data up to 50h from now.

{
  "Pressure": 1015.7,
  "GeopHeight": 26.3,
  "Temperature": 6.4,
  "DewPoint": 4.9,
  "Humidity": 92.8,
  "WindDirection": 127,
  "WindSpeedMS": 1.97,
  "WindUMS": -1.37,
  "WindVMS": 1.37,
  "PrecipitationAmount": 0.38,
  "TotalCloudCover": 100,
  "LowCloudCover": 100,
  "MediumCloudCover": 0,
  "HighCloudCover": 58.9,
  "RadiationGlobal": 4.4,
  "RadiationGlobalAccumulation": 682913.3,
  "RadiationNetSurfaceLWAccumulation": -1537350,
  "RadiationNetSurfaceSWAccumulation": 613723.9,
  "RadiationSWAccumulation": 14.2,
  "Visibility": 7441.7,
  "WindGust": 3.6,
  "time": "2022-11-02T07:00:00.000Z",
  "location": {
    "lat": 60.222,
    "lon": 24.83
  }
}

All fields for ecmwf::forecast::surface::point::simple

The model can return data up to 10 days from now.

{
  "GeopHeight": 37.6,
  "Temperature": 5.8,
  "Pressure": 1016,
  "Humidity": 95.7,
  "WindDirection": null,
  "WindSpeedMS": null,
  "WindUMS": -1.8,
  "WindVMS": -0.1,
  "MaximumWind": null,
  "WindGust": null,
  "DewPoint": null,
  "TotalCloudCover": null,
  "WeatherSymbol3": null,
  "LowCloudCover": null,
  "MediumCloudCover": null,
  "HighCloudCover": null,
  "Precipitation1h": 0,
  "PrecipitationAmount": null,
  "RadiationGlobalAccumulation": null,
  "RadiationLWAccumulation": null,
  "RadiationNetSurfaceLWAccumulation": null,
  "RadiationNetSurfaceSWAccumulation": null,
  "RadiationDiffuseAccumulation": null,
  "LandSeaMask": null,
  "time": "2022-11-02T07:00:00.000Z",
  "location": {
    "lat": 2764063,
    "lon": 8449330.5
  }
}

All fields for fmi::observations::weather::hourly::simple

https://opendata.fmi.fi/wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id=fmi::observations::weather::hourly::simple&place=helsinki&

Observations are returned 24 hours in past.

{
  "TA_PT1H_AVG": 1.9,
  "WS_PT1H_AVG": 1.5,
  "WD_PT1H_AVG": 205,
  "PRA_PT1H_ACC": 0,
  "time": "2022-11-14T08:00:00.000Z",
  "location": {
    "lat": 60.17797,
    "lon": 24.78743
  }
}

ParameterName descriptions

variable label base_phenomenon unit stat_function agg_period
1 TA_PT1H_AVG Air temperature Temperature degC avg
2 TA_PT1H_MAX Highest temperature Temperature degC max
3 TA_PT1H_MIN Lowest temperature Temperature degC min
4 RH_PT1H_AVG Relative humidity Humidity % avg
5 WS_PT1H_AVG Wind speed Wind m/s avg
6 WS_PT1H_MAX Maximum wind speed Wind m/s max
7 WS_PT1H_MIN Minimum wind speed Wind m/s min
8 WD_PT1H_AVG Wind direction Wind deg avg
9 PRA_PT1H_ACC Precipitation amount Amount of precipitation mm acc
10 PRI_PT1H_MAX Maximum precipitation intensity Amount of precipitation mm/h max
11 PA_PT1H_AVG Air pressure Air pressure hPa avg
12 WAWA_PT1H_RANK Present weather (auto) Weather rank

Maintenance guide

SSS connection

There are two methods:

  1. Method 1: SSH while on battery. Note: the device shuts down after max ssh time (see shutdown_if_on_battery.py)
  • Run until ssh -o ConnectTimeout=2 raspzero2 ; do ; done; on laptop and move to next step.

    SSH connection needs to be active before the main.py shuts down the device.

  • Press the physical on switch in PiJuice board.

  1. Method 2: SSH while plugged on power.

    • Take the display off the wall, and plug micro usb to PiJuice usb connector.

    • Wait until on

    • ssh raspzero2

      The device won't automatically shutdown while power is connected.

*After SSH session

Use shutd (alias shutd='cd ~/eink-weather-display/rasp && python shutdown.py') to shutdown the Pi after SSH session.

This ensures that the RTC alarm is set correctly.

Tips

  • Logs are at GCP Logging Explorer

  • Power should turn on automatically when cable is connected

  • Power should keep on even on battery if any SSH session is active, unless max uptime is exceeded (safely timeout to avoid draining battery)

  • git pull is executed once a day within Raspberry Pi

  • To plot battery level and other measurements history and predicted levels, run ./battery-graph.sh && open graph.png.

    If you don't want to re-fetch data from GCP Logs on consecutive runs, use ./battery-graph.sh -l true && open graph.png and it'll use locally saved files instead.

More Repositories

1

progressbar.js

Responsive and slick progress bars
JavaScript
7,755
star
2

git-hours

Estimate time spent on a git repository
JavaScript
730
star
3

react-progressbar.js

Responsive and slick progress bars for React.
JavaScript
310
star
4

url-to-image

PhantomJS screenshotting done right
JavaScript
204
star
5

howto-everything

Collection of notes and howtos. Questions / ideas -> please open an issue :) Also check my blog:
Shell
153
star
6

football-score-detector

Detect table football score from camera image with machine vision
Python
68
star
7

dont-copy-paste-this-frontend-template

Frontend template project to get you started with frontend development
JavaScript
55
star
8

express-example

Example of good Express.js architecture with Promises and nice error handling
JavaScript
55
star
9

nap

Convenient way to request HTTP APIs
Python
42
star
10

v8-profiler-trigger

Trigger CPU profile recording or heap snapshots for node apps using keyboard shortcuts.
JavaScript
28
star
11

squint

Makes visual reviews of web app releases easy.
TypeScript
26
star
12

releasor

Command line tool to automate node module releasing to NPM and Git
JavaScript
23
star
13

fix-outline

*:focus { outline: none } done right.
JavaScript
18
star
14

spawn-default-shell

Spawn shell command with platform default shell
JavaScript
12
star
15

ppmusicbot

Telegram bot which adds all linked Spotify track links to a shared playlist
JavaScript
8
star
16

dcr

Node CLI tool to decrypt partially encrypted log streams on the fly
JavaScript
8
star
17

iphone-x

Testing webgl. Based on https://github.com/PavelDoGreat/WebGL-Fluid-Simulation/
JavaScript
7
star
18

html5-pathfinder

Visualisation of path finding with canvas
JavaScript
7
star
19

promise-retryify

Add retry functionality to any Promise based library
JavaScript
6
star
20

shary.in

Mobile pastebin for images
JavaScript
5
star
21

blog

Personal blog at kimmo.blog
TypeScript
4
star
22

egtest

Test example code blocks in documentation
Python
4
star
23

rate-limiting-s3-proxy

Pass-through S3 proxy with rate limiting.
JavaScript
4
star
24

busse-release

Web version of real time bus tracking system in Tampere
JavaScript
4
star
25

concurrently

Moved under github.com/open-cli-tools/concurrently
4
star
26

labyrinth

Online version of the Labyrinth board game. The game server runs on the host's browser and networking happens peer-to-peer.
TypeScript
4
star
27

pair-coding-with-chatgpt

I paired up with ChatGPT to code a poker game with react
HTML
3
star
28

array-sort-compare

Generic type-aware compare function for Array.prototype.sort() and sortDeep.
JavaScript
3
star
29

kauko

Kauko is remote control for computer.
Python
3
star
30

shoot

UI review for web applications
JavaScript
3
star
31

bluetooth-web-api-talk

JavaScript
3
star
32

elegant-clock

Elegant analog clock
JavaScript
3
star
33

busse-web

Public transit real time on a map.
JavaScript
3
star
34

planter-docker

Dockerfile that packages planter and plantuml into a Docker image
Shell
3
star
35

lissu-proxy

Nicer JSON API for lissu.tampere.fi
JavaScript
3
star
36

service-worker-demo

Tiny demo of Service Worker
JavaScript
2
star
37

presentic

Impress your audience
JavaScript
2
star
38

drydoc

DRY documents
Python
1
star
39

random_python_utils

collection of snippets and functions
Python
1
star
40

mips-asm-calculator

Calculator
Assembly
1
star
41

arr-uniq

Generic array uniq function which supports equals predicate function
JavaScript
1
star
42

ocr-engine

Experimental OCR engine
Python
1
star
43

wave

Experimenting with canvas and paper.js
JavaScript
1
star
44

chokidar-cli

Moved to github.com/open-cli-tools/chokidar-cli
1
star
45

arr-mutations

Calculate all mutation operations between two arrays. Supports a generic equals predicate function
JavaScript
1
star
46

text-to-speech-hubot

JavaScript
1
star
47

how-much-oss-projects-are-worth-in-cash

How much open source projects are worth in cash.
JavaScript
1
star
48

busse-api

Busse API for all cities.
JavaScript
1
star
49

entangle

Screen share using Chrome without any installations or plugins.
JavaScript
1
star
50

react-scripts-antd

Fork of react-scripts to be used with http://ant.design
JavaScript
1
star
51

mozart

Convert any given text to music
JavaScript
1
star
52

tencalendar

Incredibly fast calendar for power users
JavaScript
1
star
53

portal

Real life portal controlled with Leap Motion
JavaScript
1
star
54

lodash-merge-pollution-example

Demonstration of _.merge prototype pollution vulnerability
JavaScript
1
star