The Quite Rubbish Clock, mk.2

this is bad and I should feel bad

In early 2013, I must’ve been left unsupervised for too long since I made The Quite Rubbish Clock:

It still isn’t human readable …

Written in (Owen Wilson voice) kind of an obsolete vernacular and running on hardware that’s now best described as “quaint”, it was still absurdly popular at the time. Raspberry Pis were still pretty new, and people were looking for different things to do with them.

I happened across the JASchilz/uQR: QR Code Generator for MicroPython the other day, and remembered I had some tiny OLED screens that were about the same resolution as the old Nokia I’d used in 2013. I wondered: could I …?

small microcontroller board with USB C cable attached and an OLED screen on top. The OLED is displaying a QR code which reads '172731'
OLED Shield on a LOLIN S2 Mini: very smol indeed

The board is a LOLIN S2 Mini with a OLED 0.66 Inch Shield on top, all running MicroPython. One limitation I found in the MicroPython QR library was that it was very picky about input formats, so it only displays the time as HHMMSS with no separators.

Source, of course:

# -*- coding: utf-8 -*-
# yes, the Quite Rubbish Clock rides again ...
# scruss, 2022-06-30
# MicroPython on Lolin S2 Mini with 64 x 48 OLED display
# uses uQR from https://github.com/JASchilz/uQR
# - which has problems detecting times with colons

from machine import Pin, I2C, RTC
import s2mini  # on Lolin ESP32-S2 Mini
import ssd1306
from uQR import QRCode

WIDTH = 64  # screen size
HEIGHT = 48
SIZE = 8  # text size
r = RTC()

# set up and clear screen
i2c = I2C(0, scl=Pin(s2mini.I2C_SCL), sda=Pin(s2mini.I2C_SDA))
oled = ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c)
oled.fill(0)


def snazz():
    marquee = [
        "   **",
        "   **",
        "   **",
        "   **",
        "   **",
        "********",
        " ******",
        "  ****",
        "   **",
        " quite",
        "rubbish",
        " clock",
        "  mk.2",
        "<scruss>",
        " >2022<"
    ]
    for s in marquee:
        oled.scroll(0, -SIZE)  # scroll up one text line
        oled.fill_rect(0, HEIGHT-SIZE, WIDTH,
                       SIZE, 0)  # blank last line
        oled.text("%-8s" % s, 0, HEIGHT-SIZE)  # write text
        oled.show()
        time.sleep(0.25)
    time.sleep(5)
    oled.fill(1)
    oled.show()


snazz()  # tedious crowd-pleasing intro

qr = QRCode()
while True:
    qr.add_data("%02d%02d%02d" % r.datetime()[4:7])
    qr.border = 1  # default border too big to fit small screen
    m = qr.get_matrix()
    oled.fill(1)
    for y in range(len(m)):
        for x in range(len(m[0])):
            # plot a double-sized QR code, centred, inverted
            oled.fill_rect(9 + 2*x, 1 + 2*y, 2, 2, not m[y][x])
    oled.show()
    time.sleep(0.05)
    qr.clear()

If your output is glitchy, you might need to put the following in boot.py:

import machine
machine.freq(240000000)

This increases the ESP32-S2’s frequency from 160 to 240 MHz.

qrclock, the demo reel

classy cable for the Quite Rubbish clock

The video of the Quite Rubbish Clock isn’t running the same code that’s in the listing. Here it is, showing off some of the handy code that’s in bgreat’s nokiaSPI Python class:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# qrmovie

import time
# need to use git://github.com/mozillazg/python-qrcode.git
import qrcode
from PIL import Image, ImageFont
import ImageOps
# uses bgreat's SPI code; see
# raspberrypi.org/phpBB3/viewtopic.php?f=32&t=9814&p=262274&hilit=nokia#p261925
import nokiaSPI

noki = nokiaSPI.NokiaSPI()              # create display device
qr = qrcode.QRCode(version=1,           # V.1 QR Code: 21x21 px
error_correction=qrcode.constants.ERROR_CORRECT_M,
box_size=2, border=1)
bg = Image.new('1', (84, 48))           # blank (black) image background

# intro
noki.cls()
noki.led(0)
time.sleep(3)
for i in range(0,769,32):
    noki.led(i)
    time.sleep(0.04)

# display is 14 columns by 8 rows
noki.centre_word(1, 'scruss.com')
noki.centre_word(3, 'presents')
time.sleep(3)
noki.cls()
noki.centre_word(1, 'qrclock')
noki.centre_word(2, 'the')
noki.gotorc(3,3)
noki.text("[Q]uite")
noki.gotorc(4,3)
noki.text("[R]ubbish")
noki.gotorc(5,3)
noki.text(" Clock")
time.sleep(3)

