Introduction: Weather/Matrix Lamp

In this Instructable I describe the design, construction and programming of a LED matrix lamp. The design resembles an ordinary lamp, but the interior has been replaced by a matrix of ws2812 LEDs. The control is done by means of a Raspberry Pi, so that the whole can be programmed according to your own wishes.

The lamp is about 12 inch (30 cm) high with a diameter of 4 inch (10 cm). The outside mainly consists of a glass cylinder.

With more than 40 steps it has become quite a comprehensive Instructable. It starts with the design of the lamp. This covers both the 3D design in Fusion 360, and the electrical part. Extra attention is given to the power consumption of the LEDs. For example, a special board has been designed for power distribution.

After the design the Instructable continues with the assembly of the various parts: The LED holder and the lamp foot. The LED holder contains 16 strips with 18 LEDs each, giving a total of 288 LEDs. The lamp base contains the Raspberry Pi, a small fan and additional electronic components.

In addition to designing and building, the programming of the lamp is described. This starts with controlling the LEDs and retrieving weather data with Python. Followed by the different functions of the lamp.


The primary function of this lamp is to display weather data. Due to the chosen design it is possible to use this lamp for other purposes. Like a clock or social media indicator (The Python code for an emergency light and lava lamp is included in this Instructable).

Step 1: First Sketches & Desing

About a year ago I made some Illuminated Christmas Tree Ornaments. These contained a web interface to alter the colors of the LEDs. In a later version, this web interface has been replaced by the usage of weather data. The color of the LEDs depend on the outside temperature, with all LEDs having the same color.

Later I got the idea to make a 'thermometer'. By reading the actual, minimum and maximum temperature. All the LEDs would have different colors depending on these values. This has never been developed into a working prototype because I got another idea, which resulted in this LED matrix lamp. Where displaying weather data is only one of the possibilities.

While making some sketches, I came up to the following functions:

  1. Display the current temperature.
  2. Displaying the expected minimum and maximum temperatures.
  3. Displaying the expected precipitation for the next hour (blue = rain, white = snow).
  4. Displaying the current wind speed, and if possible direction.

The drawings above are a first design of this lamp.

The possibilities of this lamp are not limited to displaying weather data. Using a Raspberry PI gives many more possibilities. Such as a clock, a plasma or lava lamp, and several social media indicators.


There are 2 ways to place the LEDs inside a Lamp: A square grid or a spiral of LEDs. The spiral version is easier to build. But the LEDs slope a little when using a spiral, and therefore looks less beautiful. Beside, the color gradient will be harder to program. That's why I've chosen to create a LED grid using ws2812 LED strips.

The ws2812 LED strip is placed vertically through the lamp, in a zigzag pattern. All LED connections are at the top or bottom of the cylinder. This gives space inside the cylinder, for the other electronic components.

Because the first idea was to display weather data, I have chosen for 16 LEDs per row. This allows for 16 wind directions:

  • N
  • NNE
  • NE
  • ENE
  • E
  • ESE
  • SE
  • SSE
  • S
  • SSW
  • SW
  • WSW
  • W
  • WNW
  • NW
  • NNW

The previous project "Christmas tree ornament" has been based on a regular icosahedron, with a circular window for each LED. This project gets a similar structure for the LEDs. But then inside a glass cylinder.

Step 2: LED Colors

The temperature during a year in the Netherlands is approximately between -10 and +30 degrees Celsius. It can get warmer or colder, but these are exceptions. The universal temperature colors are Red for hot, and Blue for cold. I've added a third color: Yellow. This gives more colors and makes the gradient more beautiful.

The minimum and maximum temperatures change during the seasons. As a result, the temperature difference is never greater than 25 degrees. In other words, almost half of the entire color range. To increase this range, a dynamic scale can be used. For example, the scale may depend on the month. The color blue can be 10 degrees Celsius in the summer, and -10 degrees Celsius in the winter.

This scale should change slowly. For example:

January    -10 to +15
February   -10 to +15
March       -5 to +20
April       -5 to +20
May          0 to +25
June        +5 to +30
July       +10 to +35
August     +10 to +35
September   +5 to +30
October      0 to +25
November    -5 to +20
December   -10 to +15

The translation between temperature and color can be stored in a table. As a result, minimal calculation is required. And the lamp is easy to adapt to other climates. A table makes is also simple to make small adjustments in color intensity.

Step 3: Glass Cylinder

A glass cylinder has been used for this lamp. It is a spare part of a well-available lamp. I've bought the lamp from a Dutch web store. It has the following specifications:

Dimensions:     10 cm diameter at +/- 27 cm high
Colour:         Milky white
Fitting:        Hole size E27 (normal / large fitting) 4 cm
Material:       Glass
Remarks:        Suitable for hanging lamps as well as floor lamps. 
                On one side is the hole for the connection,
                the other side is open.
Delivery time:  About 2 weeks (from Austria)

The glass cylinder belongs to lamps of the 'Troy' type. Which are made by a company with the name Eglo.

If the glass cylinder is not sold separately, it's also possible to buy the lamp itself. There are a pendant and a table version available (USA-link, UK-link, EU-link).

It's always possible to make your own version using another lamp.


Despite the simple image for the dimensions, they are correct. The height is 270 mm (10.6 inch) and the diameter is 100 mm (3.9 inch).

Step 4: Wemos Web Interface

One of the first real tests (before the final assembly) has been made with a Wemos board. I've used the same code as the Christmas Ornaments, and only changed the number of LEDs.

Step 5: Weather Underground

This Instructable started with an idea to create a weather controlled lamp. This requires the lamp to retrieve the actual weather. Completed with the weather forecast of the day. There are several websites which provide this information. I've choosen to use the Weather Underground website.


The Weather Underground website gives a lot weather of information. And the page for your location shows the local weather in different tabs. The webpage is made to be displayed with your browser. And It's not optimized to be read by an automated process like this project. That's why some websites provide an application programming interface (API). This API provides access to all weather data in a format which is readable by an automated process.

Accessing the Weather Underground API requires registration. And there are three plans to choose from:

  • Stratus plan
    • Geolookup
    • Autocomplete
    • Current conditions
    • 3-day forecast summary
    • Astronomy
    • Almanac for today
  • Cumulus plan
    • 10-day forecast summary
    • Hourly 1-day forecast
    • Satellite thumbnail
    • Dynamic Radar image
    • Severe alerts
    • Tides and Currents
    • Tides and Currents Raw
    • Severe alerts
  • Anvil plan
    • Hourly 10-day forecast
    • Yesterday's weather summary
    • Travel Planner
    • Webcams thumbnails
    • Dynamic animated Radar image
    • Dynamic animated Satellite image
    • Current Tropical Storms

All plans can be used for free (for developers). The only limit is the number of calls per day (500) and calls per minute (10). Purchasing a plan, and registering, returns an API key. This is a string which looks like: "3a5dff6263687ce3". This string (Your_Key) is used for identification, and is required to retrieve weather data from the website.

Accessing the website with this key return a xml-file or json-file (use your own key):

http://api.wunderground.com/api/3a5dff6263687ce3/conditions/q/NL/Enschede.json
http://api.wunderground.com/api/3a5dff6263687ce3/conditions/q/NL/Enschede.xml

The information from this files will be used to alter the colors of the lamp.


Using the Weather Underground API requires a key and a location. Finding your nearest weather station is well documented. I've choosen to use the weather station near the Twenthe Air Base. This airport has the IATA code ENS. Using the geolookup function shows several weather stations. And one of them is on the airbase.

http://api.wunderground.com/api/Your_Key/geolookup/q/ENS.json

                {
		"name": "Twenthe Air Base",
		"city": "Twenthe Air Base",
		"state": "OV",
		"country": "NL",
		"country_iso3166":"NL",
		"country_name":"Netherlands",
		"zmw": "00000.150.06290",
		"l": "/q/zmw:00000.150.06290"
		}

The following API call gives all hourly information for this airbase:

