Skip to content

Latest commit

 

History

History
278 lines (172 loc) · 11.6 KB

README.md

File metadata and controls

278 lines (172 loc) · 11.6 KB

InkyStock

A tiny, hackable, Raspberry Pi Zero powered e-ink display for cryptocurrency, stocks and more. Written in Python 🐍

Works with Raspberry Pi OS (64-bit), Raspberry Pi Zero 2W, and Inky pHAT Black & White, and Black, white and colour.

Full install last tested on 27th March, 2024.

A photo showing the project in action

Getting started

The hardware is fortunately very simple, and all just slots together, no soldering, etc. Not too many parts either:

You can buy off the shelf Raspberry Pi Zero cases. The official one looks like it would work, and the InkyPHAT tutorial uses a Pibow case. If you have access to a 3D printer, this is the case I've used (pictured above).

You might notice there are Inky pHAT displays with either color or black & white. InkyStock should display correctly on either, with the trend line in color on the color displays.

Cryptocurrency data is sourced from CoinGecko, who very helpfully provide a free API.

To display regular stocks, you'll need an IEX Cloud API key. The free tier credits are enough to update the screen every 5 minutes, 24 hours a day. No credit card required. I'll accept pull requests for other providers, as long as they meet the criteria.

Install

You'll need to have SSH access to the Pi, and it will need access to the Internet. There are a variety of tutorials on doing that, here's one.

Also, it's best to start with a freshly flashed OS; should help avoid any mysterious conflicts.

Assuming you're using the default pi user on Raspbian (Buster), SSH to the pi:

Enable SPI and I2C:

sudo raspi-config nonint do_spi 0
sudo raspi-config nonint do_i2c 0

Install dependencies and app:

wget -O inkystock.zip https://github.com/duggan/inkystock/archive/main.zip
unzip inkystock.zip && mv inkystock-main inkystock
cd inkystock
sudo make deps
make install

Now you'll want to modify config.ini to suit, adding IEX Cloud API key information if you want to use it for stocks, or CoinGecko for crypto.

Note: as of February 2024, a CoinGecko Demo API key is required, which you can get for free by using the "Create Demo Account" on their pricing page.

You can perform a test run by executing ./run.sh. If it's set up correctly, you should see your screen updated within 10 seconds or so. If not, the error messages will hopefully be helpful enough to point you in the right direction.

If you see an error like the following:

RuntimeError: No EEPROM detected! You must manually initialise your Inky board.

then it may be worth trying the inky "one line installer" from the Pimoroni tutorial.

When you're happy it's working, you can install a cron job to update the screen every 5 minutes:

sudo make cron.5m

Configure

See the comments in config.ini for additional documentation of options.

Crypto

The default configuration in config.ini will show the current price of one Bitcoin in Euro:

[Main]
currency = EUR
crypto = BTC
database = sqlite:///data/inkystock.db
provider = CoinGecko

# Other config
...

Here you can change the currency or the cryptocurrency that will be tracked. History is tracked in an SQLite database.

Stocks

To configure stocks, remove or comment out the crypto property and replace with stock, and update the provider details:

[Main]
currency = EUR
# crypto = BTC
stock = AAPL
database = sqlite:///data/inkystock.db
# provider = CoinGecko
provider = IEX

[IEX]
token = YOUR_IEX_TOKEN

# Other config
...

At present, the only supported stock provider is IEX Cloud. You can register for a free key/token here.

UI

Status Bar

Shows the coin, currency, and date/time the screen was last updated.

Ticker Bar

Shows recent price changes.

Headline

Shows the most recent price, its change since the last trading day, and a cat with a suitable level of concern/satisfaction depending on the direction.

Chart

Displaying the last 7 days of activity.

Customizing

You can fairly easily customize some parts of the UI in config.ini; changing the mascot ( goodbye, pixelcat :< ), the fonts, etc.

For example:

Happening Cat
Price goes up happy cat
Price goes down worried cat
Market closed / no movement sleeping cat

In crypto-land, however, the market never closes, so you'll probably never see sleeping cat. No rest for the proletariat!

These are controlled via these values in config.ini:

