Back to Building the Linear Clock

A LONG time ago I started to work out how to build a linear clock using a strip of 60 LEDs. It’s where all my playing with using Pi Zeros and Pi 4s as USB devices started from.

I’ve had a version running on the desk with jumper wires hooking everything up, but I’ve always wanted to try and do something a bit neater. So I’ve been looking at building a custom Pi pHAT to link everything together.

The design should be pretty simple

  • Break out pin 18 on the Pi to drive the ws2812b LEDs.
  • Supply 5v to the LED strip.
  • Include an I2C RTC module so the Pi will keep accurate time, especially when using a Pi Zero (not a Pi Zero W) with no network connectivity.

I know that the Pi can supply enough power from the 5v pin in the 40 pin header to drive the number of LEDs that the clock should have lit at any one time.

Also technically the data input for the ws2812b should also be 5v but I know that at least with the strip that I have it will work with the 3.3v supplied from GPIO pin 18.

I had started to work on this with Eagle back before it got taken over by Autodesk, while there is still a free version for hobbyists I thought I’d try something different this time round and found librePCB.

Designing the Board

All the PCB board software looks to work in a similar way, you start by laying out the circuit logically before working out how to lay it out physically.

The component list is:

The RTC is going to need a battery to keep it up to date if the power goes out so I’m using the Keystone 500 holder which is for a 12mm coin cell. These are a lot smaller than the more common 20mm (cr2032) version of coin cells, so should take up lot less space on the board.

I also checked that the M41T81 has a RTC Linux kernel driver and it looks to be included in the rtc-m41t80 module, so should work.

Finally I’m using the terminal block because I don’t seem to be able to find a suitable board mountable connector for the JST SM connector that comes on most of the LED strips I’ve seen. The Wikipedia entry implies that the standard is only used for wire to wire connectors.

Circuit layout

LibrePCB has a number of component libraries that include one for the Raspberry Pi 40 pin header.

Physical and logical diagram of RTC component

But I had to create a local library for some of the other parts, especially the RTC and the battery holders. I will look at how to contribute these parts to the library once I’ve got the whole thing up and running properly.

Block circuit diagram

The Pi has built in pull up resistors on the I2C bus pins so I shouldn’t need to add any.

Board layout

Now I have all the components linked up it was time to layout the actual board.

View of PCB layout

The board dimensions are 65mm x 30mm which matches the Pi Zero and with the 40 pin header across the top edge.

The arrangement of the pins on the RTC mean that no matter which way round I mount it I always end up with 2 tracks having to cross which means I have one via to the underside of the board. Everything else fits into one layer. I’ve stuck the terminal block on the left hand edge so the strip can run straight out from there and the power connector for the Pi can come in to the bottom edge.

Possible Improvements

  • An I2C identifier IC – The Raspberry Pi HAT spec includes the option to use the second I2C bus on the Pi to hold the device tree information for the device.
  • A power supply connector – Since the LED strip can draw a lot of power when all are lit, it can make sense to add either a separate power supply for just the LEDs or a bigger power supply that can power the Pi via the 5v GPIO pins.
  • A 3.3v to 5v Level shifter – Because not all the LED strips will work with 3.3v GPIO input.
  • Find a light level sensor so I can adjust the brightness of the LED strip based on the room brightness.

Next Steps

The board design files are all up on GitHub here.

I now need to send off the design to get some boards made, order the components and try and put one together.

Getting boards made is getting cheaper and cheaper, an initial test order of 5 boards to test from JLC PCB came in at £1.53 + £3.90 shipping (this looks to include a 50% first order discount). This has a 12 day shipping time, but I’m not in a rush so this just feels incredibly cheap.

Soldering the surface mount RTC is going to challenge my skills a bit but I’m up for giving it a go. I think I might need to buy some solder paste and a magnifier.

I’ll do another post when the parts all come in and I’ve put one together.

Another LoRa Temperature/Humidity Sensor

Having deployed my Adafruit Feather version of a Temperature/Humidity LoRa Sensor to make use of my The Things Network gateway it’s now time to build another sesnor, this time with a Raspberry Pi Zero.

Pi Supply LoRa node pHat with sensor

I have a Pi Supply LoRa node pHAT that adds the radio side of things, but I needed a sensor. The pHAT has a header on the right hand edge which pulls out the 3.3v, 2, 3, 4 and Gnd, pins from the pi.