http://api.wunderground.com/api/YourKey/hourly/q/NL/Twenthe%20Air%20Base.xml

The spaces in the name are replaced by %20 (hexadecimal ASCII value).


Documentation about the API can be found on the Weather Api Documentation page.

Step 6: Raspberry PI

Installing the Raspberry Pi is well described on the raspberry.org website. Download the "Raspbian stretch lite"-image from the Rasberry Pi download page. Unzip the file, and use Win32Diskimager to copy the files to a (micro) SD card.

The WiFi setup can be done by placing two files on the boot partition of the SD card, there is no need for a monitor, keyboard and mouse. The first file is an empty file named "ssh". Make sure the file has no file-extension (.txt). The second file "wpa_supplicant.conf" contains your WiFi SSID and password.

country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="your_wifi_ssid"
    scan_ssid=1
    psk="your_password"
    key_mgmt=WPA-PSK
}

Place the SD card in the raspberry pi, and power on the Raspberry Pi. Now wait until the green LED stops blinking (disk activity). This takes a few minutes.

Log in to your WiFi router and find the IP-address for the host with the name "raspberrypi". Use Putty to connect to the raspberry pi. Login as "pi" user with the password "raspberry".


The following statements upgrade all software on the Raspberry Pi:

sudo apt-get install rpi-update
sudo rpi-update
sudo reboot


sudo apt-get update
sudo apt-get upgrade
sudo apt-get autoremove
sudo reboot

After upgrading it's time to setup the Raspberry Pi, and to disable some services.

sudo raspi-config

Alter the following settings:

  • Change password: My_Password
  • Hostname: WeatherLamp
  • Change locale: Timezone
  • Advanced options: Audio: HDMI

This is a so called Headless configuration, there is no need for HDMI output. We can save some power usage by disabling the HDMI port. Open the rc.local file:

sudo nano /etc/rc.local

Add the following lines before the "exit 0" command:

# Disable HDMI
/usr/bin/tvservice -o

The Raspbian lite-image already has a minimum of background services. There is no need to stop any of them.

sudo service --status-all | grep +
 [ + ]  avahi-daemon
 [ + ]  bluetooth
 [ + ]  cron
 [ + ]  dbus
 [ + ]  dhcpcd
 [ + ]  dphys-swapfile
 [ + ]  fake-hwclock
 [ + ]  kmod
 [ + ]  networking
 [ + ]  procps
 [ + ]  raspi-config
 [ + ]  rsyslog
 [ + ]  ssh
 [ + ]  triggerhappy
 [ + ]  udev

Reboot the Raspberry Pi for the changes to take effect.


The following packages are required for the Python software (Python v2 and v3):

sudo apt-get install python-dev  python-setuptools  python-pip  python-virtualenv
sudo apt-get install python3-dev python3-setuptools python3-pip python3-virtualenv 
sudo apt-get install git scons swig
sudo apt-get install python3-dev python3-rpi.gpio
sudo pip install --upgrade pip
pip3 install colour
pip  install colour

Now the Raspberry Pi is ready to use.


The ws2812 LEDs require an additional library. This can be downloaded or installed from Github.

The following commands installs the required library (Python v2 and V3):

git clone https://github.com/jgarff/rpi_ws281x.git
cd rpi_ws281x 
scons
cd python 
sudo python  setup.py install
sudo python3 setup.py install

The following python program tests a single ws2812 LED on gpio port 18:

# TestLed.py
import time
from neopixel import *

LEDS        = 1      # Single LED
PIN         = 18     # GPIO 18 / PIN 12
BRIGHTNESS  = 255    # min 0 / max 255

stick = Adafruit_NeoPixel(LEDS, PIN, 800000, 5, False, BRIGHTNESS)
stick.begin()

for r in range (0, 192, 16):
    for g in range (0, 192, 16):
        for b in range (0, 192, 16):
            stick.setPixelColor(0,Color(r,g,b))
            stick.show()
            time.sleep(.1)

stick.setPixelColor(0,Color(255,255,255))
stick.show()

Connect the LED to the +5 Volt, ground and gpio18 (pin 12), and start it with "sudo python TestLed.py" (Python v2) or "sudo python3 TestLed.py" (Python v3).

Step 7: Weather Forecast

There are some code examples in the Weather Underground documentation. And the next code part is an updated (Python 3) version of the Weather Underground Python sample code:

from urllib.request import urlopen
import json

f = urlopen('http://api.wunderground.com/api/Your_Key/conditions/q/CA/San_Francisco.json')
json_string = f.read()
parsed_json = json.loads(json_string.decode('utf-8'))
temp_c = parsed_json['current_observation']['temp_c']
temp_f = parsed_json['current_observation']['temp_f']
print (temp_c , " " , temp_f)
f.close()

The response of this request contains one line with the current temperature in San Francisco:

19,4 67,0

The previous example returns a single value from the url/json-file. This lamp requires more input values. The following example retrieves the temperature, wind and rain conditions for the Twente Air Base.

from urllib.request import urlopen
import json

f = urlopen('http://api.wunderground.com/api/YourKey/geolookup/forecast/q/NL/Twenthe%20Air%20Base.json')
json_string = f.read()
forecast_json = json.loads(json_string.decode('utf-8'))

temp_high0 = forecast_json['forecast']['simpleforecast']['forecastday'][0]['high']['celsius']
temp_low0  = forecast_json['forecast']['simpleforecast']['forecastday'][0]['low']['celsius']
print ('Temperature today between {0} and {1}'.format(temp_low0, temp_high0))

temp_high1 = forecast_json['forecast']['simpleforecast']['forecastday'][1]['high']['celsius']
temp_low1  = forecast_json['forecast']['simpleforecast']['forecastday'][1]['low']['celsius']
print ('Temperature tomorrow between {0} and {1}'.format(temp_low1, temp_high1))

f.close

f = urlopen('http://api.wunderground.com/api/YourKey/geolookup/conditions/q/NL/Twenthe%20Air%20Base.json')
json_string = f.read()
conditions_json = json.loads(json_string.decode('utf-8'))

temp_now = conditions_json['current_observation']['temp_c']
print ('Current temperature is {0}'.format(temp_now))

wind_dir = conditions_json['current_observation']['wind_dir']
wind_kph = conditions_json['current_observation']['wind_kph']
print ('Wind speed is {0} kph from the {1}'.format(wind_kph, wind_dir))

# actual rain
real_rain = conditions_json['current_observation']['precip_1hr_metric']
print ('Rain past hour {0}'.format(real_rain))

f.close

f = urlopen('http://api.wunderground.com/api/YourKey/geolookup/hourly/q/NL/Twenthe%20Air%20Base.json')
json_string = f.read()
hourly_json = json.loads(json_string.decode('utf-8'))

# Quantitative Precipitation Forecasts
hour_rain = hourly_json['hourly_forecast'][0]['qpf']['metric']
print ('Expected rain next hour {0}'.format(hour_rain))

hour_snow = hourly_json['hourly_forecast'][0]['snow']['metric']
print ('Expected snow next hour {0}'.format(hour_snow))

f.close()

Executing this code gives the following data:

Temperature today between 1 and
Temperature tomorrow between 2 and 6
Current temperature is 3.5
Wind speed is 0 kph from the SW
Rain past hour  0
Expected rain next hour 0
Expected snow next hour 0

One of the values for today can be empty. Then that temperature has already been reached for that day. This can be solved by using tomorrows temperature.

The code makes 3 calls to the Weather Underground website. The number of calls is limited to 500 calls per day and 10 calls per minute. But not every value has to be updated each time. The daily forecast can be limited to once an hour.

Step 8: Simulated LEDs

In order to be able to start with the code during the construction of the lamp, I started with a NeoPixel simulation on a Windows Computer. The following code draws a single square on the screen, and the color of the "LED" can easily be changed by calling the method.

import pygame, time, sys
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode ((256,96),0,32)
screen.fill ((255,255,255))
pygame.display.set_caption ('NeoPixel LED')

colorlist = (0,16,32,64,96,128,160,192,224,255)

