Fish Pi LEDs

I decided that I wanted to ditch the fluorescent tubes lighting my aquarium and replace with LEDs so that I could save electricity and tune the colour.

Step 1: bought WS2801 waterproof LEDs like these.

Step 2: dug out a Raspberry Pi Zero W – shop around to get the best price, you can buy with a header pre-soldered, solder your own or get a hammer header. I just installed the latest version of Raspbian from here (“Buster”).

Step 3: used this excellent tutorial to get the lights connected to the Pi and tested with the provided Python code.

Step 4: oh, you’ll need a bare minimum of 3 A per 50 LEDs so I’ve been using these 10 A supplies in case I want to expand.

Step 5: For the time being I’ve made some simple quick code (see below) to linearly change the RGB components between fixed points during the day. I will eventually set up a “natural” cycle, but will stop short of having the colour output being the same as the outdoor weather by pointing a camera at the sky as I have seen elsewhere – it wouldn’t be so great in a northern hemisphere winter! I welcome comments on the wavelengths that these LEDs emit and diurnal variations in brightness and colour. At the moment if you look at the code I’ve got “bluish”, then “bright”, then “reddish” to dark. One aim of this project was to turn the lights on and dim themĀ  slowly – I always feel a bit sorry for the little fishes when the lights come on with a BANG!

Step 6: I used OpenSCAD to design the LED holders and printed them on a Geeetech 3D printer, OpenSCAD files available here.

Step 7: bolt it all together and to the tank with M5 bolts, switch it on… and to my slight amazement it works!

Step 8: Still to do is to make a compact case for the Raspberry Pi with the minimum of wire holes. Also tempted to add a thermometer and a (small) LED display.

# Adapted from "Simple demo of of the WS2801/SPI-like addressable RGB LED lights."
import time
import RPi.GPIO as GPIO

# Import the WS2801 module.
import Adafruit_WS2801
import Adafruit_GPIO.SPI as SPI
import datetime
from array import *

nZones = 4
rArray = array('i', [0, 220, 255, 255, 0])
gArray = array('i', [0, 220, 255, 100, 0])
bArray = array('i', [0, 255, 255, 100, 0])
# 8 am, 1pm, 6pm, 8pm, 10pm
tArray = array('l', [28800,46800, 64800, 72000, 79200])

# Configure the count of pixels:
PIXEL_COUNT = 100

# Alternatively specify a hardware SPI connection on /dev/spidev0.0:
SPI_PORT = 0
SPI_DEVICE = 0
pixels = Adafruit_WS2801.WS2801Pixels(PIXEL_COUNT, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE), gpio=GPIO)

def updateTank(pixels, seconds):

  lower = 99
  first = 0

  # find zone
  if seconds < tArray[0] or seconds > tArray[nZones] :
    pixels.clear()
    pixels.show()
  else :
    # calculate position in zone
    for i in range(nZones):
      if seconds >= tArray[nZones - i - 1] and first == 0:
        lower = nZones - i - 1
        first = 1

   # interpolate values
   zoneRange = tArray[lower + 1] - tArray[lower]
   zonePosition = seconds - tArray[lower]
   interpolateValue = zonePosition / float(zoneRange)
   rCol = int(rArray[lower] + interpolateValue * (rArray[lower+1]-rArray[lower]))
   gCol = int(gArray[lower] + interpolateValue * (gArray[lower+1]-gArray[lower]))
   bCol = int(bArray[lower] + interpolateValue * (bArray[lower+1]-bArray[lower]))

   print(seconds, lower,zoneRange, zonePosition, interpolateValue, rCol, gCol, bCol)

   for i in range(pixels.count()):
     pixels.set_pixel(i, Adafruit_WS2801.RGB_to_color( rCol, gCol, bCol ))
     pixels.show()

if __name__ == "__main__":
  
  # This just lets you run a colour test if you set to 1
  simulate = 0

  if simulate == 0 :

    now = datetime.datetime.now()
    midnight = now.replace(hour=0, minute=0, second=0, microsecond=0)

    daySeconds = (now - midnight).seconds
 
    updateTank(pixels, seconds = daySeconds)

else :

  step = 20.0
  gap = tArray[nZones] - tArray[0]
  inc = int(gap / step)

  for i in range(int(step)) :
    daySeconds = tArray[0] + i * inc
    updateTank(pixels, seconds = daySeconds)
    time.sleep(2)

  pixels.clear()
  pixels.show()