elapsed=0
start_time = time.time()
while (elapsed<12):
    qr.clear()
    newbg = bg.copy()                   # copy blank background
    s = time.strftime('%Y-%m-%d %H:%M:%S')
    qr.add_data(s)                      # make QR Code of YYYY-MM-DD HH:MM:SS
    qr.make()
    qrim = qr.make_image()              # convert qrcode object to PIL image
    qrim = qrim.convert('L')            # make greyscale
    qrim = ImageOps.invert(qrim)        # invert colours: B->W and W->B
    qrim = qrim.convert('1')            # convert back to 1-bit
    newbg.paste(qrim, (18, 0))          # paste QR Code into blank background
    noki.show_image(newbg)              # display code on LCD
    time.sleep(0.4)                     # pause before next display
    elapsed = time.time() - start_time

noki.cls()
noki.centre_word(1, 'for')
noki.centre_word(2, 'more')
noki.centre_word(3, 'details')
time.sleep(3)
noki.cls()
noki.load_bitmap("blogpost-nokia.bmp", True)
time.sleep(7)
noki.cls()
noki.centre_word(3, 'fin')
noki.centre_word(5, 'scruss, 2013')
time.sleep(1)
for i in range(768,-1,-32):
    noki.led(i)
    time.sleep(0.05)
time.sleep(1)
noki.cls()

(This source, plus nokiaSPI class: qrclock-movie.zip)

Lines 43-58 show off the QR clock for a maximum of 12 seconds. Any more, and you’d get really bored.

The screen handling functions I used are:

  • cls() — Clears the screen.
  • led(brightness) — sets the backlight to brightness. For me, full brightness is at 768. A value of zero turns the backlight off. If you don’t have the screen LED connected to one of the Raspberry Pi’s PWM pin, this will either be full on (for any brightness >= 1), or off, for brightness=0. This is used to fade up the screen in lines 24-26, and fade it down far too theatrically in lines 72-74.
  • show_image(PILImage) — display a single bit depth black and white Python Imaging Library object PILImage. This can be no larger than 84×48 pixels.
  • load_bitmap(file, Invert) — load a single bit depth black and white BMP file of maximum size 48×84. If Invert is true, keep the colours as they are, otherwise swap black and white to make a negative image. nokiSPI flips images by 90°, so the image I loaded to show the URL of the blog post looks like this:
    blogpost-nokia
    (I know, I could have generated this in code, but I’d already made the image using qrencode. I couldn’t be bothered working out the image size and offsets.)

The text handling functions I used are:

  • gotorc(row, column) — move the text cursor to row, column. The screen only has 14 columns by 8 rows if you use the standard 6×6 pixel font, so keep your text short to avoid disappointment.
  • text(text) — write text at the current cursor position.
  • centre_word(row, text) — write text centred in row row. Since the text rows are a maximum of 14 columns, text with an odd number of characters will appear slightly off-centre.

There are many more functions in the nokiaSPI class; watch the demo, have a dig through the source and see what you can use.

The Quite Rubbish Clock

Hey! This article is really old and probably doesn’t work any more: things have changed a lot in Raspberry Pi world since 2013

Update 3: code for the demo video is here.

Update 2: In which I actually post working code.

Update: Eep! This post was featured on the Raspberry Pi blog today. Thanks, Liz!

And now for something completely different:

… a clock that isn’t human readable. You’ll need a QR code reader to be able to tell the time.

Nokia screen on Raspberry Pi

This, however, is not the prime purpose of the exercise. I was looking for an excuse to try some direct hardware projects with the GPIO, and I remembered I had a couple of Nokia-style surplus LCDs lying about that could be pressed into service. These LCDs aren’t great: 84×48 pixels, 3V3 logic, driven by SPI via an 8-pin header which includes PWM-controllable LED backlighting. They are cheap, and available almost everywhere: DealExtreme ($5.36), SparkFun ($9.95), Adafruit ($10, but includes a level shifter, which you really need if you’re using a 5V logic Arduino), Solarbotics ($10) and Creatron (about $12; but you can walk right in and buy one). Despite being quite difficult to use, helpful people have written drivers to make these behave like tiny dot-addressable screens.

I’d been following the discussion on the Raspberry Pi forum about driving the Nokia LCD from a Raspberry Pi. Only when user bgreat posted some compact code that was supposed to run really fast did I dig out the LCD board and jumper wires. Building on bgreat’s nokiaSPI.py class and a few other bits of code, here’s what I built to make this singularly pointless clock:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# qrclock - The Quite Rubbish Clock for Raspberry Pi - scruss, 2013-01-19

