Fixie Clock

The Fake ‘Nixie’ clock

So I had an idea about making fake nixie tubes with an OLED display and a laser etched filter.

I had a play for Makevember and I was quite pleased with the outcome using a 128×64 bit white I2c OLED display.

The first test was with a plain acrylic etched peice

I also engraved the numbers on the acrylic so it looked like the wires in a nixie tube, and started playing with casings and ways of mounting the OLED. I found a pack of 5 small hexagonal jars were ideal, the flat faces didn’t distort the displays as much as the round ones did.

As this was going well I ordered some orange acrylic, I also found online the NixieOne.ttf font, Ideal…

Fixie First test

As this looked so good, I thought I’d build a clock (what else do you do to prove the fixies “Fake Nixies” ) were workable. As I had a PIZeroW available I based the clock on that.

The problem is, by default the 0.96″ OLED displays can only be assigned 2 I2C addresses, and the RPI only has a single I2C bus (without software emulating I2C). I would need four displays at minimum for a clock. Luckily Pimoromi (and Adafruit) do an I2C multiplexer breakout, that allows you to connect 8 I2C busses to a single I2C bus. A bit of software playing later.

OK that works. I’d also realised that as I had 5 jam jars I may as well do seconds too. I only had a single jam jar for seconds, so it had to display both digits (This is great as it confuses real nixie fans when they see a nixie that does 0-99)

I started building a case from laser cur MDF to try to keep things tidy as I was developing (at this point I really didn’t know where I was going with this)

Of course, you get side tracked. I started to think about chiming. So I added a driver board from and ExtKits Solenoid engine, a solenoid and some brass.

With some delicate timing I could get the solenoid to hit the brass quickly enough to make a reasonable bong.

BONG!

A view without the solenoid.

And a note to self, not to connect the 12v supply for the solenoid the wrong way around 🙂

As things were coming together I started to look at the aesthetics, and laser cut / etched a front panel.

Stained the clock and front panel with a dark wood stain, and highlighted the etched pieces with gold acrylic paint

and added a top piece to the clock to both hide the bottoms of the jam jars and keep them together with a strip of brass to hide the tangs holding the clock together and some feet.

The finished clock

Software

 

Config File – oledclockconfig.py

#12/24hour
twelvehour=0

#gpio for bongs
BONGPIN=12
#hour to start bonging from (till 00:00)
BongFromHour=9 

# Raspberry Pi pin configuration:
RST = 24

#define display font
from PIL import Image,ImageDraw,ImageFont

#font for Nixies
fontb = ImageFont.truetype('/home/NixieOne.ttf', 110)
fontl = ImageFont.truetype('/home/NixieOne.ttf', 78)

#font for 7 segs
#fontb = ImageFont.truetype('/home/digital-7.ttf', 110)
#fontl = ImageFont.truetype('/home/digital-7.ttf', 78)

#define display size
width=128
height=64


#global variables
#Maximum Displays to initalise
MaxDisplays=5

#Global display object
disp =[None] * MaxDisplays


Routines – oledclockroutines.py

import smbus
import Adafruit_SSD1306
import oledclockconfig

from PIL import Image,ImageDraw,ImageFont

def DrawBigNumber(bus,number,chan):
    SelectI2C(bus,chan)
    oledclockconfig.disp[chan].clear()
    image = Image.new('1', (oledclockconfig.width, oledclockconfig.height))

    i1 = Image.new('1', ( oledclockconfig.width,oledclockconfig.width))
    oledclockconfig.draw = ImageDraw.Draw(i1)
    num=number % 10
    oledclockconfig.draw.text((1, 1),    str(num),  font=oledclockconfig.fontb, fill=255)

    i2= i1.rotate(270) # needs shift
    #bodge to centre 1
    if num==1:
      image.paste(i2,(0,14)) # rot 270
    else:
      image.paste(i2,(0,-2)) # rot 270

    oledclockconfig.disp[chan].image(image)
    oledclockconfig.disp[chan].display()