class WS2812:
    def __init__(self,color,x,y,screen):
        self.c = color
        self.x = x
        self.y = y
        self.screen = screen
        self.draw()

    def draw(self):
        led = pygame.draw.rect (screen,self.c,(self.x,self.y,32,32))
        pygame.display.update ()

    def color(self,color):
        self.c = color
        self.draw ()

led = WS2812 ((0,0,0),104,32,screen)
time.sleep(1)

for r in colorlist:
    for g in colorlist:
        for b in colorlist:
            led.color ((r,g,b))
            time.sleep(.1)

            for event in pygame.event.get ():
                if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                    pygame.quit ()
                    sys.exit ()

I have changed this method so that it is possible to display an array of LEDs, by means of an array.

colorarray = []

for i in range (numleds):
    colorarray.append(i)
    colorarray[i] = (16,16,16)

...

led = WS2812 (colorarray,screen)

The NeoPixelMatrix.py-code isn't the most optimal, and I'm using global variables inside a method. But it does exactly what I want, simulate an LED array.

Step 9: Temperature Color Map

The initial color gradient has been made with Powerpoint. The colors from this image must be translated to RGB values. So that they can be converted to a temperature scale.

The 'colorrange.py'-script reads all colors from the first row of an image. The output of this script gives all RGB values. The first image above clearly shows the change of the different color values.

from PIL import Image

image  = Image.open("colorrange.png")
pixels = image.load()
width, height = image.size

for x in range (width):
    print (pixels[x,0])

The following code creates 765 color codes from blue to purple (second image).

from PIL import Image

image = Image.new( 'RGB', (255*3,255), "white")
pixels = image.load()
width, height = image.size

for x in range (255):
    for y in range (255):
        red   = int(x);
        green = int(x)  ;
        blue  = int(255-x);
        pixels[x,y] = (red, green, blue)

for x in range (255):
    for y in range (255):
        red   = int(255);
        green = int(255 - x);
        blue  = int(0);
        pixels[255+x,y] = (red, green, blue)

for x in range (255):
    for y in range (255):
        red   = int(255-x/2);
        green = int(0);
        blue  = int(x/2);
        pixels[255*2+x,y] = (red, green, blue)

image.save("colortemp.png")

These colors cover a range from -20 degrees Celsius to +40 degrees Celsius.

Step 10: Functions and Exceptions

Some important elements were missing in the python code in the previous steps: exceptions and functions. There will be an error message if the weather data cannot be retrieved, which stops the Python program. To prevent this, the program must deal with this error.

The first example is a function to retrieve the temperatures for today:

def weatherForecast(YourKey):
    try:
        weatherUrl = urlopen('http://api.wunderground.com/api/' + YourKey + '/geolookup/forecast/q/NL/Twenthe%20Air%20Base.json')
        json_string = weatherUrl.read()
        forecast_json = json.loads(json_string.decode('utf-8'))

        temp_high0 = forecast_json['forecast']['simpleforecast']['forecastday'][0]['high']['celsius']
        temp_low0  = forecast_json['forecast']['simpleforecast']['forecastday'][0]['low']['celsius']
        temp_high1 = forecast_json['forecast']['simpleforecast']['forecastday'][1]['high']['celsius']
        temp_low1  = forecast_json['forecast']['simpleforecast']['forecastday'][1]['low']['celsius']
        weatherUrl.close

    except Exception as diag:
        temp_high0 = 40
        temp_low0 = -20
        temp_high1 = 40
        temp_low1 = -20

    return min(temp_low0, temp_low1), max(temp_high0, temp_high1)

The following function makes use of the storage sequence of elements in a Python list. The index value of -1 results in the last value of the list. All negative temperatures are stored at the end of the list. This makes it possible to address tempartures below zero as a regular value.

The colorMap-function creates 3 lists

def colorMap(minTempRange=-20,maxTempRange=40,maxValue=255):
    minRange   = abs(minTempRange*10)+1
    plusRange  = int((maxTempRange*10)/2)+1
    minFactor  = abs(maxValue/minRange)
    plusFactor = maxValue/plusRange

    resultRed   = []
    resultGreen = []
    resultBlue  = []

    for x in range (plusRange):
            resultRed.append   (int(maxValue))
            resultGreen.append (int(maxValue-x*plusFactor))
            resultBlue.append  (int(0))

    for x in range (plusRange):
            resultRed.append   (int(maxValue-x*plusFactor/2))
            resultGreen.append (int(0))
            resultBlue.append  (int(x*plusFactor/2))

    for x in range (minRange):
            resultRed.append   (int(x*minFactor))
            resultGreen.append (int(x*minFactor))
            resultBlue.append  (int(maxValue-x*minFactor))

    return resultRed, resultGreen, resultBlue

The default map ranges from -20 and 40 degrees.

Step 11: LEDs Choice

The inner diameter of the glass cylinder is 9.2 mm (3,6 inch). The LEDs have to be placed inside this cylinder with some distance between the LEDs and the glass. This step will determine the optimal distance between the LEDs and the Glass.

Each row will have 16 LEDs. And the distance between the LEDs has to be approximately equal, horizontal and vertical. The inner diameter must have enough room to hold all electronics. All this with the most efficient use of the LED strips.

The following table shows the required diameter of the LEDs for different LED strips:

LEDs/meter |LED distance|Length LEDs | LED Strip  | Required  | Distance
           |            | (16 LEDs)  |  Diameter  | Diameter  |LEDs-Glass
-----------+------------+------------+------------+-----------+-----------
      30   |      3.3   |     53.3   |     17.0   |     9.2   |    -3.9
      60   |      1.7   |     26.7   |      8.5   |     9.2   |     0.4
      74   |      1.4   |     21.6   |      6.9   |     9.2   |     1.2
      96   |      1.0   |     16.7   |      5.3   |     9.2   |     1.9
     100   |      1.0   |     16.0   |      5.1   |     9.2   |     2.1

distance and diameter in cm

It's not possible to use the 30 LEDs/meter strip for an evenly distributed LED placement. The best distance values are for the 60 and 74 LEDs/meter strips. These also provide enough room for the electronics.

The glass's height is 26,5 cm (10,4 inch). This divided by the LED distance gives the number of LED rows.

One thing to take into account is the sales length of the LED strips. Some are sold per meter. This makes it more efficient to use 4 pieces of 25 cm, instead of pieces of 26.5 cm. The following table shows the required amounts for the different LED strips:

LEDs    |LED distance|LED rows |  LEDs  |Total LEDs|  LED-Strips | Price  
meter   |            |         |Height  |          |   Required  |Estimate
--------+------------+---------+--------+----------+-------------+--------
    60  |       1.7  |     15  |  25.0  |     240  | 1 x 4 meter | $20.00 
    74  |       1.4  |     19  |  25.7  |     304  | 6 x 1 meter | $60.00 
    74  |       1.4  |     18  |  24.3  |     288  | 4 x 1 meter | $40.00 
    96  |       1.0  |     25  |  26.0  |     400  | 6 x 1 meter | $75.00 
    96  |       1.0  |     24  |  25.0  |     384  | 4 x 1 meter | $50.00 

distance and height in cm

A distance of 1 cm between the LED and the glass gives a better effect than 0.4 cm. That's why I opted for a strip of 74 LEDs per meter. The inner diameter of 70 mm also gives enough room for using a regular Raspberry Pi.


The choice of the 74 LED/meter strip gives a diameter of about 7 centimeter (2,8 inch). The sketch (second image) determines all horizontal sizes for the final design. The previous step gave a rough calculation of the diameter. The following calculation is based on the specifications:

16 LEDs x (5 mm + 8.78 mm) = 220.48 mm
220.48 / pi = 70.18 mm (outer diameter)
70.18 mm - 2.13 mm (thickness LED) = 68 mm (inner diameter)

The sketch starts with two construction circles of 70 and 80 millimeter. Giving a maximum wall thickness of 10 mm. A rectangle of 5 x 2 mm is drawn on the inner circle. This represents the LED. A second rectangle of 12 x 2 mm is drawn near the first box. This gives a flat surface for the pcb-strip.

