Pi4 USB-C Gadget

Pi4 Gadget

I’ve previously blogged about using Pi Zero (and Zero W) devices as USB Gadgets. This allows them to be powered and accessed via one of the micro USB sockets and it shows up as both a CD-Drive and a ethernet device.

A recent update to the Raspberry Pi 4 bootloader not only enables the low power mode for the USB hardware, allows the enabling of Network boot and enables data over the USB-C port. The lower power means it should run (without any hats) with the power supplied from a laptop.

Details of how to check/update the bootloader can be found here.

Given that the Pi4 has a Gigabit Ethernet adapter, WiFi and 4 USB sockets (need to keep the power draw low to be safe) and up to 4Gb RAM to go with it’s 4 x 1.5Ghz core processor it makes for a very attractive plugin compute device.

With this enabled all the same script from the Pi Zero’s should just work but here is the updated version for Raspbian Buster.

  • Add dtoverlay=dwc2 to the /boot/config.txt
  • Add modules-load=dwc2 to the end of /boot/cmdline.txt
  • Add libcomposite to /etc/modules
  • Add denyinterfaces usb0 to /etc/dhcpcd.conf
  • Install dnsmasq with sudo apt-get install dnsmasq
  • Create /etc/dnsmasq.d/usb with following content
interface=usb0
dhcp-range=10.55.0.2,10.55.0.6,10.55.0.0,255.255.255.248,1h
dhcp-option=3
leasefile-ro
  • Create /etc/network/interfaces.d/usb0 with the following content
auto usb0
allow-hotplug usb0
iface usb0 inet static
  address 10.55.0.1
  netmask 255.255.255.248
  • Create /root/usb.sh
#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p pi4
cd pi4
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
echo 0xEF > bDeviceClass
echo 0x02 > bDeviceSubClass
echo 0x01 > bDeviceProtocol
mkdir -p strings/0x409
echo "fedcba9876543211" > strings/0x409/serialnumber
echo "Ben Hardill" > strings/0x409/manufacturer
echo "PI4 USB Device" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower
# Add functions here
# see gadget configurations below
# End functions
mkdir -p functions/ecm.usb0
HOST="00:dc:c8:f7:75:14" # "HostPC"
SELF="00:dd:dc:eb:6d:a1" # "BadUSB"
echo $HOST > functions/ecm.usb0/host_addr
echo $SELF > functions/ecm.usb0/dev_addr
ln -s functions/ecm.usb0 configs/c.1/
udevadm settle -t 5 || :
ls /sys/class/udc > UDC
ifup usb0
service dnsmasq restart
  • Add /root/usb.sh to /etc/rc.local before exit 0

With this setup the Pi4 will show up as a ethernet device with an IP address of 10.55.0.1 and will assign the device you plug it into an IP address via DHCP. This means you can just ssh to pi@10.55.0.1 to start using it.

Building a Bluetooth speaker

Recently I’ve been playing with how to build a Bluetooth audio device using a Raspberry Pi Zero. The following are some notes on what I found.

First question is why build one when you can buy one for way less than the cost of the parts. There are a couple of reasons:

  • I build IoT prototypes for a living, and the best way to get a feel for the challenges is to actually face them.
  • Hacking on stuff is fun.

The Hardware

I’m starting out with a standard Raspberry Pi Zero W. This gets me a base high level platform that includes a WiFi and Bluetooth.

Raspberry Pi Zero W

The one thing that’s missing is an audio output (apart from the HDMI) but Raspberry Pi’s support audio using the I2S standard. There are several I2S pHATs available and I’m going to be using a pHAT DAC from Pimoroni. I’ve used these before for a project so I’m reasonably happy with how to set it up, but Pimoroni have detailed instructions.

I’m going to add a screen to show things like the current track title & artist along with the volume. I’m also going to need some buttons to send Play/Pause, Next & Previous commands to the connected device. I have a PaPiRus e-ink display that has 5 buttons built in which I was going to use but this clashes with the GPIO pins used for the DAC so instead I’ve opted for the Inky pHAT and the Button Shim.

The Software

I knew the core components of this had to be a problem others had solved and this proved to be the case. After a little bit of searching I found this project on github.