These just happen to line up with the I2C bus. They are also broken out in the same order as used by the Pimoroni Breakout Garden series of I2C sensors which is really useful.

Pimoroni’s Breakout Garden offer a huge range of sensors and output devices in either I2C or SPI. In this case I grabbed the BME680 which offers temperature, pressure, humidity and air quality in a single package.

Pimoroni supply a python library to read from the sensor so it was pretty easy to combine this with python to drive the LoRa pHat

#!/usr/bin/env python3
from rak811 import Mode, Rak811
import bme680
import time

lora = Rak811()
lora.hard_reset()
lora.mode = Mode.LoRaWan
lora.band = 'EU868'
lora.set_config(app_eui='xxxxxxxxxxxxx',
                app_key='xxxxxxxxxxxxxxxxxxxxxx')
lora.join_otaa()
lora.dr = 5

sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)

while True:
    if sensor.get_sensor_data():
        temp = int(sensor.data.temperature * 100)
        humidity = int(sensor.data.humidity * 100)
        foo = "{0:04x}{1:04x}".format(temp,humidity)
        lora.send(bytes.fromhex(foo))
        time.sleep(20)

lora.close()

I’m only using the temperature and humidity values at the moment to help keep the payload compatible with the feather version so I can use a single “port” in the TTN app.

I’ve put all the code for both this and the Feather version on GitHub here.

As well as the Pi Zero version I’ve upgraded the Feather with a 1200mAh LiPo battery and updated the code to also transmit the battery voltage so I can track battery life and work out how often I need to charge it.

Adding the battery also meant I could stick it in my bag and go for a walk round the village to see what sort of range I’m getting.

I’m currently using a little coil antenna on the gateway that is laid on it’s side, I’ll find something to prop it up with which should help. The feather is also only using a length of wire antenna so this could be seen as a worst case test.

It’s more than good enough for what I need right now, but it would be good to put a proper high gain one in then it might be useful to more people. It would be great if there was a way to see how many unique TTN applications had forwarded data through a gateway to see if anybody else has used it.

64bit Raspberry Pi OS build with USB-C Ethernet enabled

As a follow up to the 32bit version I built, using the script I described in my post about building custom SD card images, I have created a 64bit version based on the Raspberry Pi OS beta.

You can download the 64bit version from here. This is a full Raspberry Pi OS image, not a “lite” version as only full versions are available in beta.

This is a manual build (following these instructions) because at the moment the script won’t work with the 64bit version because DockerPi doesn’t support USB devices when emulating 64bit CPUs. USB is needed to attach the emulated Network Adapter. There are a set of patches for QUEMU pending that should enable this.

As soon as the patches become generally available I’ll update the project on github.

Raspberry Pi Streaming Camera

I talked about using a ONVIF camera to stream to a Chromecast earlier because they come with an open well documented interface for pulling video from them (as well as pan/tilt/zoom control if available).

If you don’t have a camera that supports ONVIF you can build something similar with a Raspberry Pi and the Camera module.

This should work with pretty much all of the currently available Raspberry Pi models (With the exception of the basic Pi Zero that doesn’t have Wifi)

  1. Flash a SD card with the Raspbian Lite image
  2. Insert the camera ribbon cable into both the camera module and the Pi
  3. Once the card has booted use the raspi-conf command to enable the Camera interface
  4. Install ffmpeg sudo apt-get install ffmpeg
  5. Create a script with the following content
#!/bin/sh

v4l2-ctl --set-ctrl video_bitrate=300000