A third circle, with a diameter of 20 mm, is placed outside the drawing. This circle goes through the outer corners of the LED, and gives a curved area around the LEDs. Measuring the location, of this circle, gives 45.5 mm from the center of the drawing.
A final circle with a diameter 67 mm gives the inside markers for the LED strip.

The 2 rectangles and the outher circle are copied 15 times in a circular pattern. With this sketch as result.


It is possible to create this project with ws2812b pixel panels. These have a density of 1 LED/cm2. A total of 24 x 24 LEDs can be used inside this glass cylinder. A total of 576 LEDs, which doubles the resolution.

I have chosen for the LED strips for various reasons: The panels are more expensive ($100 instead of $40) and require even more current (which gives more heat).

Step 12: LED Holder

It takes only 18 steps to create this LED holder in Fusion 360.

The sketch has been made using the measurements from the previous step. I've left out the LEDs and the outer circles. The result is a solid body of 10 mm thick. Pay attention to the "point" at the outer construction circle. This is used to place a spere.

The sketch is extracted in two directions (20 and 240 mm). The LED windows are made using a spere and an extracted rectangle of 5 x 5 mm. Both are copied (a total of 18) over a length of 227.4 mm. All these 18 windows are then copied (16 times 18) in a circular way. This is done in a single operation, resulting in 288 openings for the LEDs.

Because the entire object can not be printed at once, it is divided into three parts. The body is split at 73.765 and 163.39 mm (between the windows):

  1. The lower part with 6 rows. There is also a round recess on the inside, this is the bottom.
  2. The middle part with 7 rows. This basically has no top / bottom.
  3. The upper part with 5 rows. The top also has a round recess on the inside, this is the top.

Printing these parts takes some time. They can be printed at a lower detail (higher speed and layer-height) because the printed parts are not visible.Almost any color filament can be used. I've made the outside silver-colored, using spray paint.

Step 13: Power Supply

This project contains 4 meter of ws2812 LED strips. The specified power usage is about 22 Watt/meter. This makes the total power usage 88 Watt. This is the maximum, with all LEDs at full brightness.

The LEDs require a 5 Volt power source. And 88 Watt at 5 Volt results in a current of 17.6 Ampere. There are several 5 Volt power supplies available. For example:

Power Source          | Current |  price  |  Size (lxbxh)
----------------------+---------+---------+----------------
Mean Well LRS 50-5    |    10 A | € 15.00 | 100 x  80 x 30 
Mean Well LRS 75-5    |    15 A | € 22.00 | 100 x 100 x 30 
Mean Well LRS 150-5   |    26 A | € 30.00 | 200 x 100 x 40

None of these power supplies fit inside the glass cylinder. The power supply is placed out of sight.

The lamp is intended to display light effects. And will not be made to give maximum light output. The 75 Watt version is the closest to 88 Watt, and should give enough power for this project.


The simplest method to limit power consumption is to limit the maximum brightness of all LEDs. By setting this to 50% the power consumption is limited to 9 Ampere. This makes it possible to use the 10 Ampere power source (this leaves 1 Amp for the Raspberry Pi).

In order to limit the power consumption, I have developed a different software solution. With which it's possible that certain LEDs give more than 50% output.

Each LED uses 0,3 Watt. This equals 60 mA at 5 Volt. This is the current for the maximum white light output. Each ws2812 LED contains 3 small LEDs (red, green and blue). And white light is made out of 3 colors: red, green and blue. And each individual color takes 20 mA.

All LEDs (for this lamp) at a single output color (red, green or blue) takes 18 x 16 x 20 mA = 5.76 Ampere. The required current, for a single color, is below 9 Ampere. There is no need to maximize the LED output at 50% when using a single color for all LEDs.

A current of 9 Ampere can power (9A / 20 mA) 450 colors (red, green or blue) at maximum output. But there are (288 x 3) 864 small LEDs which can use this current.

The ws2812 LEDs are indiviually controlled by three values from 0 to 255. The value 0 equals off, the value 128 equals half power and 255 equals maximum output. And it takes 3 x 288 bytes to change all colors of all LEDs.

The idea is to limit the sum of all output bytes to a certain value. And this value represents a current of 9 Ampere. If the output of all 864 bytes is more than this value, all output values are reduced.

in the following example example is not enough power available for the requested output, and the ouput is reduced to 78%:

9 Ampere divided by 20mA equals 450 colors at full brightness
Total maximum output equals: 450 (LEDs) x 255 (value) = 114,750

Half the LEDs (144) at full brightness (255) and half the LEDs full blue:
144 x 255 (red) + 144 x 255 (green) + 144 x 255 (blue) = 110,160
144 x   0 (red) + 144 x   0 (green) + 144 x 255 (blue) =  36,720 

The requested output (146,880) exceeds the maximum output (114,750)
Correction = 0.78 

New values:
144 x 199 (red) + 144 x 199 (green) + 144 x 199 (blue) = 85,968 
144 x   0 (red) + 144 x   0 (green) + 144 x 199 (blue) = 28,656

Step 14: Limiting Power Usage

The following code implements limiting the current for the ws2812 LEDs. There is a circuit in Tikercad to test the code.

#include <Adafruit_NeoPixel.h>

#define NUM_PIXELS 24
Adafruit_NeoPixel pixels(NUM_PIXELS, 8, NEO_GRB | NEO_KHZ800);

int potRed   = 0;
int potGreen = 1;
int potBlue  = 2;

int redValue   = 0;
int greenValue = 0;
int blueValue  = 0;

long maxRGB = 3 * 215 * NUM_PIXELS; 
long totRGB = 0;

void setColor(int R, int G, int B) 
{
   totRGB = NUM_PIXELS * R + NUM_PIXELS * G + NUM_PIXELS * B;

   if (totRGB < maxRGB)   
   {
      totRGB = maxRGB;
   }  
 
   Serial.println (totRGB);

   R = max (min (R*maxRGB/totRGB, 255), 0);
   G = max (min (G*maxRGB/totRGB, 255), 0);
   B = max (min (B*maxRGB/totRGB, 255), 0);
   Serial.println (R);
   Serial.println (G);
   Serial.println (B);

   for (int i = 0; i < NUM_PIXELS; i++) 
      {
         pixels.setPixelColor (i, R, G, B);
      }     
   pixels.show();
}

void setup() 
{
   Serial.begin(9600); 

   pixels.begin();
   setColor (32,32,32);
}

void loop() {
   redValue   = analogRead(potRed);   
   greenValue = analogRead(potGreen); 
   blueValue  = analogRead(potBlue);
  
   redValue   = map(redValue,   0, 1023, 0, 255);
   greenValue = map(greenValue, 0, 1023, 0, 255);
   blueValue  = map(blueValue,  0, 1023, 0, 255);

   setColor (redValue, greenValue, blueValue);

   delay(1000);                  
}

It's not the most efficient code, and all LEDs have the same color, but It's about the idea. The main loop reads 3 values and calls a fuction to alter the color of the LEDs. And after a second the main loop starts again.

The setColor function adds all input RGB values multiplied by the number of LEDs. If this value exceeds a treshold (3 x 215 x 24, equals 1 Ampere) the output values are reduced by a factor. This way the output value always stays below 1 Ampere.

The value of 3 x 215 doesn't allow all RGB values at a maximum output, but it allows 2 colors at maximum output.


This method maximizes the output of the power supply. Make sure the power supply can deliver this value over a long time.

Step 15: Real Power Usage

While testing my current limiter software, I noticed that the power usage was much lower than expected. At first I thought there was something wrong with my code. Until I set all LEDs at full bright white light. The real power didn't came close to the specified poser usage.

In order to prevent an overdimensioned power supply, I measured the actual power consumption.


I started with an Adafruit NeoPixel ring with 12 LEDs as a reference. These LEDs require the following amount of current (mA):