As part of the configuration we need to generate the Bluetooth Class bitmask. This can be done one this site.


Class options

This outputs a hex value of 0x24043C which is added to the /etc/bluetooth/main.conf

With this up and running I had a basic Bluetooth speaker that any phone can connect to without a pin and play music, but nothing else. The next step is to add some code to handle the button pushes and to update the display.

The Bluetooth stack on Linux is controlled and configured using DBus. Dbus is a messaging system supporting IPC and RPC

A bit of Googling round turned up this askubuntu question that got me started with the following command:

dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0/dev_44_78_3E_85_9D_6F org.bluez.MediaControl1.Play

This sends a Play command to the connected phone with the Bluetooth mac address of 44:78:3E:85:9D:6F. The problem is knowing what the mac address is as the system allows multiple devices to pair with the speaker. Luckily you can use DBus to query the system for the connected device. DBus also has some really good Python bindings. So with a bit more poking around I ended up with this:

#!/usr/bin/env python
import signal
import buttonshim
import dbus
bus = dbus.SystemBus()
manager = dbus.Interface(
bus.get_object("org.bluez","/"), 
"org.freedesktop.DBus.ObjectManager")

@buttonshim.on_press(buttonshim.BUTTON_A)
def playPause(button, pressed):
	objects = manager.GetManagedObjects()
	 
	for path in objects.keys():
	    interfaces = objects[path]
	    for interface in interfaces.keys():
	        if interface in [
	        "orge.freedesktop.DBus.Introspectable",
	        "org.freedesktop.DBus.Properties"]:
	            continue
	 
	        if interface == "org.bluez.Device1":
	            props = interfaces[interface]
	            if props["Connected"] == 1:
	                media = objects[path + "/player0"]["org.bluez.MediaPlayer1"]
	 
	                mediaControlInterface = dbus.Interface(
	                bus.get_object("org.bluez",path + "/player0"),
	                "org.bluez.MediaPlayer1")
	 
	                if media["Status"] == "paused":
	                    mediaControlInterface.Play()
	                else:
	                    mediaControlInterface.Pause()

signal.pause()

When button A is pressed this looks up the connected device, and also checks the current state of the player, is it playing or paused and toggles the state. This means that one button can be Play and Pause. It also uses the org.bluez.MediaPlay1 API rather than the org.bluez.MediaControl1 which is marked as deprecated in the doc.

The button shim also comes with Python bindings so putting it all together was pretty simple.

DBus also lets you register to be notified when a property changes, this paired with the Track property on the org.bluez.MediaPlay1 as this holds the Artist, Track Name, Album Name and Track length information supplied by the source. This can be combined with the Inky pHAT python library to show the information on the screen.

#!/usr/bin/env python

import dbus
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib

def trackChanged(*args, **kw):
	target = args[0]
	if target == "org.bluez.MediaPlayer1":
		data = args[1].get("Track",0)
		if data != 0:
			artist = data.get('Artist')
			track = data.get('Title')
			print(artist)
			print(track)


DBusGMainLoop(set_as_default=True)
system_bus = dbus.SystemBus()
system_bus.add_signal_receiver(trackChanged, 
	dbus_interface="org.freedesktop.DBus.Properties", 
	signal_name="PropertiesChanged", 
	path='/org/bluez/hci0/dev_80_5A_04_12_03_0E/player0')
loop = GLib.MainLoop()
loop.run()

This code attaches a listener to the MediaPlayer object and when it spots that the Track has changed it prints out the new Artist and Title. The code matches all PropertiesChanged events which is a little messy but I’ve not found a way to use wildcards or partial matches for the DBus interface in python (since we don’t know the mac address of the connected device at the time we start listening for changes).

Converting the Artist/Title information into an image with the Pyton Image Library then getting the Inky pHAT to render that is not too tricky

from PIL import Image, ImageDraw, ImageFont
from font_fredoka_one import FredokaOne
from inky import InkyPHAT

...

disp = InkyPHAT("yellow")
font = ImageFont.truetype(FredokaOne, 22)

