Ubuntu is lighting up the Raspberry Pi this week with the first of a two-part collection of Unicorn HAT tutorials from our resident Pi developers, Dave ‘waveform’ Jones and William ‘jawn-smith’ Wilson.
In part 1 we start with a couple of simple Unicorn pHAT projects for those just getting started. This is followed by a more advanced case study from Dave on how to set up a dashboard for his exciting piwheels project using the Unicorn HAT.
This is a guest post from William’s blog, which he’s kindly allowed us to share here. Check out his site for more great Pi tutorials as well as some equally colorful 3D printing projects.
As of Ubuntu 22.04, the Pimoroni Unicorn hats are supported on Ubuntu out of the box. This includes the standard Unicorn Hat, Unicorn pHAT, Unicorn HAT Mini, and Unicorn HAT HD.
To install the libraries for each HAT, run the following commands:
sudo apt install python3-unicornhat
sudo apt install python3-unicornhathd
sudo apt install python3-unicornhatmini
Below are some examples of how to use them!
Section written by William Wilson
Note: sudo is required for all pHAT scripts
The pHAT is the perfect size to use on a Raspberry Pi Zero 2 and uses the same library as the Unicorn HAT.
The following script will display the Ukrainian flag:
import unicornhat as uh
uh.set_layout(uh.AUTO)
uh.brightness(0.5)
width,height=uh.get_shape()
for y in range(height):
for x in range(width):
if x < 2:
uh.set_pixel(x, y, 0, 87, 183)
else:
uh.set_pixel(x, y, 255, 221, 0)
uh.show()
while True:
pass
This next example periodically checks your internet speed and uses the pHAT to indicate if the speed is good or bad, using color (green, yellow or red) as an indicator. It requires the speedtest python module, which isn’t packaged natively in Ubuntu.
To install it, run: sudo pip3 install speedtest-cli
Then use the following script to create your mini-dashboard.
import unicornhat as uh
import speedtest
import time
st = speedtest.Speedtest()
uh.set_layout(uh.AUTO)
uh.rotation(0)
uh.brightness(0.5)
width,height=uh.get_shape()
while True:
# run a speed test for download speed
dl = st.download()
# run a speed test for upload speed
ul = st.upload()
# Set the Unicorn pHAT LEDs accordingly
if dl > 30000000: # 30 Mb/s
# set the LEDs to green!
dleds = (0, 255, 0)
elif dl > 15000000: # 15 Mb/s
# set the LEDs to yellow
dleds = (255, 255, 0)
else: # below 15 Mb/s
# set the LEDs to red
dleds = (255, 0, 0)
if ul > 30000000: # 30 Mb/s
# set the LEDs to green!
uleds = (0, 255, 0)
elif ul > 15000000: # 15 Mb/s
# set the LEDs to yellow
uleds = (255, 255, 0)
else: # below 15 Mb/s
# set the LEDs to red
uleds = (255, 0, 0)
for y in range(height):
for x in range(width):
if x < 2:
uh.set_pixel(x,y,uleds)
else:
uh.set_pixel(x,y,dleds)
uh.show()
# sleep 10 minutes
time.sleep(600)
As you can see in the image above, my download speed was very good but my upload speed was not.
For more projects like this, Pimoroni has many more examples in their Unicorn HAT GitHub repository.
Section written by Dave Jones
Note: sudo is required for all Unicorn Hat scripts
Anybody that’s been on a video call with me has generally noticed some neopixely thingy pulsing away quietly behind me. This is the Sense HAT-based piwheels monitor that lives on my desk (and occasionally travels with me).
For those unfamiliar with piwheels, the piwheels project is designed to automate the building of wheels from packages on PyPI for a set of pre-configured ABIs. In plain English, this means that piwheels contains pre-built packages rather than the source packages that would need to be built locally as part of the installation saving Pi users valuable time when installing new packages.
Piwheels currently only builds wheels for RaspiOS but we are currently exploring Ubuntu support in piwheels as well.
While a monitor comprised of 64 colored dots may seem minimal bordering on useless, I’ve found it quite the opposite for several reasons:
If anyone wants to follow in my pioneering lo-fi monitoring footsteps, here’s a little script to achieve something similar with a Unicorn HAT. We’ll go through it piece by piece:
#!/usr/bin/python3
import ssl
import math
import subprocess as sp
from pathlib import Path
from itertools import cycle
from time import sleep, time
from threading import Thread, Event
from urllib.request import urlopen, Request
import unicornhat
We start off with all the imports we’ll need. Nothing terribly remarkable here other than to note the only external dependency is the Unicorn HAT library. Now onto the main monitor function:
def monitor(layout):
unicornhat.set_layout(unicornhat.AUTO)
unicornhat.rotation(0)
unicornhat.brightness(1.0)
width, height = unicornhat.get_shape()
assert len(layout) <= height
assert all(len(row) <= width for row in layout)
pulse = cycle(math.sin(math.pi * i / 30) for i in range(30))
updates = UpdateThread(layout)
updates.start()
try:
for p in pulse:
colors = {
None: (0, 0, 0),
True: (0, 127, 0),
False: (int(255 * p), 0, 0),
}
for y, row in enumerate(layout):
for x, check in enumerate(row):
value = check.value if isinstance(check, Check) else check
unicornhat.set_pixel(x, y, colors.get(value, value))
unicornhat.show()
sleep(1/30)
finally:
unicornhat.clear()
updates.stop()
updates.join()
This accepts a single parameter, layout, which is a list of lists of checks. Each check corresponds to a single pixel on the HAT, so you can’t define more than 8 per row, and no more than 64 in total.
The function sets up:
Then it goes into an infinite loop (for p in pulse – remember that pulse is an infinite generator) constantly updating the HAT with the values of each check.
Note: You may note that check.value is only used when our check is actually a check, and further that if the value isn’t found in the colors lookup table, we just use the value directly. This allows us to specify literal False, True, or None values instead of checks (in case we want to space things out a bit), or have checks directly return color tuples instead of bools.
Now an important question: what is a check? Let’s define some:
def page(url, *, timeout=10, status=200, method='HEAD', **kwargs):
context = ssl.create_default_context()
req = Request(url, method=method, **kwargs)
try:
print(f'Requesting {url}')
with urlopen(req, timeout=timeout, context=context) as resp:
return resp.status == status
except OSError:
return False
def cmd(cmdline, shell=True):
try:
print(f'Running {cmdline}')
sp.check_call(
cmdline, stdout=sp.DEVNULL, stderr=sp.DEVNULL, shell=shell)
except sp.CalledProcessError:
return False
else:
return True
def file(filename, min_size=1):
try:
print(f'Checking {filename}')
return Path(filename).stat().st_size > min_size
except OSError:
return False
We define three check functions:
Next, we define a class which we’ll use to define individual checks. It will wrap one of the functions above, the parameters we want to pass to it, and how long we should cache results for before allowing the check to be run again:
class Check:
def __init__(self, func, *args, every=60, **kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
self.every = every
self.last_run = None
self.value = None
def update(self):
now = time()
if self.last_run is None or self.last_run + self.every < now:
self.last_run = now
self.value = self.func(*self.args, **self.kwargs)
Next, we need the background thread that will loop round running the update method of all the checks in the layout:
class UpdateThread(Thread):
def __init__(self, layout):
super().__init__(target=self.update, args=(layout,), daemon=True)
self._done = Event()
def stop(self):
self._done.set()
def update(self, layout):
while not self._done.wait(1):
for row in layout:
for check in row:
if isinstance(check, Check):
check.update()
Finally, we need to run the main monitor function and define all the checks we want to execute. I’ve included some examples which check some common / important pages on the piwheels site, some pages on my blog server, some basic connectivity checks (can ping the local gateway, can ping a DNS name, can ping Google’s DNS), and some example file checks.
if __name__ == '__main__':
monitor([
[ # some connectivity tests, centered
None,
None,
None,
Check(cmd, 'ping -c 1 -W 1 192.168.0.1', every=5),
Check(cmd, 'ping -c 1 -W 1 8.8.8.8', every=30),
Check(cmd, 'ping -c 1 -W 1 ubuntu.com', every=30),
],
[ # a blank row
],
[ # check some piwheels pages
Check(page, 'https://www.piwheels.org/'),
Check(page, 'https://www.piwheels.org/packages.html'),
Check(page, 'https://www.piwheels.org/simple/index.html'),
Check(page, 'https://www.piwheels.org/simple/numpy/index.html'),
],
[ # make sure Dave's little pi blog is running
Check(page, 'https://waldorf.waveform.org.uk/'),
Check(page, 'https://waldorf.waveform.org.uk/pages/about.html'),
Check(page, 'https://waldorf.waveform.org.uk/archives.html'),
Check(page, 'https://waldorf.waveform.org.uk/tags.html'),
Check(page, 'https://waldorf.waveform.org.uk/2020/package-configuration.html'),
],
[ # a coloured line
(255, 127, 0)
] * 8,
[ # are our backups working?
Check(file, '/var/backups/dpkg.status.0'),
Check(file, '/var/backups/apt.extended_states.0'),
Check(file, '/tmp/foo', every=5),
],
])
You can run the full script like so:
$ sudo ./monitor.py
Press Ctrl+C to exit the script.
The last file check is for /tmp/foo, which probably doesn’t exist. So when you run this script you should see at least one blinking red “failure”. Try running echo foo > /tmp/foo and watch the failure turn green after 5 seconds. Then rm /tmp/foo and watch it turn back to blinking red.
If you wish to run the script automatically on boot, place this service definition in /etc/systemd/system/unicorn-monitor.service (this assumes you’ve saved the script under /usr/local/bin/monitor.py):
[Unit]
Description=Unicorn HAT based monitor
After=local-fs.target network.target
[Service]
Type=simple
Restart=on-failure
ExecStart=/usr/bin/python3 /usr/local/bin/monitor.py
[Install]
WantedBy=multi-user.target
Then run the following and you should find that the monitor will start automatically on the next reboot:
$ sudo systemctl daemon-reload
$ sudo systemctl enable unicorn-monitor
Enjoy!
And that’s all from William and Dave for this week, check back soon for part 2 where they take us through how to build a system monitor using the Unicorn HAT HD as well as a playable game of micro-Pong on the Unicorn HAT Mini!
If these ideas have sparked the imagination, don’t forget you can share your projects in the Raspberry Pi category on the Ubuntu Discourse!
For tips on getting started with the Raspberry Pi, as well as further project ideas, check out some of the links below.
One of the most critical gaps in traditional Large Language Models (LLMs) is that they…
Canonical is continuously hiring new talent. Being a remote- first company, Canonical’s new joiners receive…
What is patching automation? With increasing numbers of vulnerabilities, there is a growing risk of…
Wouldn’t it be wonderful to wake up one day with a desire to explore AI…
Ubuntu and Ubuntu Pro supports Microsoft’s Azure Cobalt 100 Virtual Machines (VMs), powered by their…
Welcome to the Ubuntu Weekly Newsletter, Issue 870 for the week of December 8 –…