[Mascot]
increasing = ./resources/pixelcat/pixelcat_cool.png
decreasing = ./resources/pixelcat/pixelcat_worried.png
static = ./resources/pixelcat/pixelcat_sleeping.png

The important thing is that to display correctly, they need to be 1-bit images, and quite small (about 25x25 pixels). It's small enough that the images need to be designed for that resolution (pixel art). Resizing large images probably won't get you a result you'll be happy with.

Brandon James Greer has a good introduction/tutorial to the concept if you fancy trying your hand at it. Otherwise Google, Shutterstock, etc., are your friend!

It could be used for more than showing stock prices! It's got a basic UI framework, and can be modified to use other data.

For example, a simple swap out of stocks for COVID-19 vaccination data (see resources/examples/vaccines.py):

UI with Ireland COVID-19 Vaccination Rate

Credits

Developing

The project requires Python 3, and works with the version (3.7) present on Raspberry Pi OS (Raspbian).

Because the project dependencies rely on the RPi.GPIO library, it can't use the same set of dependencies for developing (unless you use a Raspberry Pi for development!). I use macOS, so there's a seperate dev-requirements.txt which is basically the pip freeze output of a working virtualenv.

Structure

The entrypoint is main.py, which is where the providers and UI are tied together.

The main UI is set up in ui.py. This is a collection of objects that represent different sections, primarily: Status Bar, Ticker, Headline, and Chart.

Each of these sections is rendered and stitched together:

Status Bar

Ticker Bar

Headline

Chart

et voilá!

Rendered

I started with a script with everything hardcoded, pixel positions, etc, then decided it would be nice if I could use something more like HTML/CSS to organize the data. Ultimately, I ended up using the project as a way to learn how to write a layout engine. The results are a bit rough, but if you want to try hacking away at it, a brief explanation follows.

The programming model is similar to rudimentary HTML/CSS, with sections composed of div-like boxes (Container objects) which have properties like display, align, padding, and border, that operate similarly.

width and height can be specified, but will default to the size the elements that are added.

from inkystock.config import Config
from inkystock.paint import Pillow
from inkystock.layout import Container, Display, Align, Padding, Border

config = Config(path="./hello_world.ini")
painter = Pillow()

# an empty root container must be specified, attributes on this one are ignored.
root = Container()

# Add a container with desired attributes (1px padding, 1px border),
# display and align default to Display.BLOCK and Align.LEFT respectively.
hello_world = Container(display=Display.BLOCK,
                        align=Align.LEFT,
                        padding=Padding(top=2, left=2, bottom=2, right=2),
                        border=Border(top=1, left=1, bottom=1, right=1))

# Elements should be added via the painter.
hello_world.add(painter.text("Hello, world!",
                      font=config.fonts.statusbar,
                      font_size=config.fonts.statusbar_size))

root.add(hello_world)

The composed Containers are fed to a Layout, which can be handed to the Painter that does the actual mapping of instructions to pixels via PIL/Pillow.

from inkystock.layout import Layout

layout = Layout(root).layout()

# The canvas size is specified here with the layout
image = painter.paint((hello_world.width(), hello_world.height()), layout)

# the image render() method returns a Pillow image, this can then be saved or manipulated
image.render().save("./hello_world.png")

# the display() method configures the InkyPHAT driver and sends the image to the screen
painter.display(image)

hello, world!

Example code for hello world.

As "hello worlds" go it's quite verbose, but it works fine when putting lots of things together. See main.py and ui.py for more.

Adding a Stock Provider

A stock provider must provide both a current price quote, and historical prices.

If adding a third-party client, it should not pull in pandas or other large dependencies. I'd much prefer an implementation that just uses requests to interact with the relevant API endpoints.

Providers tried/rejected:

  • Alpha Vantage: only supports historical data
  • Coindesk: only supports Bitcoin

Disclaimer

I feel silly putting this disclaimer here, but:

This project intended for entertainment purposes only.

It is not intended to be investment advice. Seek a duly licensed professional for investment advice.

Just in case you were thinking of building a financial strategy based on a cat-meme hobby project 😅

It is also very much a work-in-progress / testing ground for things I wanted to play around with (like Pydantic, writing a layout engine, etc).