img = Image.new("P", (inky_display.WIDTH, inky_display.HEIGHT))
draw = ImageDraw.Draw(img)

draw.text((), "Artist: "+ artist, disp.WHITE, font=font)
draw.text((), "Track: "+ track, disp.WHITE, font=font)

disp.set_image(img)
disp.show()


That’s the basics working, now I need to find/build a case for it and then look at seeing if I can add Chromecast Audio and Airplay support.

Updated Pi Zero Gadgets

Following on from my last post I’ve continued to work on improving my instructions for a USB connectable gadget based on a Raspberry Pi Zero.

Firstly I’ve got a slight improvement to the dnsmasq config I mentioned last time. This removes the dnsmasq.leases file which can cause issues if you plug the Zero into multiple computers. This can be a problem because while I had managed to fix the mac address for Host computer end of the connection the OS still pushes down the host name and it’s own unique id when it requests a DHCP address from dnsmasq, this causes dnsmasq to cycle through it’s small pool of addresses. This combined with the fact the clock on Zero is not battery backed up so only gets set correctly when it can reach internet can cause strangeness with addresses getting expired in strange ways. Anyway there is a pretty simple fix.

Adding leasefile-ro to the dnsmasq config causes it to not write lease information to disk, but rely on the dhcp-script to keep track of things. To do this I needed to add handling for a new option to the script to deal with dnsmasq wanting to read the current lease state at startup.

#!/bin/bash
op="${1:-op}"
mac="${2:-mac}"
ip="${3:-ip}"
host="${4}"

if [[ $op == "init" ]]; then
  exit 0
fi

if [[ $op == "add" ]] || [[ $op == "old" ]]; then
  route add default gw $ip usb0
fi

Now on to getting things working better with Windows machines.

To do this properly we need to move from the g_ether to the g_multi kernel module, this lets the Zero be a USB Mass Storage device, a network device (and a serial device) at the same time. This is useful because it lets me bundle .inf files that tell Windows which drivers to use on the device it’s self so it they can be installed just by plugging it in.

The first order of business is to fix the cmdline.txt to load the right module, after making the changes in the last post it looks like this:

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether

The g_ether needs replacing with g_multi so it looks like this:

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_multi

next we need to fix the options passed to the module, these are in the /etc/modprobe.d directory probably in a file called g_ether.conf. We should don’t need to, but to make it obvious when we come back to this a lot later on we’ll rename it to g_multi.conf. Now we’ve renamed it we need to add a few more options.

It currently looks like this:

options g_ether host_addr=00:dc:c8:f7:75:15 dev_addr=00:dd:dc:eb:6d:a1

It needs the g_ether needs changing to e_multi and some options adding to point to a disk image.

options g_multi file=/opt/disk.iso cdrom=y ro=y host_addr=00:dc:c8:f7:75:15 dev_addr=00:dd:dc:eb:6d:a1

Now we have the config all sorted we just need to build the disk image. First we need to create a directory called disk-image to use as the root of the file system we will share from the Zero. Then we need to get hold of the 2 .inf files, they ship with the Linux Kernel doc, but can be found online (serial port, RNDIS Ethernet).

Once we have the files place them in a directory called disk-image/drivers. We should also create a README.txt to explain what’s going on, put that in the root of disk-image. Along side that we want to create a file called Autorun.inf, this tell Windows about what’s on the “cd” when it’s inserted and where it should search for the driver definitions.

[AutoRun]
open="documentation\index.html"
icon=clock.ico,0

[DeviceInstall]
DriverPath=drivers

[Content]
MusicFiles=no
PictureFiles=no
VideoFiles=no

Full details of what can go in the Autorun.inf file can be found here, but the interesting bits are the DriverPath=drivers which points to the directory that we put the .inf files in earlier. Also the open=”documentation\index.html” which should open documentation/index.html when the device is plugged in which explains how to install the drivers. I also added an icon file so the drive shows up looking like a clock in the file manager.

That should be the bare minimum that needs to be on the image, but I ran into an issue with the g_multi module complaining the disk image being too small, to get round this I just stuck a 4mb image in the directory as well. To build the iso image run the following command:

mkisofs -o disk.iso -r -J -V "Zero Clock" disk-image

This will output a file called disk.iso that you should copy to /opt/disk.iso on the Zero (I built the image on my laptop as it’s easier to edit files and mkisofs is not installed in the default raspbian image).

This is working well on Linux and Windows, but I’m still getting errors on OSx to do with the file system. It’s not helped by the fact I don’t have a Mac to test on so I end up having to find friends that will let me stick a random but of hardware in to the side of their MacBook.

Once I’ve got the OSx bits sorted out I’ll put together script to build images for anything you want.

So now we have a Pi Zero that should look like a CD-ROM drive and a Network adapter when plugged into pretty much any machine going, it brings the driver information with it for windows, sets up a network connection with a static IP address and a Avahi/Bonjour/mDNS address to access it. I’m planning on using this to set up my Linear Clock on the local WiFi but there are all manner of interesting things that could be done with a device like this. e.g. an offline Certificate Authority, a 2FA token, a Hardware VPN solution or a Web Controllable display device.

brr

Raspberry Pi Zero Gadgets

I’m still slowly plugging away at my linear clock project. Currently I’m working out how to make it easy to configure it to connect to WiFi network.

One approach is the Physical Web/Web Bluetooth approach I’ve talked about before. This is a really neat solution but it only works with Android phones at the moment as Apple don’t look to be planning any Web Bluetooth support at the moment.

While looking for a more general solution I decided to look at expanding the method I’ve been using to develop the clock. Raspberry Pi Zero’s USB port can act in both Host and Device mode. This means you can plug it into another computer and have it show up as a peripheral. There is support for several different modes, Ethernet adapter, Mass Storage, Serial port plus a few others. The most popular is the Ethernet adapter. You can find some really good instructions on setting all this up here.

This works pretty well for a developer looking to access the Pi Zero to poke around but it’s still a little bit brittle for a consumer device as it relies on Bonjour/avahi to locate the device as it will end up with a randomly assigned 169.254.x.x address. This can be solved by adding some more config options after the instructions in the blog I linked to earlier.

  • First we need to fix the mac address that the Ethernet device presents. This is so that when you plug the Zero into a computer it always recognises it as the same device. To do this we need to add some options to the g_ether module. Create a file in the /etc/modprobe.d directory called g_ether.conf with the following content:
    options g_ether host_addr=02:dd:c8:f7:75:15 dev_addr=02:dd:dc:eb:6d:a1

    This sets the mac address for Zero end of the connection to

  • Next we need to give the Zero a fixed IP address, to do this we add the following to the /etc/network/interfaces file:
    auto usb0
    iface usb0 inet static
      address 10.33.0.1
      netmask 255.255.255.0
    

    The 10.0.0.0/8 range is one of the RFC 1918 private address ranges, I picked 10.33.0.0/24 as it’s not likely to clash with the average home network address range.

  • We also need to stop the dhcp client from adding that 169.254.x.x address, this is the bit that took me ages to find, but in the end you just need to add noipv4ll to the end of /etc/dhcpcd.conf
  • That takes care of the network from the Zero’s point of view but we still need to find a way to assign a network address to the Host computer. This is done with a DHCP server and the simplest to set up for this is dnsmasq. This is where the first tricky bit happens as dnsmasq is not installed by default in the raspbian lite image*. Once installed add file called local.conf to /etc/dnsmasq.d/ with the following:
    interface=usb0
    dhcp-range=usb0,10.33.0.2,10.33.0.5,255.255.255.0,1h
    dhcp-option=3
    

    This tells the Zero to serve up an address between 10.33.0.2 an 10.33.0.5, but given we fixed the mac address of the host end of the network connection it will always end up just handing out 10.33.0.2.

After all that you should have a Pi Zero you can plug into any computer and it should always be available on 10.33.0.1 (as well as raspberrypi.local if the connected computer supports Bonjour/Avahi). This will make writing documentation a lot easier.

I have a couple of extra bits as well, such as a script that sets the default route to the IP address handed out by dnsmasq so if you have internet sharing/Masquerading enabled on the host then the Zero can access the internet. (There is also DHCP option 19 which should enable packet forwarding on the DHCP client, but I need to investigate how this actually works and what effects it has on different OS)