Value (mA)   |   64  |  128  |  192  |  255
-------------+-------+-------+-------+-------
single color |   37  |   75  |  114  |  150
two colors   |   65  |  146  |  214  |  285
White        |   91  |  211  |  311  |  414

The specifications mention that each LED has a maximum constant current drive of ~18mA. There are 3 LEDs (Red, Green and Blue), resulting in a total of 54 mA/LED.

The real maximum current per led equals 414 mA / 12 = 34.5 mA.

Translated to 74 LEDs: 34.5 mA * 5 V * 74 LEDs = 12.7 Watt per meter.


After receiving the ordered LEDs, I decided to measure the required current for one strings with 74 LEDs:

Value (A)    |   64  |  128  |  192  |  255
-------------+-------+-------+-------+-------
single color | 0.246 | 0.507 | 0.729 | 0.954
two colors   | 0.420 | 0.936 | 1.365 | 1.801
White        | 0.590 | 1.346 | 1.979 | 2.617

The required current per LED is almost the same for these LEDs as for the Adafruit NeoPixels.

The maximum current per led equals 2.617 A / 74 = 35.4 mA.

Translated to 2.617 * 5 V = 13 Watt per meter (for 74 LEDs, the lamp uses 4 x 72 LEDs).


These values change the required power source, and there is no need to dimm the LEDs.

The calculated power usage equals: 4 x 22 = 88 W, at 5 volt equals 88/5=17.6 A

The measured power usage e/quals: 4 x 13 = 52 W, at 5 volt equals 52/5=10.4 A

A 14 Ampere power supply should be able to provide full brightness for all LEDs, including power for the Raspberry Pi.

Step 16: AWG Wires

The LEDs require a lot of current, as mentioned in a previous step. This must be taken into account during the design. It is not intended that the wires become too hot, resulting in a possible short circuit. Therefore, the power wires must meet certain conditions. And one of these standards is the American Wire Gauge. The higher the AWG number, the thinner the wire.

The following table shows the maximum currents for different AWG values.

AWG | Diameter | Square | Single | Multi Core
    | (mm)     | (mm2)  | Core   |  1-3  |  4-6  | 7-24  |25-42  |  43+
----+----------+--------+--------+-------+-------+-------+-------+-------
24  |   0.51   |   0.20 |    3.5 |    2  |   1.6 |   1.4 |  1.2  |  1.0  
22  |   0.64   |   0.33 |    5.0 |    3  |   2.4 |   2.1 |  1.8  |  1.5  
20  |   0.81   |   0.50 |    6.0 |    5  |   4.0 |   3.5 |  3.0  |  2.5  
18  |   1.0    |   0.82 |    9.5 |    7  |   5.6 |   4.9 |  4.2  |  3.5  
16  |   1.3    |   1.3  |   15   |   10  |   8.0 |   7.0 |  6.0  |  5.0  
14  |   1.6    |   2.1  |   24   |   15  |  12   |  10   |  9.0  |  7.5  
12  |   2.1    |   3.3  |   34   |   20  |  16   |  14   | 12    | 10   
10  |   2.6    |   5.3  |   52   |   30  |  24   |  21   | 18    | 15

One of my suppliers had the following wires in stock:

  • 0,22 mm2, 7 cores, AWG24 -> 1,4 Amps
  • 0,32 mm2, 7 cores, AWG22 -> 2,1 Amps
  • 0,50 mm2, 10 cores, AWG20 -> 3,5 Amps
  • 0,82 mm2, 16 cores, AWG18 -> 4,9 Amps
  • 1,31 mm2, 26 cores, AWG16 -> 7 Amps
  • 2,08 mm2, 41 cores, AWG14 -> 9 Amps
  • 3,31 mm2, 65 cores, AWG12 -> 10 Amps
  • 5.26 mm2, 105 cores, AWG10 -> 15 Amps

This project uses a 5 Volt power supply which can deliver 14 amps. And there are 16 LED-strips with 18 LEDs. All 288 LEDs require 10.5 Amps, giving 0,6 Amps per LED-strip (18 LEDs). This requires minimal AWG24 wires.


The power unit is placed near the lamp. The wire between the power supply and the LEDs must be an AWG 10 or 12 wire. I've choosen to use a wire with a diameter of 3mm. This wire will be short, and the load on the power supply will never be the maximum.

Step 17: Other Electronic Parts

Using ws2812 LEDs with an Arduino requires some additional components. The Neopixel best-practices advises to use an extra resistor between the data output pin and the input to the first LED. And to place a capacitor as buffer for sudden changes in the current drawn by the strip.

Step 18: Lamp Foot

The lamp foot contains a small fan and a Raspberry Pi. There is also room for a small PCB containing all other electronics. Like the fan controller, and additional resistors for the power switch.

The fan is placed at the bottom of the lamp foot.

There are four holes for spacers. These are required for the air supply of the fan.

Step 19: Power Distribution Board

The power supply is able to deliver a current of 14 Ampere. And the wire between the power supply is based on this current. But this wire is too thick to solder on the LED strips. Besides there are 16 LED strips which needs power.

Each quadcopter contains a PDB (power distribution board). This board divides the battery power to the several components. And provides 5 Volt for the flight controller. This lamp requires such an part. The power from the power supply must be divided over the 16 LED strips and the Raspberry Pi.

Fuses must be used between the power supply and the LED wires. The power supply can deliver 14 Amps, and the (thinner) wires to power the LEDs can't handle these currents. The current through these wires must be limited to the corresponding AWG value.


The power distribution board (PDB) is made from a prototype PCB. The upper part is for the 3mm GND-wire, the middle part is for the 3mm VCC wire and the lower part for the separate (AWG24) wires to the LEDs and the Raspberry Pi.

To reduce the number of fuses, I have decided to connect two LED strips to a single fuse. This gives a total of 10 (8 LEDs + 1 Raspberry Pi + 1 fan) fuses. The PTC fuses have a nominal value of 1.85 Amps and a trip value of 3 Amps at 40 degrees Celsius. These currents require AWG 22 wires. The price difference between these wires is minimal, and it saves 8 fuses.

Step 20: Lamp Ribs

3D printed ribs are used to keep the LEDs in place. Three 3 mm threaded rods come through these ribs. These secure the LED holder to the lamp base.

Step 21: LEDs Desolder

The LED strips are sold per meter and contain 74 LEDs each. This lamp requires 16 strips with 18 LEDs, a total of 4 x 1 Meter.

Cutting these LED strips exactly between two leds, gives 6 small solder pads (2 x 3). Althoug it is posible to solder these pads, it's better to create a double sized solder pad (second image). But there are only 2 spare LEDs on each strip.