import time
# need to use git://github.com/mozillazg/python-qrcode.git
import qrcode
from PIL import Image
import ImageOps
# uses bgreat's SPI code; see
# raspberrypi.org/phpBB3/viewtopic.php?f=32&amp;amp;amp;amp;t=9814&amp;amp;amp;amp;p=262274&amp;amp;amp;amp;hilit=nokia#p261925
import nokiaSPI

noki = nokiaSPI.NokiaSPI()              # create display device
qr = qrcode.QRCode(version=1,           # V.1 QR Code: 21x21 px
                   error_correction=qrcode.constants.ERROR_CORRECT_M,
                   box_size=2, border=1)
bg = Image.new('1', (84, 48))           # blank (black) image background

while 1:
    qr.clear()
    newbg = bg.copy()                   # copy blank background
    s = time.strftime('%Y-%m-%d %H:%M:%S')
    qr.add_data(s)                      # make QR Code of YYYY-MM-DD HH:MM:SS
    qr.make()
    qrim = qr.make_image()              # convert qrcode object to PIL image
    qrim = qrim.convert('L')            # make greyscale
    qrim = ImageOps.invert(qrim)        # invert colours: B-&amp;amp;amp;gt;W and W-&amp;amp;amp;gt;B
    qrim = qrim.convert('1')            # convert back to 1-bit
    newbg.paste(qrim, (18, 0))          # paste QR Code into blank background
    noki.show_image(newbg)              # display code on LCD
    time.sleep(0.4)                     # pause before next display

(Convenient archive of all the source: qrclock2.zip, really including bgreat’s nokiaSPI class this time …)

To get all this working on your Raspberry Pi, there’s a fair amount of configuration. The best references are bgreat’s own comments in the thread, but I’ve tried to include everything here.

Enabling the SPI kernel module

As root, edit the kernel module blacklist file:

sudo vi /etc/modprobe.d/raspi-blacklist.conf

Comment out the spi-bcm2708 line so it looks like this:

#blacklist spi-bcm2708

Save the file so that the module will load on future reboots. To enable the module now, enter:

sudo modprobe spi-bcm2708

Now, if you run the lsmod command, you should see something like:

Module                  Size  Used by
spi_bcm2708             4421  0

Installing the WiringPi, SPI and other required packages

WiringPi by Gordon is one of the neater Raspberry Pi-specific modules, as it allows relatively easy access to the Raspberry Pi’s GPIO pins. For Raspbian, there are a few other imaging libraries and package management tools you’ll need to install here:

sudo apt-get install python-imaging python-imaging-tk python-pip python-dev git
sudo pip install spidev
sudo pip install wiringpi

Installing the Python QR code library

Finding a library that provided all the right functions was the hardest part here. I ended up using mozillazg‘s fork of lincolnloop‘s python-qrcode module. mozillazg’s fork lets you use most of the lovely PIL methods, while the original hides most of them. Since I had to do some image compositing and colour remapping to make the image appear correct on the Nokia screen, the new fork was very helpful.

To install it:

git clone git://github.com/mozillazg/python-qrcode.git
cd python-qrcode/
sudo python ./setup.py install

The tiny 84×48 resolution of the Nokia screen doesn’t give you many options for sizing QR codes. For the time display of the clock, a 21×21 module Version 1 code with two pixels per module and one module margin just fits into 48 pixels. Using a medium level of error correction, you can fit the 19-character message (such as “2013-01-19 18:56:59”) into this tiny screen with a very good chance of it being read by any QR code reader.

(In the video, there’s a much larger QR code that’s a link to this blog post. That’s a Version 7 code [45×45 modules] at one pixel per module and no margin. This doesn’t meet Denso Wave’s readability guidelines, but the Nokia screen has large blank margins which seem to help. It won’t read on every phone, but you’re here at this link now, so you don’t need it …)

Wiring it all up

(Do I really need to say that you’ll be messing around with the inner delicate bits of your Raspberry Pi here, and if you do something wrong, you could end up with a dead Raspberry Pi? No? Okay. Just make sure you take some static precautions and you really should have the thing shut down and powered off.)

You’ll need 8 female-female right-angled ones). Note that the thick border of the LCD is the top of the screen. These boards are made who-knows-where by who-knows-whom, and there’s a huge variety of labels and layouts on the pins. My one appears to be yet another variant, and is labelled:

  1. VCC
  2. GND
  3. SCE
  4. RST
  5. D/C
  6. DNK(MOSI)
  7. SCLK
  8. LED
screen labels

This is how I wired it (from comments in bgreat’s code and the GPIO reference):

 LCD Pin       Function      Pi GPIO Pin #   Pi Pin Name