The script lives in /root/route.sh looks like this:

#!/bin/bash
op="${1:-op}"
mac="${2:-mac}"
ip="${3:-ip}"
host="${4}"

if [[ $op == "add" ]] || [[ $op == "old" ]]; then
  route add default gw $ip usb0
fi

And to enable it add the following line to the end of the /etc/dnsmasq.d/local.conf

dhcp-script=/root/route.sh

There is still on niggle, that while the driver (RNDIS Ethernet driver) for this is shipped with Windows you still need to manually install it before it will work. There are some inf files that ship with the Linux kernel docs that can make this a lot easier to do so my next task is to work out how to user the g_multi mode which allows the Zero to be both a Ethernet adapter and a Mass Storage device. This will mean that the Zero will show up as a thumb drive as well the network adapter. I can then include some Documentation and the inf files on that drive. I have most of it working, but it still needs a little polishing, I’ll post again when I’ve got it all working nicely.

*You need to find a way to get the Zero online to install it, I used the RedBear IoT pHAT which lets me get on to my WiFi while still powering/accessing the Zero via the USB socket, but you can also boot the Zero normally with a USB Ethernet or WiFi adapter. To install dnsmasq run the following:

apt-get install dnsmasq

Lightbar clock

I’ve been meaning to get on with this project for ages, it was originally going to be based on the espruino pico but I never quiet got round to sorting it all out.

I even prototyped it as a little webpage

In the end I’ve ended up using a Raspberry Pi Zero and Node-RED to drive it all. It uses a 60 segment RGB LED strip I picked up off ebay. The LED strip is driven by the node-red-node-pi-neopixel node from the function node.

Screenshot from 2016-06-17 15-11-27

var date = new Date();
//console.log(date);
var red = [];
var green = [];
var blue = [];
var b = 125;

for (var i=0; i<60; i++) {
    red.push(0);
    green.push(0);
    blue.push(0);
}

var array = [[]];
var hours = date.getHours();
var hs = ((hours + 12) % 12) * 5;
for (var i=0; i<5; i++) {
    red[hs + i] = b;
}

var m = date.getMinutes();
green[m] = b;

var s = date.getSeconds();
blue[s] = b;

for (var i=0; i<60; i++) {
    array[0].push({
        payload: i + "," + 
            red[i] + "," +
            green[i] + "," +
            blue[i]
    })
}
return array;

It just needs a nice mount sorting to hang it on the wall.

Further work may include adding a light sensor to vary the brightness depending on the ambient light levels, a nice way to configure a wifi dongle so it can join the network for ntp time updates and so a user could set the timezone.

Flic.io Linux library

As I mentioned I recently got my hands on a set of 4 flic.io buttons. I pretty immediately paired one of them with my phone and started playing. It soon became obvious that while fun, the use cases for a button paired to a phone where limited to a single user environment and not what I had in mind.

What was needed was a way to hook the flic buttons up to something like a Raspberry Pi and Node-RED. While I was waiting for the buttons to arrive I was poking round the messages posted to the indiegogo one of the guys from Shortcut Labs mentioned that such a library was in the works. I reached out to their developer contact point asking about getting access to the library to build a Node-RED node round, I said I was happy to help test any code they had. Just before Christmas I managed got hold of a early beta release to have a play with.

From that I was able to spin up a npm module and a Node-RED node.

The Node-RED node will currently listen for any buttons that are paired with the computer and publish a message as to if it was a single, double or long click

Flic.io Node-RED node

I said I would sit on these nodes until the library shipped, but it appeared on github yesterday so hence this post. The build includes binaries for Raspberry Pi, i386 and x86_64 and needs the very latest Bluez packages (5.36+).

Both my nodes need a little bit of cleaning up and a decent README writing, once that is done I’ll push them to npm.

UPDATE:
Both nodes are now on npmjs.org:
https://www.npmjs.com/package/node-flic-buttons
https://www.npmjs.com/package/node-red-contrib-flic-buttons

GPIO buttons – Demo stand