ffmpeg -f video4linux2 -input_format h264 -video_size 640x360 -framerate 30 -i /dev/video0  -vcodec copy -an -f flv rtmp://192.168.1.96/show/pi
  • This script sets the max video bitrate to 30kps
  • If you need to rotate the video you can insert v4l2-ctl --set-ctrl=rotate=180 before ffmpeg to rotate 180 degrees
  • ffmpeg uses the videolinux2` driver to read from the attached camera (/dev/video0)
  • Takes h264 encoded feed at 640x360 and 30 frames per second and outputs it to the same nginx instance that I mentioned in my previous post. The feed is called pi

ffmpeg uses the on board hardware support for the video encoding so even a Pi Zero W runs at about 5% CPU load. This means that if you only have 1 camera you could probably run nginx on the same device, else you can have a multiple cameras all feeding to a central video streaming server.

If you want a kit that comes with the Pi Zero W, Camera and a case to mount it to a window have a look at the Pimoroni OctoCam.

The instructions should also work for pretty much any USB (or built in) camera attached to a Linux machine.

Building Custom Raspberry Pi SD Card Images

After my post about using a Raspberry Pi 4 as a USB gadget got linked to by a YouTuber who worked out it also worked with the iPad Pro it has been getting a lot of traffic.

Pi4 Gadget

Along with the traffic came a number of comments from people wanting help setting things up. While the instructions are reasonably complete, they do assume a certain amount of existing knowledge and access to a monitor/keyboard/network to complete everything. Also given the majority of the readers were Apple users they couldn’t mount the main partition of the SDCard as it is a ext4 file system.

The quick and dirty solution is for me to modify a standard image and host it somewhere. This is OK, but it means I have to,

  • Find somewhere to host a 500mb file
  • Keep it up to date when new versions are released
  • Provide some way for people to trust what changes I’ve made to the image

I can probably get round the first one pretty easily, bandwidth is a lot cheaper than it used to be. The second and third items are a little harder.

I started to look at a way to script the modifications to a standard Raspbian image, that way I could just host the script and people could provide their own starting image. On a Linux machine I could mount both partitions on the card and modify or add the required config files, but the problem was installing dnsmasq. This needs the image to actually be running for apt-get to run, which gets back to the chicken/egg problem of needing to boot the pi order to make the changes. That and it would only run on Linux, not a OSx or Windows machine.

What I need is a way to “run” a virtual Raspberry Pi and a way to run commands in that virtual machine. I know from working with services like Balena.io‘s build system that it is possible to emulate the ARM processor found in a Raspberry Pi on Intel based hardware. I found a couple of examples of people using Qemu to run virtual Pis and I was just about to set up the same when I came across dockerpi, which is a docker image with everything preconfigured. You can mount a SD card image

Virtual Raspberry Pi in a terminal window

When started you end up with what is basically the virtual machine console as a command line app. You can interact with it just like any other console application. I logged in as pi and then used sudo to run apt-get update and apt-get install dnsmasq.

That works for doing it manually, but I need to script this, so it’s time to break out some old school Linux foo and use expect.

Expect is a scripting tool that reads from stdin, and outputs to stdout, but will wait for a known output before sending a reply. It was used in the early days of the Internet to script dial up internet.

#!/usr/bin/expect -f
set timeout -1
set imageName [lindex $argv 0]
if {[string trimleft $imageName] eq ""} {
  puts "No Image file provided"
  exit
}
set cwd [file normalize [file dirname $argv0]]
set imagePath [file join $cwd $imageName]
spawn docker run -i --rm -v $imagePath:/sdcard/filesystem.img lukechilds/dockerpi:vm
expect "login: "
send "pi\n"
expect "Password: "
send "raspberry\n"
interact

This expect script takes the name of the SD Card image as an argument, starts the Docker container and then logs in with the default pi/raspberry username & password.

With a bit more work we can get all all the changes done including creating the extra files.

...
proc slurp {file} {
    set fh [open $file r]
    set ret [read $fh]
    close $fh
    return $ret
}
...
set file [slurp "etc/network/interfaces.d/usb0"]
expect "# "
send "cat <<EOF >> /etc/network/interfaces.d/usb0\n"
send "$file\n"
send "EOF\n"
...

I’ve checked all the files into a github repository here. I’ve tested the output with a Pi Zero and things look good. To run it for yourself, clone the repo, copy the Raspbian Lite image (unzip it first) into the directory and run ./create-image <image file name>

There is a version of the output from here.

I’ve still got to get round to trying to the RNDIS Ethernet device support working so it will load the right drivers on Windows. And I need to extend the script to build the Personal CA Appliance from my last post.

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
  • If you have not already enabled ssh then create a empty file called ssh in /boot
  • 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,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
  • Make /root/usb.sh executable with chmod +x /root/usb.sh
  • Add /root/usb.sh to /etc/rc.local before exit 0 (I really should add a systemd startup script here at some point)

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.

Addendum

Quick note, not all USB-C cables are equal it seems. I’ve been using this one from Amazon and it works fine.

The latest revision (as of late Feb 2020) of the Pi 4 boards should work with any cable.

There is also now a script to create pre-modified Raspbian images here with a description here and a copy of the modified image here.

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.