The first and last LED of the strip are used: Remove the wires from the LED strip. This gives large solder pads on both sides (even the manufacturer doesn't cut exactly between two LEDs). Cut off 18 LEDs and include the solder path of the 19th LED. Remove this 19th LED (like in the second image).

There is a solder connection in the middle of the strip. Remove all solder. This gives the last 2 strips of 18 LEDs. Here, one of the LED strips has a large soldering pad, and the other one has a small (half) solder pad.

In the end there are now 16 strips with 18 LEDs (---> = direction of the arrows on the LED strips):

  • 4x: large pads (with solder) ---> large pads
  • 4x: large pads ---> large pads (with solder)
  • 4x: small pads (with solder) ---> large pads
  • 4x: large pads ---> large pads (with solder)

Step 22: LEDs Solder

After the previous step, there are 16 LED strips with 18 LEDs each. They each have a clean large solder pad on one side (as a result from cutting off LED 19). Lay down all LED strips, with these clean solder pads facing upwards towards the top. The most left-hand strip should have an arrow pointing downwards, And the next one must have an upward arrow. Repeat this for all LED strips (up-down-up-down....).

Lay the LEDs facing towards the table. Remove some of the adhesive strip on the back: Approximately one half inch at the bottom, and until the third LED at the top. Don't forget to remove the adhesive layer.
The LED strips are soldered at the backside, and not at the frontside. And the power connection isn't at the same place as the signal wire.

Take 16 red and 16 brown wires (AWG 20) of 10 cm (4 inches). Strip a few millimeters from one side, and slightly more from the other side. The long side is later soldered to the power distribution board.
Solder the red wires on the positive side of the LED strips, and the brown wires on the negative sides (the direction of the LED strips alternates). Solder these wires on to the third solder island, in the direction of the LED strip.

Then solder 8 signal wires (AWG24) at the bottom of the LED strips. These wires are about 5 cm (2 inches) long. First strip off a few mm on both sides. Solder the wires towards the LED strip. This results in 8 times 2 LED strips.

Solder a signal wire (5 cm, AWG24) to 7 LED strips with the arrows pointing downwards. The 8th LED strip gets a longer wire (40 cm, AWG24). This wire will be connected to the raspberry pi. Do not solder these 8 parts together yet, the signal wires are further soldered after assembly.

Finally, this results in 8 times 2 connected LED strips like the first photo. The signal from the Raspbery Pi should be able to go from the top right corner to the upper left corner (zigzag).


Test the 8 (dual) LED strips before placing them into the lamp.

The following code comes from Adafruit. It's a simple test, and doesn't light up all LEDs at once. This reduces the power usage and makes it possible to power the LEDs by USB using an Arduino.

#include <Adafruit_NeoPixel.h>
 
#define PIN 6
#define N_LEDS 36
 
Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, PIN, NEO_GRB + NEO_KHZ800);
 
void setup() {
  strip.begin();
}
 
void loop() {
  chase(strip.Color(255, 0, 0)); // Red
  chase(strip.Color(0, 255, 0)); // Green
  chase(strip.Color(0, 0, 255)); // Blue
}
 
static void chase(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels()+4; i++) {
      strip.setPixelColor(i  , c); // Draw new pixel
      strip.setPixelColor(i-4, 0); // Erase pixel a few steps back
      strip.show();
      delay(25);
  }
}

After testing, I strengthened the connection of the wires at the soldering points with a drop of glue. As a result, the wire itself is glued to the LED strip. This gives less stress on the soldered connection while assembling the lamp.

Step 23: LEDs Assembly Part 1

The initial idea was to place a piece of double-sided adhesive strip between the LEDs and the LED holder. Unfortunately, the adhesive strip did not sit properly. That's why I switched to hot glue.

Take the eight double LED strips and temporarily tie the wires together with some tape/heat shrink tube (loose). This makes assembly easier.

Place the first LED part in the middle section (3D printed part with seven windows). Make sure that five LEDs remain free at the side of the power wires. Six LEDs remain free on the other side. Attach both LED strips (with 18 LEDs) at the same time with little hot glue. Start in the middle, and finish on both sides. Repeat this until all eight double LED strips are in place.

Now place 2 ribs in the middle section. The holes of the ribs have to be at the same place. If necessary, secure them with glue.

Step 24: LEDs Assembly Part 2

Slide the part with the six windows (bottom part) over the six LEDs. Attach both printed parts , and glue the LEDs at two places onto the 3D printed part.

Place one of the ribs in the middle of this section. The holes of the ribs have to be in line with the other two ribs.


Turn the lamp over and solder the seven purple signal wires. Mind the arrows on the LED strips. The long purple signal wire is the input wire. The signal comes in here, and goes to the bottom of the lamp, to go up again by another strip. The end of this strip must be connected to the next strip.

Step 25: LEDs Assembly Part 3

The red and brown power supply wires (AWG20) are rather rigid. This requires them to be soldered on the PDB, before placing the final 3D printed part.

Take the 3mm power cable through the lamp. And solder all brown ground wires to the PDB. Work in a circle, and make sure that the wires do not get tangled.