I recently got asked to help out building a demo for one of the projects the team has been working on.

The project uses situational awareness to vary the level of authentication needed to carry out different tasks. Rather than make the person using the demo run all over the place to change location or log in and out of various systems we have stubbed out the inputs and needed a way to toggle them locally. The demo runs on a tablet so we wanted something that could sit near by and allow the inputs to be varied.

To do this I thought I’d have a play with a Raspberry Pi and the GPIO pins. The plan was to attach 5 buttons to the toggle the state of the inputs, the buttons have lights that will show the state.

This would have been a perfect use case for the new Raspberry Pi Zero as it only needs 1 USB port and the GPIO pins, but I couldn’t convince any of the guys on the team lucky enough to grab one to give it up.

The chosen buttons where 5 of these:

http://uk.rs-online.com/web/p/push-button-switches/7027140/

I’d uses something very similar in the past for the MQTT Powered Video Walls in IBM Southbank Client Centre.

The first single button prototype worked nicely after I’d worked out what was needed with pull-up resistors for the buttons and current limiting resistors for the LEDs.

Button prototype

Node-RED was set up to read the state of the buttons from the GPIO pints and to update toggle the LED output the same way.

Node-RED flow to read buttons
Node-RED flow to read buttons

To make the demo totally standalone the rPi acts as a WiFi access point that the tablet connects to. It uses this connection to load the demo and to read the updates from the buttons via MQTT over Websockets.

The USB WiFi adapter available was a Edimax EW-7811UN, The default Linux driver for this doesn’t support HostAPD, luckily there is a page with instructions and a github project with a functional version.

Mosquitto 1.4.2 was installed to act as the the MQTT broker and to support MQTT over websockets so the web app could connect directly to get the button updates.

Having finished off the wiring and configuring the rPi the whole thing was mounted in a display board put together by Andy Feltham.

And ended up looking like this, all ready for a overlay with a description and instructions.

Completed Board

Emergency FTTC Router

On Monday I moved to a new broadband provider (A&A). The BT Openreach guy turned up and swapped over the face plate on my master socket, dropped off the FTTC modem, then went and down to the green box in the street and flipped my connection over. It all would have been very uneventful except for the small problem that the new router I needed to link my kit up to FTTC modem had not arrived.

This is because BT messed up the address for where they think my line is installed a few of years ago and I’ve not been able to get them to fix it. A&A quickly sent me out a replacement router with next day delivery but it would mean a effectively 2 days without any access at home.

The routers talks to the FTTC modem using a protocol called PPPoE over normal ethernet. There is a Linux package called rp-pppoe which provides the required support. So to quickly test that the install was working properly I installed this on to my laptop and plugged it directly into FFTC modem. Things looked really good but did mean I was only able to get one device online and I was tied to one end of the sofa by the ethernet cable.

PPPoE is configured the same way PPP used to be used with dial up modems, you just need to create /etc/ppp/pppoe.conf file that looks a bit like this:

ETH=eth0
USER=xxxxxxx
DEMAND=no
DNSTYPE=SERVER
PEERDNS=yes
DEFAULTROUTE=yes
PING="."
CONNECT_POLL=2
CF_BASE=`basename $CONFIG`
PIDFILE="/var/run/$CF_BASE-adsl.pid"
LCP_INTERVAL=20
LCP_FAILURE=3
FIREWALL=NONE
CLAMPMSS=1412
SYNCHRONOUS=no
ACNAME=
SERVICENAME=
CONNECT_TIMEOUT=30
PPPOE_TIMEOUT=80

And include you username and password in the /etc/ppp/chap-secrets. Once set up you just need to run pppoe-start as root

In order to get back to something like normal I needed something else. I had a Raspberry Pi in my bag along with a USB Ethernet adapter which looked like it should fit the bill.

I installed rp-pppoe and the dhcp server then plugged one ethernet adapter into the FTTC modem and the other into a ethernet hub. Into the hub I had a old WiFi access point and the rest of my usual machines. After configuring the Pi to masquerade IP traffic from the hub I had everything back up and running. The only downside is that speeds are limited to 10mbps as that is as quick as the built in ethernet adapter on Pi will do.