A 360 leds clock

May 07, 2018

Previously I talked about a LED box. I explained how to operate a ws2812 led strip. Thoses strips can be connected together in serial.
After a first try one year ago, I built this clock from scratch with 360 leds, divided in 3 parts.

Mecanical

My first try was not a success. I bought some 1cm wide bars. Those aluminium bars can be easily bent in circles. However it is impossible to have a perfect circle. If the circle is wide the shape seems good. With smaller circles (diameter < 50cm) it looks bad.

Back to the basis : wood circles ! With a jigsaw (thanks @bluxte) and a simple wire it is the best way for perfect circles. I learned that at the age of 6 :-). I made 3 concentric circles : one with 60 leds, a second with 120 and the last have 180 leds.

There is 60 leds per meter on this strip. I need a 31,8cm diameter circle (100 / 3,14). I glued the strip on the edge.

Behind I removed some wood where the wires reach. On the final result, no wires are visible.

Wood circles are glued together after wiring. Remember for next time : painting will be far easier before gluing leds...

Wiring

Power Supply

ws2812 need a 5 volts power supply, with 20mA consumption per LED. 360x0.02 = 7.2 A so 36W ! It may be very bright ! I found a 50w power supply on Amazon.

I have 3 separated strips. Each strip is powered by 2 points on each end. 
On my first mount, I soldered 2 wires (+5v and GND) at the begining of the strip. A strip with a length greater than 2 meters need a redundant power. 

Signal

The wide circle have LEDs from 1 to 180, the mid 181 to 300 and the smallest 301 to 360.

Signal wire is plugged on a PWM output on a raspberry pi. 

leds numbers

First tests

Get the library and install it : https://github.com/jgarff/rpi_ws281x

Example files written in python are great for testing. Put the right configuration : 

# LED strip configuration:
LED_COUNT = 360 # Number of LED pixels.
LED_PIN = 18 # GPIO pin connected to the pixels (must support PWM!).
LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
LED_DMA = 10 # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest
LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL = 0
LED_STRIP = ws.SK6812_STRIP_RGBW

in blue

Mounted on the wall :

in white

Clock modes

I implemented 3 different clock modes.

Simple

On the biggest circle, I have 180 leds. My implementation turns on 3 leds by second. I need to compute an offset. Led number 1 is located at the bottom. So I inserted a "180 degrees offset". The event queue contains brightness.

    def simple_clock(hours, minutes, seconds):
      global strip
      global q
      global brightness
      if not q.empty():
        brightness = q.get()
      strip.setBrightness( brightness )
    
      # show seconds :
      for i in range(0, 59):
        offset = i + 29
        # show in hours color elapsed
        if offset >= 60:
          offset = offset - 60
        if i == seconds:
          strip.setPixelColor((offset*3)-1, Color(0,255,0))
          strip.setPixelColor((offset*3), Color(0,255,0))
          strip.setPixelColor((offset*3)+1, Color(0,255,0))
        else:
          strip.setPixelColor((offset)*3-1, Color(0,0,0))
          strip.setPixelColor((offset*3), Color(0,0,0))
          strip.setPixelColor((offset*3)+1, Color(0,0,0))
Do the same with minutes, with leds from 181 to 300 :

        # same for minutes, with an offset
        for i in range(0,60):
          offset = i + 30
          # show in green color elapsed
          if offset >= 60:
            offset = offset - 60
          if i == minutes :
              strip.setPixelColor((offset*2)+180, Color(255,0,0))
              strip.setPixelColor((offset*2+1)+180, Color(255,0,0))
          else :
            strip.setPixelColor((offset*2)+180, Color(0,0,0))
            strip.setPixelColor((offset*2+1)+180, Color(0,0,0))      
Then the hours, from leds 301 to 360.

    # hours
    for i in range(0,60):
        if hours > 12:
            hours = hours - 12
        if hours == 0:
            hours = 12
        offset = i + 30
        if offset >= 60:
            offset = offset - 60
        #show in blue
        index = hours *5 -1
        if (i == index) :
            strip.setPixelColor(offset + 300 , Color(0,0,255))
        else:
            strip.setPixelColor(offset + 300 , Color(0,0,0))