Also solder a long brown wire (40 cm) to the PDB. This is used to power the raspberry pi and the fan (at this point I've decided to use a single fuse for the fan and the Raspberry Pi, the PDB can be made with 9 fuses).


After soldering all ground wires it is time for a second test. The same code as the previous test can be used, with the N_LEDS value set to 288.

Connect the groundwire (40 cm, for the Raspberry Pi) and the 16 red power wires to a breadboard by means of dupont wires. Connect an Arduino to the breadboard (5 Volt and ground) and connect the (purple) signal wire to pin 6 of the Arduino.

Use an external 5 Volt power supply and connect it to the breadboard.


After testing, the ground wires are fixed with use of hot glue. I also isolated the soldered side with glue. This reduces the chance of a short circuit.

Step 26: LEDs Assembly Part 4

Solder all brown VCC wires. Attach two wires per fuse. Make sure that the wires are soldered in order.

Finally attach a 40 cm brown wire (AWG20) for the Raspberry Pi and the fan.


Test the lamp again after soldering the VCC wires. Use the ground and VCC wires for the Raspberry Pi, and the purple signal wire.


Secure the red VCC wires after testing the lamp. And move the PDB towards the middle part of the lamp. This creates space to attach the final 3D printed part (with 5 windows). Because of the wires it is not possible to use ribs. Therefore, fix the LEDs with glue.

Move the VCC, ground and signal wire for the raspberry pi through the lamp.


Possible improvement: During the fastening of the LEDs the wires turned out to be quite stiff. This made it difficult to attach the final 3D printed part. This would probably have been easier, if the LED holder was divided in four or six parts. Horizontal (print size) and vertical (assembly).

Step 27: Assembly: Threaded Rods

Pull 3mm threaded rods through the holes in the ribs. I've placed an additional rib at the top. This is later replaced by the 3D printed upper part.

Step 28: LEDs and Current Usage

After assembly of the LEDs and connecting all LED strips to the power distribution, it is time to test the entire lamp. Attach the three wires for the Raspberry Pi to an Arduino: Red to VCC, brown to ground and the purple signal wire to pin 6. Connect the power supply to the 3mm power wires.

The following Arduino code ensures a slow increase of current:

#include <Adafruit_NeoPixel.h>

#define PIN        6
#define NUM_PIXELS 288
Adafruit_NeoPixel pixels(NUM_PIXELS, PIN, NEO_GRB | NEO_KHZ800);

void setup(void)
{
   pixels.begin();
   for (int x = 0; x < NUM_PIXELS; x++)
   { 
      pixels.setPixelColor (x, 0, 0, 0);
   }
   pixels.show();
}

void loop(void)
{
  for (int r = 0; r < 255; r=r+16)
  {
     for (int g = 0; g < 255; g=g+16)
     {
        for (int b = 0; b < 255; b=b+16)
        {
            for (int x = 0; x < NUM_PIXELS; x++)
            { 
               pixels.setPixelColor (x, r, g, b);
            }
            pixels.show();
            delay (1000);
        }
     }
  }
}

A modified version of this program is used to measure the LED current. These current values are for the LEDs only, the Arduino is powered by USB. The expected current for 4 LED strips is 10,4 Ampere. This value is for 4 times 74 LEDs. The Lamp has 288 LEDs giving an expected current of 10,1 Ampere. This is just in range of my multimeter:

 Value |  Single  |  Double  |     All 
-------+----------+----------+----------
    0  |    0,27  |    0,27  |    0,27 
   16  |    0,48  |    0,68  |    0,88 
   32  |    0,69  |    1,09  |    1,49 
   48  |    0,90  |    1,50  |    2,10 
   64  |    1,11  |    1,91  |    2,70 
   80  |    1,32  |    2,32  |    3,31 
   96  |    1,53  |    2,73  |    3,92 
  112  |    1,74  |    3,14  |    4,52 
  128  |    1,94  |    3,55  |    5,13 
  144  |    2,15  |    3,96  |    5,73 
  160  |    2,36  |    4,37  |    6,32 
  176  |    2,57  |    4,78  |    6,93 
  192  |    2,78  |    5,18  |    7,52 
  208  |    2,99  |    5,59  |    8,12 
  224  |    3,20  |    5,99  |    8,73 
  240  |    3,41  |    6,40  |    9,33 
  255  |    3,63  |    6,81  |    9,92

Any deviations are due to the measuring method. The first measurement used a lab power supply, this measurement used a multimeter.

Conclusion: All LEDs at full power use 50 Watt.


The LEDs give enough output at 80% power. This makes it is possible to use a 10 Amp power supply, and limit the LED current to 8 Amps (the easiest way is to limit the brightness of all LEDs to 200).

Step 29: Fan Control

The fan is controlled by the Raspberry Pi. It's a regular 5 Volt fan, which will be controlled by a PWM signal.The fan can not be powered by the GPIO output port. The port is only 3.3 Volt, and can't supply enough power.

This circuit uses a mosfet transistor. The fan is turned on when the GPIO port is high. The design has been tested with Tinkercad using an Arduino.

The Raspberry Pi uses 3.3 Volt for the gpio ports, while most Arduinos have 5 Volt logic. Because of this lower voltage, we have to omit the 1K resistor at the gpio ports (second image, signal).


In addition to the fan control, the PCB also contains the connections for the LEDs and the 'off' button.

Step 30: Assembly Raspberry Pi & Fan

Attach the ventilator, the Raspberry Pi and the fan controller to the lamp foot. The fan controller fits behind the raspberry pi, but can also be glued to the top of the lamp base.

Make sure that the SD card of the Raspberry Pi is headed downwards. The SD card can be replaced, after disassembly of the fan. Apply some glue to the fan bolts, so that the fan can be reattached without disassembling the lamp.


The six wires from the fan controller (LED, Button, Fan, 3V3, VCC and GND) must be connected to the Raspberry PI's gpio ports:

  • Attach the VCC, 3V3 and GND wire to the gpio ports near the SD card.
  • GPIO 18 / PIN 12 is used for the LEDs.
  • GPIO 17 / PIN 11 is used for the fan.
  • GPIO 04 / PIN 07 is used for the power down button.

Step 31: GPIO Fan Control

The fan is controlled by a PWM signal from a gpio port. This requires a small Python program which translates a temperature to a certain fan speed. There isn't a temperature sensor inside the lamp, the Raspberry Pi is used to measure it's CPU temperature. This value can be read with:

vcgencmd measure_temp

The Raspberry Pi CPU temperature is qualified from -40°C to 85°C. But I wouldn't advice to go above 60 degrees Celsius.

The next Python program reads the temperature, and calculates a corrospending fan speed. It's a linear function with 100% output at 50 degrees Celsius. The speed is decreased by 2% (or 3%) for each degree below this maximum value. And this value never falls below 20%.

A temperature of 30 degrees gives a speed of 60% (or 40%).

#!/usr/bin/env python3

import os
from time import sleep
import signal
import sys
import RPi.GPIO as GPIO

maxTemp = 50
delay   = 15
pin     = 17

def getTemp():
    result = os.popen('vcgencmd measure_temp').readline()
    temp =(result.replace("temp=","").replace("'C\n",""))
    return temp

try:
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(pin, GPIO.OUT)
    myPWM=GPIO.PWM(pin,60)
    myPWM.start(60)
    sleep(delay)

    while True:
        coreTemp = float(getTemp())
#       print (coreTemp)
        delta = maxTemp-coreTemp
        if delta<0:
            delta=0
        speed = 100 - (2 * delta)
#       speed = 100 - (3 * delta)

        if speed>100:
            speed=100
        if speed<20:
            speed=20

        myPWM.ChangeDutyCycle(speed)
        sleep (delay)

except:
    GPIO.output(pin, False)
    GPIO.cleanup()

The fan will not start at very low PWM values. The initial value of 60% will start the fan. The RPM value is adjusted each 15 seconds.

This fan will keep the temperature of the CPU below 30 degrees Celsius.

Step 32: Lamp Assembly (Part 1)

After finishing the LED holder and the lamp base, these can be attached to each other. Guide the (5 volt) main power wire through the lamp base. And solder the two power supply wires from the power distribution board (red = VCC and brown = GND) to the PCB near the Raspberry Pi.

Slide the LED holder over the lamp base. The holes in the ribs must be in line with the holes in the lamp foot. The 3 mm threaded rod goes from top to bottom through the lamp. This makes it possible to fix the 3mm threaded rods in the next step.

The two wires for the off-butten should go through the top of the LED holder.

Step 33: Lamp Assembly (Part 2)

I used a temporary rib when I first placed the threaded rods (Step 27). Remove this rib from the upper part and add some insulation or heat shrink tubing over the threaded rods as electrical insulation. Then place the 3D printed inner part. This requires little bending of the threaded rods, because of the LED strips. Place three 3 mm bolts on top, and make sure the bolts and the threaded rods do not stick out on the upper side.

Fasten the threaded rod on the down side of the lamp and cut off the remaining 3 mm rod.


The final part will be attached with a bayonet mount. I've strengtened the radial pins with m3 bolts before placing this part.

Step 34: Lamp Switch

This is the final step of the lamp assembly. Place the glass cylinder over the lamp and attach the final part. The two wires for the button should go through the center hole.

Put some heat shrink over the wires and solder the wires to the switch. This switch must be open by default. After pressing and releasing the button the circuit is closed and the PI will be switched off. Press the micro switch in place and the lamp assembly is finished.

Step 35: Emergency Light

This lamp is basicly a led matrix connected to a Raspberry Pi. The following code exampe changes the lamp into an emergency light.

#!/usr/bin/env python3

import time
from neopixel import *

numrows = 16
ledsrow = 18
numleds = numrows * ledsrow

PIN         = 18     # GPIO 18 / PIN 12
BRIGHTNESS  = 255    # min 0 / max 255

stick = Adafruit_NeoPixel(numleds, PIN, 800000, 5, False, BRIGHTNESS)
stick.begin()

white = Color(0,0,0)
red   = Color(0,255,0)
blue  = Color(0,0,255)

backgroundarray = []
for i in range (0, numleds):
    backgroundarray.append (i)
    backgroundarray[i]=white

try:
    while True:
        for i in range (0, 16):
            foregroundarray=list(backgroundarray)
            for j in range (0,18):
                foregroundarray [     (i*18+j)%numleds] = red
                foregroundarray [ ((i+8)*18+j)%numleds] = blue
            for j in range (0, numleds):
                stick.setPixelColor(j,foregroundarray [j])
            stick.show()
            time.sleep(.05)

except:
    print ("exit")

The temperature of the Raspberry Pi is about 25 degrees Celsius while running this script. With a fanspeed of 25% of the maximum speed.

Step 36: WeatherLamp Class

When I started this Instructable I knew little about Python. Occasionally a small program, and usually copied from another program. And after some adjustments the program worked as I wanted.

I've used several web-pages and two Python books (Lean Python and Beginning Python), to get me started and to improve my Python skills. Using this lamp makes programming with Pyhon fun. And the language gives a working product fairly quickly.


I started programming this Lamp with writing a classic program. Many loops, if-then-else, functions and many other 3GL features. But I quickly switched to a class. The first goal was to control all the lights by a matrix with 288 color values. After this worked, I slowly started adding functions.

Optimizations were also made between different versions. For example:

def initRain(self):
    for i in range (self.xleds * self.yleds):
        self.rainArray.append (i)
        self.rainArray[i] = self.black

def initWind(self):
    for i in range (self.xleds * self.yleds):
        self.windArray.append (i)
        self.windArray[i] = self.black

Can be written as:

self.rainArray = [self.black] * self.yleds * self.xleds
self.windArray = [self.black] * self.yleds * self.xleds

In the next two steps the rain and the wind are programmed. Both with the Neopixel LEDs as with simulated Neopixels. The used program examples are far from complete, but they show the basic principle.

Step 37: Rain

Rain is displayed by moving pixels from the top to the bottom. The weatherLamp class contains an additional array to store these pixels. The inital value of this array are only black pixels.

The rainMoveDown function moves all values one pixel downwards. And adds random pixels at the top, if it's raining.

    def initRain(self):
        print ("rainInit")
        for i in range (self.xleds * self.yleds):
            self.rainArray.append (i)
            self.rainArray[i] = self.black

    def rainMoveDown(self):
        for i in range (16):
            for j in range (17, 0, -1):
                self.rainArray [i*18+j] = self.rainArray [i*18+j-1]
            self.rainArray [i*18] = self.black

        for i in range (self.hourRain):
            x = random.randint(0,15)
            self.rainArray[x*18]=self.white<br>

The python file in this step contains all code to display raindrops. It creates an object "weatherLamp" and calls the rainMoveDown function. This function modifies the rainArray inside the object.

The showLeds function displays an array on the Lamp. It can be used by any array with 288 color values.

#
# Main
#

stick = Adafruit_NeoPixel(numleds, ledpin, 800000, 5, False, 255)
stick.begin()

minTempRange, maxTempRange = colorRange()
hour_rain, hour_snow = weatherHourly(YourKey)
weatherLamp = Lamp (stick, minTempRange, maxTempRange)

weatherLamp.hourRain=2 #testvalue
for x in range (100):
    weatherLamp.rainMoveDown()
    weatherLamp.showLeds (weatherLamp.rainArray)
    time.sleep (.2)

weatherLamp.hourRain=0
for x in range (20):
    weatherLamp.rainMoveDown()
    weatherLamp.showLeds (weatherLamp.rainArray)
    time.sleep (.2)

The raintest.py-file contains a first version of the weatherLamp class. It contains two arrays: The backArray contains the background colors with the temperature scale. And the rainArray which is controlled by the rainMoveDown-function.

The showLeds function has to be altered to display the merged arrays.

Step 38: Wind

Wind is displayed by moving pixels horizontally. The principle is almost equal to the programmed rain. There is an array with 'wind'-pixels, and values are only displayed if the pixel isn't black. New pixels are inserted and move both ways towards the wind direction. The inserted colors is a lighter color of the background color.

Wind speed is translated using the Beaufort scale. And the returned value is the maximum number of changed colors.

Beaufort | Description     | Wind Speed
---------+-----------------+-----------
    0    | Calm            |    0-1
    1    | Light air       |    1-5
    2    | Light breeze    |    6-11
    3    | Gentle breeze   |   12-19
    4    | Moderate breeze |   20-28
    5    | Fresh breeze    |   29-38
    6    | Strong breeze   |   39-49
    7    | High wind       |   50-61
    8    | Gale            |   62-74
    9    | Strong gale     |   75-88
   10    | Storm           |   89-102
   11    | Violent storm   |  103-117
   12    | Hurricane force |     >117

The final version of the code will have multiple arrays. And it's possible to use the NeoPixel library to merge these arrays. The Neopixel function "show" changes the colors of the LEDs using the data stored with the "setPixelColor" function. It's posible to alter a LED's color multiple times, but only the last setPixelColor-value will be shown.

Step 39: Temperature, Wind and Rain

The final code includes four items:

  1. Background temperature range
  2. Current temperature
  3. Rain
  4. Wind

The "showLedsMerge" function in the weatherLamp class combines this information and sends it to the LEDs. It also takes care of displaying the current temperature.
The displayMatrix function handles the animation of the different types. The showLedsMerge function is called twice, to make the temperature indicator move twice as fast as the rain and wind.

def displayMatrix():
    while True:
        weatherLamp.rainMoveDown()
        weatherLamp.windMoveDir()
        weatherLamp.showLedsMerge()
        time.sleep (.2)
        weatherLamp.showLedsMerge()
        time.sleep (.2)

The WeatherUnderground API allows 500 calls per day.This is about 20 per hour. Since it takes 3 calls to collect al required data, the weather data will be updated every 10 minutes. This gives 432 API calls per day.

while True:
    currmin=datetime.datetime.now()
    if currmin.minute >= nextmin:
        nextmin=(currmin.minute+10)%60
        temp_low, temp_high = weatherForecast(YourKey)
        hour_rain, hour_snow = weatherHourly(YourKey)
        temp_now, wind_kph, wind_dir, real_rain = weatherConditions(YourKey)
        print (currmin)
        print (temp_low, temp_high)
        print (hour_rain, hour_snow)
        print (temp_now, wind_kph, wind_dir, real_rain)
        weatherLamp.minTempRange = min (temp_low, temp_now)
        weatherLamp.maxTempRange = max (temp_high, temp_now)
        weatherLamp.hourRain     = real_rain
        weatherLamp.windSpeed    = wind_kph
        weatherLamp.windDir      = wind_dir
        weatherLamp.temperature  = temp_now
        weatherLamp.BackGround()
    time.sleep (5)

The first version of this software had the displayMatrix function inside the main loop. This caused the display to stood still every 10 minutes for a short moment. In order to prevent this, the updating of the screen must be carried out independently of updating the data. This can be done with threads:

matrixThread = threading.Thread(name='displayMatrix', target=displayMatrix)
matrixThread.start()

This starts the displayMatrix function in a different process. This means there is no delay during retrieval of the weather data.

Step 40: Startup and Shutdown

The lamp is assembled and the code is almost complete. But nothing visible happens when the power is turned on. Internally some things happens: The Raspberry Pi boots up and connects to the WiFi network. And when this is finished there is nothing else to do than wait.

This instructable contains 3 programs parts which must be added to the boot procedure of the Raspberry Pi:

  1. Fan: fanSpeed.py
  2. Button: buttonStop.py
  3. Lamp: weatherLamp.py

Shutting down the Raspberry Pi keeps the LEDs at their last state. To switch off the LEDs, a program must be added to the shutdown procedure.


There are 3 programs that must be started at startup. And the easiest way to automaticly start the programs, is to add them to the rc.local script. Place the three files in the /home/pi-directory and make them executable ("chmod +x *.py"). Modify the rc.local-file (sudo nano /etc/rc.local). And add the following 3 lines before the "exit 0":

/home/pi/fanSpeed.py &
/home/pi/buttonStop.py &
/home/pi/weatherLamp.py &

Now the programs are automatically started after powering on the Raspberry Pi.


The weatherLamp.py script can be replaced by "Lava.py" to change this lamp into a plasma/lava-lamp.

Step 41: Materials

The following parts have been used to make this lamp:

I've bought most small electronic components at Aliexpress. Mostly several different values and types in a pack.

Step 42: Improvements and Alternatives

I started the design of this lamp a while ago. I calculated the electrical part for 288 LEDs. It turned out that the real power consumption is lower than the specified power consumption. And I have adjusted the power supply accordingly.

This lamp is still designed to give light at full output. There is enough power, the wires and fuses can handle the currents, and the fan keeps the temperature in control.

I've provided enough detail to build this lamp with a smaller power supply. It's possible to use a 10 Amps power supply, or even a 5 Amps power supply. This allows for thinner wires, and less fuses. Even the fan can be omitted.
The only drawback is that the LEDs can only light up at half power (or less). But the LEDs give enough light at low values (This lamp is not made to illuminate an entire room). Most images in this Instructable are taken at half of the regular output. Otherwise it was not possible to take pictures (first image).


During the fastening of the LEDs the wires turned out to be quite stiff. This made it difficult to attach these wires to the PDB. This would probably have been easier, if the LED holder was divided in four or six parts. Divided horizontal for print size, and vertical for assembly.

It's possible to replace the printed LED holder by a 60mm PVC tube. The LEDs have to be glued onto the tube, which alters the way of soldering. But the wiring diagram remains the same. This project can then probably be made without a 3D printer.


All in all, this Instructable has become a bit longer than I had in mind. But I am very satisfied with the end result. Both the hardware part and the software part. The code will probably require some small changes in the future. And thanks to my newly acquired Python knowledge, this will not be a problem.

By writing this Instructable, I hope that I have given enough information and inspiration, for those who also want to make a similar project.

GosseAdema

LED Contest 2017

Grand Prize in the
LED Contest 2017

Raspberry Pi Contest 2017

Second Prize in the
Raspberry Pi Contest 2017

Epilog Challenge 9

Participated in the
Epilog Challenge 9