Lightweight dashboards via the Linux Framebuffer

A lightweight Python module I wrote for my Raspberry Pi to display monitoring dashboards using without any GUI, just the framebuffer.
, Updated: 5 min read

Background

I wanted to re-implement my CloudWatch dashboard on my Raspberry Pi, but I wanted to make it more extensible and easier to work with. I decided to write a Python module that could display multiple widgets on the framebuffer, and I could easily add new widgets as needed using the plethora of Python libraries available.

Notes

Although this was written for a Raspberry Pi, it should work on any Linux system with a framebuffer. The Raspberry Pi is a great choice because it’s cheap, low-power, and has built-in HDMI port(s). This makes it easy to connect to a TV or monitor and display the dashboard.

Each widget has a configurable refresh rate, and refreshes occur in the background on separate threads. This allows for multiple widgets to update concurrently, and the main thread can focus on rendering the latest output to the framebuffer.

Currently Implemented Widgets

  • CloudWatch Dashboards via Image API
  • Images from the web - support arbitrary images, like security cams, etc.
  • Clock - a simple clock widget, with date and time, and an optional timezone
  • Stock Prices Chart - a candlestick chart of a stock symbol
  • Text - static text - mainly an example for future widgets
  • Satellite Tracking - plot the location of a satellite (e.g. the Space Station)

Final Result

Here’s some things you could do with it:


Prerequisites

Add user to video group

We won’t run the main script as root, so we add the user to the video group to access the framebuffer.

usermod -aG video "$USER"

Python requirements

If desired, run this in a virtualenv.

pip3 install -r requirements.txt

Pillow dependencies

If not installed, you will likely need libopenjp2-7 for the Pillow library to work. I ran into this error: ImportError: libopenjp2.so.7: cannot open shared object file: No such file or directory, which we can solve by running:

sudo apt-get install libopenjp2-7

Switch to Graphics Mode

I originally integrated this into the main program but that’d require us to call it as root. Instead, I separated the script and we can run this as root to set the graphics mode. This functions by calling ioctl on the framebuffer device to set the graphics mode, which is a privileged operation.

This has the same effect as tools like tput civis and tput cnorm terminal, but can be run outside of the tty that it affects.

sudo python3 gfxmode.py /dev/tty1 graphics

Since we’re downloading images from the internet and loading with PIL, I figured running the whole thing as root (or trying to drop permissions) would be more trouble than it’s worth so this portion is separate.

The Code

I published the code here: https://github.com/richinfante/fb-dashboard, which you can clone and run on your Raspberry Pi.

Copy the examples/example1.toml to be named config.toml, and customize the file to your liking. This is where you specify the widgets you want to display, their position, etc.

Configuration

CloudWatch Dashboards

This is an example config. The widget key is the JSON representation of the widget from the CloudWatch dashboard. You can get this by clicking the “Source” tab in the widget editor, and setting the type to “Image API”. Remove all newlines and it should work.

If you replace the hardcoded width and height values in the JSON with w and h, the image will auto-size to the widget bounds. This is optional if you want to stretch or scale the image.

[widgets.my_cloudwatch_metric]
x = '0'
y = '0'
w = 'w / 2'
h = 'h / 2'
type = 'CloudWatchMetricImage'
aws_profile = 'default'
aws_region = 'us-east-1'
widget = '{"metrics": [[ "AWS/CloudFront", "Requests", "Region", "Global", "DistributionId", "YOUR_DISTRIBUTION_ID" ]],"view": "timeSeries","stacked": false,"stat": "Sum","period": 900, "width": w, "height": h,"start": "-PT72H", "end": "P0D", "timezone": "-0400"}'

Images from the web

You can pull images from the via an HTTP request as well. This is an example of pulling an image from Unsplash. The path key is the URL of the image you want to display.

[widgets.unsplash_image]
x = 'w / 2'
y = 'h / 2'
w = 'w / 2'
h = 'h / 2'
type = 'Image'
path = 'https://images.unsplash.com/photo-1532522750741-628fde798c73?w=1600&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTB8fGhhY2tlcnxlbnwwfHwwfHx8MA%3D%3D'

IP Cameras

Although it won’t give a real-time feed, you can pull images from security cameras as well. This is an example of pulling an image from an Amcrest camera.

Since Amcrest cameras use digest auth, you’ll need to specify the auth_type, username, and password keys. If your camera uses basic auth, you can omit the auth_type key.

[widgets.security_cam]
x = 'w / 2'
y = '0'
w = 'w / 2'
h = 'h / 2'
type = 'Image'
path = 'http://192.168.1.123/cgi-bin/snapshot.cgi'
# NOTE: `digest` is the auth type for Amcrest cameras, you may need to change this to `basic`, or omit credentials entirely
auth_type='digest'
username='camera_username'
password='camera_pass'

Clock

Here’s an example of a clock widget config. Customize the position, colors, and timezone to your liking.

[widgets.clock]
x = '0'
y = '0'
w = 'w / 2'
h = 'h / 2'
type = 'Clock'
clock_format = '%I:%M %p'
date_format = '%A, %B %d, %Y'
bg_color = '#000000'
fg_color = '#2196F3'
timezone = 'America/New_York'

Stocks

[widgets.apple_stock]
x = '0'
y = 'h / 2'
w = 'w / 2'
h = 'h / 2'
type = 'StockMarketCandlestick'
# Apple stock
symbol = 'AAPL'
time_period = '5d'
interval = '1h'
refresh_interval = '600'
# plot_style = 'nightclouds'
show_volume = true

Running the dashboard

To test your configuration, you can run it manually. To run it on an unsupported system and get a PNG file instead of writing to the framebuffer, you can pass the --no-framebuffer flag.

cd into the cloned repo and run the following command:

python3 -m fb_dashboard

Run at boot

On Raspberry Pi, /etc/rc.local is a pretty easy place to run scripts at boot. Add this line before exit 0:

You will likely need to change the username and path to the cloned repo. This will run the graphics mode script as root, then run the main script as the normal user.

python3 /home/rich/aws-dash/gfxmode.py /dev/tty1 graphics

# if using a virtualenv
sudo -u rich bash -c 'cd /home/rich/aws-dash && source env/bin/activate && python3 -m fb_dashboard'

If not using a virtual environment you replace the last line above with this:

# if not using a virtualenv
sudo -u rich bash -c 'cd /home/rich/aws-dash && python3 -m fb_dashboard'

Auto-screen on/off

For nighttime, I usually want these kinds of dashboards to turn off.

To shut down automatically and turn on at a certain time, you can use cron. Add these lines to your crontab:

# turn on at 6:30 AM
30 6 * * * vcgencmd display_power 1

# turn off at 10:00 PM
0 22 * * * vcgencmd display_power 0

Testing

I have also built in a test mode that doesn’t use a framebuffer, but instead writes to a PNG file named framebuffer.png. This can be used for testing on your non-linux machine or inside a desktop environment. You can also pass --exit to exit after one (complete) frame after all widgets have been rendered.

python3 -m fb_dashboard --no-framebuffer

Future Enhancements

I may add some new widget types, such as a clock, and maybe some other system stats. Also looking at increasing refresh rates and implementing a more comprehensive refresh/threading system for concurrent updates. If you have any ideas, let me know!

Subscribe to my Newsletter

Like this post? Subscribe to get notified for future posts like this.

Change Log

  • 7/29/2024 - Initial Revision
  • 8/1/2024 - Updates to include latest development / usage
  • 8/1/2024 - Content updates
  • 8/4/2024 - Update configuration examples to new toml version

Found a typo or technical problem? file an issue!