Define an infinite loop for a mode, with a queue for input events such as brightness adjustment.

    def loop_simple_clock(queue):
    global brightness
    global q
    q = queue
    brightness = q.get()
    # LED strip configuration:
    LED_COUNT = 360      # Number of LED pixels.
    LED_PIN = 18      # GPIO pin connected to the pixels (must support PWM!).
    LED_FREQ_HZ = 800000  # LED signal frequency in hertz (usually 800khz)
    LED_DMA = 5       # DMA channel to use for generating signal (try 5)
    LED_BRIGHTNESS = 255  # Set to 0 for darkest and 255 for brightest
    # True to invert the signal (when using NPN transistor level shift)
    LED_INVERT = False
    global strip
    strip = Adafruit_NeoPixel(
    LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
    # Intialize the library (must be called once before other functions).
    strip.begin()

    # Shutdown all leds
    for i in range(0, strip.numPixels(), 1):
      strip.setPixelColor(i, Color(0, 0, 0))

    strip.show()
    while 1 :
      localtime = time.localtime()
      simple_clock(localtime.tm_hour, localtime.tm_min, localtime.tm_sec)
      time.sleep(0.5)
And 2 REST API endpoints :

    @app.route('/simple', methods=['POST'])
    def simple():
        global p
        global q
        q.put(lastBrightness)
        if p is not None:
            p.terminate()
        p = Process(target=loop_simple_clock, args=(q,))
        p.start()
        return "ok"

    @app.route('/bright', methods=['POST'])
    def bright():
        global q
        q.put( int(request.args.get("brightness")))
        lastBrightness = int(request.args.get("brightness"))
        return "ok"

Continuous

This mode is almost the same than the simple :

Simply change a condition to :

    if (i <= index) :
      strip.setPixelColor(offset + 300 , Color(0,0,255))
    else:
      strip.setPixelColor(offset + 300 , Color(0,0,0))
For minutes, hours and seconds.

Minimal

Minimal clock is a "hiddle" mode. It shows on the little circle hours, minutes and seconds.

With this mode, I can show every color I want on the 300 other leds ! Hours tooks 3 blue leds, minutes 2 green leds and seconds only one red.

    # show seconds :
    for i in range(0, 60):
        if hours > 12:
            hours = hours - 12
        if hours == 0:
            hours = 12
    
        red = 0
        blue = 0
        green = 0
        offset = i + 29
        # show in hours color elapsed
        if offset >= 60:
            offset = offset - 60
        if i == seconds:
            red=255
        if (i == minutes) or (i == minutes-1):
            green=255
        if (i == hours*5) or (i == (hours*5-1)) or (i == (hours*5+1)) :
            blue=255
    
        strip.setPixelColorRGB((offset)+300, red, green, blue)
In the loop, I intialize the strip (from 1 to 300) with a fixed color.

    def loop_fixed_color(red, green, blue, queue, strip):
    usleep = lambda x: time.sleep(x/1000000.0)
    global q
    global brightness
    q = queue
    brightness = q.get()
    print strip
    
    if not queue.empty():
        brightness = q.get()
    strip.setBrightness( brightness )
    for i in range(0, 300, 5):
        for j in range( 0, 5):
        strip.setPixelColor(i+j, Color(red, green, blue ))
        strip.show()
        usleep(2)
    strip.show()
    while 1 :
        localtime = time.localtime()
        minimal_clock(localtime.tm_hour, localtime.tm_min, localtime.tm_sec, strip)
        if not queue.empty():
        brightness = q.get()
        strip.setBrightness( brightness )
        time.sleep(0.5)

APIs

    # show a simple clock
    POST /simple

    # switch to continuous mode
    POST /continuous

    # switch to minimal mode
    POST /minimal

    # set bightness
    GET /bright 

Web remote control

I am not very familiar with frontend development. So I made something simple and functionnal.

Slider are not included in bootstrap.
http://seiyria.com/bootstrap-slider/ does the job perfectly with a perfect integration.
There is no Android native application for now. Only a shortcut to the webpage is enough.