============= ============= =============== =============
 1 VCC         Vcc            1              3.3 V
 2 GND         Ground        25              GND
 3 SCE         Chip Enable   24              GPIO08 SPI0_CE0_N
 4 RST         Reset         11              GPIO17
 5 D/C         Data/Command  15              GPIO22
 6 DNK(MOSI)   Data In       19              GPIO10 SPI0_MOSI
 7 SCLK        Serial Clock  23              GPIO11 SPI0_SCLK
 8 LED         Backlight     12              GPIO18 PWM0
GPIO wiring
back of screen

Wire it up, and fire up the program:

sudo ./qrclock.py

Yes, code that accesses GPIO needs to be run as root. Pesky, but helps you avoid running code that accidentally scrams the nuclear power station you’re controlling from your Raspberry Pi …

Too many QR Codes

I have, of late, been rather more attached to QR Codes than might be healthy. I’ve been trying all sorts of sizes and input data, printing them, and seeing what camera phones can scan them. I tried three different devices to scan the codes:

  • iPhone 4s – 8 MP, running either i-nigma (free) or Denso Wave’s own QRdeCODE ($2). QRdeCODE is better, but then, it should be, since it was created by the developer of the QR Code standard.
  • Nexus 7 – 1.2 MP, running Google Goggles.
  • Nokia X2-01Catherine‘s new(ish) phone, which I can’t believe only has a 0.3 MP VGA camera on it. Still, it worked for a small range of codes.

QR Code readability is defined by the module size; that is, the number of device pixels (screen or print) that represent a single QR Code pixel. Denso Wave recommends that each module is made up of 4 or more dots. I was amazed that the iPhone could read images with a module size of 1 from the screen, like this one:

hello_____-ei-m01-300dpi

On this laptop, one pixel is about 0.24 mm. The other cameras didn’t fare so well on reading from the screen:

  • iPhone 4s – Min module size: 1-2 pixels (0.24-0.48 mm/module)
  • Nexus 7 – Min module size: 2-3 pixels (0.48-0.72 mm/module)
  • Nokia X2-01 – Min module size: 3-4 pixels (0.72-0.96 mm/module)

So I guess for screen scanning, Denso Wave’s recommendation of 4 pixels/module will pretty much work everywhere.

I then generated and printed a bunch of codes on a laser printer, and scanned them. The results were surprisingly similar:

  • iPhone 4s – Min module size: 3-4 dots (0.25-0.34 mm/module)
  • Nexus 7 – Min module size: 4-5 dots (0.34-0.42 mm/module)
  • Nokia X2-01 – Min module size: 8-9 dots (0.68-0.76 mm/module)

A test print on an inkjet resulted in far less impressive results. I reckon you need to make the module size around 25% bigger on an inkjet than a laser, perhaps because the inkjet is less crisp.

I have to admit I went a bit nuts with QR Codes. I made a Vcard: my vcard

(and while I was at it, I created a new field for ham radio operators: X-CALLSIGN. Why not?). I even encoded some locations in QR Codes.

Just to show you what qrencode can do, here’s a favourite piece of little prose:

a_real_man

#swagfail

Put me out to pasture, my conference swag skills are failing.

I picked this up at Solar Power International:

I thought I was picking up a USB memory stick, as I’d nabbed one in the same form factor before. Break off the backing card at the hinge, and you’ve got a nice tiny data store like the Kingmax ones I used to use.

On plugging it into my Mac, a couple of icons bipped on my dock, then Skype opened. Wat? More importantly, there was no storage to be seen, so once my virus fears had subsided a bit, I was determined to find out what this pointless piece of plastic was doing.

The stick identified itself to the system as an Apple keyboard (USB ID 05ac:020b), and spits out the following characters (captured by cat and xxd on my Raspberry Pi):

0000000: 1b72 1b5b 317e 1b5b 3477 7777 2e62 757a  .r.[1~.[4www.buz
0000010: 7a63 6172 642e 7573 2f73 6365 2d32 3230  zcard.us/sce-220
0000020: 0a                                       .

After reading about evil USB dongles, it seems that the Ctrl-R keypress it’s sending is the Windows “Open Browser” command, and then opens the url www.buzzcard.us/sce-220. This link redirects to www.plugyourbrand.com/gosolar_sce/index.html?u=220, which appears to do some Flash/JS stuff which I don’t want to understand.

The funny thing is, the card has the perfectly respectable www.GoSolarCalifornia.ca.gov (well, respectable if you consider a US .gov website as such) link printed on it. Even printing a card with a QR code linking to that address would be less opaque.

(This is not a link to goatse, honest.)

As is, a bunch of plastic was wasted in vain just to save people typing an URL. We’re all going to die, and it really is your fault …