def DrawLittleNumber(bus,number,chan):
    SelectI2C(bus,chan)
    oledclockconfig.disp[chan].clear()
    image = Image.new('1', (oledclockconfig.width, oledclockconfig.height))

    i1 = Image.new('1', ( oledclockconfig.width,oledclockconfig.height))
    oledclockconfig.draw = ImageDraw.Draw(i1)
    num=number % 100
    oledclockconfig.draw.text((20, -12),    str(num // 10),  font=oledclockconfig.fontl, fill=255)
    oledclockconfig.draw.text((70, -12),    str(num % 10),  font=oledclockconfig.fontl, fill=255)

    oledclockconfig.draw.text((21, -11),    str(num // 10),  font=oledclockconfig.fontl, fill=255)
    oledclockconfig.draw.text((71, -11),    str(num % 10),  font=oledclockconfig.fontl, fill=255)

    i2= i1.rotate(180)
    image.paste(i2,(0,0)) # rot 180

    oledclockconfig.disp[chan].image(image)
    oledclockconfig.disp[chan].display()

def DrawLittleText(bus,text,chan):
    SelectI2C(bus,chan)
    oledclockconfig.disp[chan].clear()
    image = Image.new('1', (oledclockconfig.width, oledclockconfig.height))

    i1 = Image.new('1', ( oledclockconfig.width,oledclockconfig.height))
    oledclockconfig.draw = ImageDraw.Draw(i1)
#    num=number % 100
    oledclockconfig.draw.text((20, -12),    text,  font=oledclockconfig.fontl, fill=255)
#    oledclockconfig.draw.text((70, -12),    str(num % 10),  font=oledclockconfig.fontl, fill=255)

#    oledclockconfig.draw.text((21, -11),    str(num // 10),  font=oledclockconfig.fontl, fill=255)
#    oledclockconfig.draw.text((71, -11),    str(num % 10),  font=oledclockconfig.fontl, fill=255)

    i2= i1.rotate(180)
    image.paste(i2,(0,0)) # rot 180

    oledclockconfig.disp[chan].image(image)
    oledclockconfig.disp[chan].display()


def SelectI2C(bus,chan):
  chanb=(1<<chan)
  bus.write_byte_data(0x70,0,chanb)

def InitDisplay(bus,chan):
  global width,height,disp
  print ("INIT Display")
  print (chan)
  SelectI2C(bus,chan)
  # 128x32 display with hardware I2C:
  #disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST)

  # 128x64 display with hardware I2C:
  oledclockconfig.disp[chan] = Adafruit_SSD1306.SSD1306_128_64(rst=oledclockconfig.RST, i2c_address=0x3C)

  # Initialize library.
  oledclockconfig.disp[chan].begin()

  # Clear display.
  oledclockconfig.disp[chan].clear()
  oledclockconfig.disp[chan].display()

#  oledclockconfig.width = disp[chan].width
#  oledclockconfig.height = disp[chan].height

Main – oledclock.py

#Install
#python < 3
#sudo python -m pip install --upgrade pip setuptools wheel
#sudo pip install Adafruit-SSD1306
#
#or
#
#python >= 3
#sudo python -m pip3 install --upgrade pip setuptools wheel
#sudo pip3 install Adafruit-SSD1306
#
#settings in oledclockconfig.py
#
#

import time
from datetime import datetime
import smbus
import threading
import RPi.GPIO as GPIO
import oledclockroutines
import oledclockconfig

GPIO.setmode(GPIO.BCM)
GPIO.setup(oledclockconfig.BONGPIN, GPIO.OUT) #pin12

#init i2c bus
bus = smbus.SMBus(1)    # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)

def bongs(name):
   now = datetime.now()
   hour=now.hour
   bongs=hour % 12
   if (bongs==0): bongs=12
   print ("Bongs")
   for x in range(bongs):
     GPIO.output(oledclockconfig.BONGPIN, 1)
     time.sleep(0.009)
     GPIO.output(oledclockconfig.BONGPIN, 0)
     time.sleep(1.5)

#init the displays
for c in range(0,oledclockconfig.MaxDisplays):
  oledclockroutines.InitDisplay(bus,c)

#init variables
oldsec=0
oldmin=0

#main thread
if __name__ == "__main__":
  while(1):  
    for n in range(10):
      now = datetime.now()
      sec = now.second

      if (sec!=oldsec):  #if seconds changed do display
        oldsec=sec
        min=now.minute
        hour=now.hour
        oledclockroutines.DrawLittleNumber(bus,sec,0) #display seconds
        print (now)    

        if (min!=oldmin): #if minutes changed do mins/hours display
          oldmin=min
          minH=min % 10
          minL=min // 10
          if (oledclockconfig.twelvehour):hour=hour % 12 #12 hour clock?
          hourH=hour % 10
          hourL=hour // 10
          #display minutes
          oledclockroutines.DrawBigNumber(bus,minH,1)
          oledclockroutines.DrawBigNumber(bus,minL,2)
          #display hours 
          oledclockroutines.DrawBigNumber(bus,hourH ,3)
          oledclockroutines.DrawBigNumber(bus,hourL,4) 
        #bongs  
        if (hour>oledclockconfig.BongFromHour): #bong only from 7 to midnight
          if (min==0 and sec==0):
            #if we need bongs start a new bong thread
            x = threading.Thread(target=bongs, args=(1,))
            x.start()
    
      time.sleep(0.2) #DFA for 200ms