header image

Arduino Lava Lamp Project

I've got a lava lamp that I was given by someone at work for some reason or another. It sits next to my desk at home and I never turn it on because the switch is on the cord behind a cabinet that's hard to reach.

I also have a few arduino boards sitting around that were leftovers from when we built "build break orbs" at work (basically things that control colored LEDs and can turn green/red/whatever to indicate the state of a build). An arduino board is a little credit-card sized microcontroller you can plug into your computer's USB port and upload programs ("sketches") to. Those sketches can read input from physical devices, read input from the computer over the USB connection, and change the state of pins to interact with the outside world. In my case, I wired pin 13 on the arduino to a power switch tail so that when I sent a (very) little current to it, the light would turn on (and vice versa). If I was really cool I would have made my own power switch tail but I'm not that cool.

Anyway, I decided to use an arduino board and a power switch tail to turn on and off the lava lamp on some condition with a cron job and my computer. What does that mean? It means that when its going to snow at my favorite ski place or its going to be nice enough to take the top down on my convertible the lava lamp is on, otherwise off.

How? First, I programed the arduino to basically watch the serial port and turn on a pin if a non-zero byte was read, turn off the pin otherwise. Here's that code:

int lightPin = 13;

void setup() {
  pinMode(lightPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int b;
  if (Serial.available() > 0) {
    b = Serial.read();
    if (b) {
      digitalWrite(lightPin, HIGH);
    } else {
      digitalWrite(lightPin, LOW);
    }
    delay(1000);
  }
  delay(25);
}

If you don't understand that or don't know what an arduino is, check out this series on youtube. The delays are in milliseconds (1000ms is 1s).

The next step was the python code on the other side to send the serial bytes. I wrote this code to periodically poll the state of a file and, if it changes, to fire a byte over the serial line to the arduino sketch above. Here's that:

#!/usr/bin/python

"""Controller script for lava lamp... turns it on or off based on lavastate."""

import sys
import time
import serial
import os

lava_serial = serial.Serial("/dev/tty.usbserial-A6008iTZ", baudrate=9600)
lava_file = "./lavastate"

last = ''
while True:
  if not os.path.isfile(lava_file):
    sys.stderr.write("Could not file " + lava_file + " file.")
    sys.exit(1)

  infile = open(lava_file)
  while infile:
    line = infile.readline()
    if len(line) == 0:
      break
    line = line[:-1]

    if line == "on":
      c = '1'
    else:
      c = '\0'

    if c != last:
      lava_serial.write(c)
      lava_serial.flush()
      last = c
  time.sleep(0.5)

Basically just keep reading some file every half second and, when it changes, send a byte over the USB to the arduino. The sleeps in here are in seconds, not milliseconds. The /dev/tty.usbserial... is what my mac calls the arduino card / driver. If you're on FreeBSD, that will be something like /dev/ttyU0 and linux calls is something like /dev/ttyUSB0.

Final step is another python program that writes that lavastate file. I thought a while about this and decided to do it based on a weather forecast. Two actually: one at the local ski place and the other at my house. It pulls data from weather underground. To do so you need to register and get a key but it's free as long as you don't call more than 500x per day. I only query once per hour so this is fine for my purposes. Here's my script (developer key obfuscated, of course):

#!/usr/bin/python

import urllib2
import json
import time
import sys

code = 0
month = int(time.strftime("%m"))
if (month >= 11 or month <= 4):
  f = urllib2.urlopen('http://api.wunderground.com/api/YOUR_KEY_GOES_HERE/geolookup/conditions/forecast/pws:1/q/pws:MSTG48.json')
  json_string = f.read()
  parsed_json = json.loads(json_string)
  f.close()

  snow = parsed_json['forecast']['simpleforecast']['forecastday'][0]['snow_allday']
  if (snow["in"] >= 5):
    print "DUMPING AT STEVENS"
    code = 1
  sky_tomorrow = parsed_json['forecast']['simpleforecast']['forecastday'][0]['skyicon']
  if (sky_tomorrow == "partlycloudy" or sky_tomorrow == "mostlysunny"):
    print "BLUEBIRD DAY AT STEVENS"
    code = 1

f = urllib2.urlopen('http://api.wunderground.com/api/YOUR_KEY_GOES_HERE/geolookup/conditions/forecast/pws:1/q/WA/Bellevue.json')
json_string = f.read()
parsed_json = json.loads(json_string)
f.close()

high_tomorrow = parsed_json['forecast']['simpleforecast']['forecastday'][0]['high']
sky_tomorrow = parsed_json['forecast']['simpleforecast']['forecastday'][0]['skyicon']

if (high_tomorrow['fahrenheit'] >= 60 and sky_tomorrow == "mostlysunny"):
  print "CONVERTIBLE"
  code = 1

sys.exit(code)

The format of the request and the JSON response is somewhat documented but really with the JSON it's easier to just try it out and see what happens. That code will:

  • From November to April, check for snow forecast tomorrow and for blue skies tomorrow at Stevens Pass, WA using a URL from weather underground looking for snow or blue skies.
  • Anytime, check for warm enough and sunny enough that I might be able to put the top down on my (ridiculous to own in WA state but loved none-the-less) convertible.
It parses the weather response JSON and exits with a non-zero value which causes the cron job to write "on" to the lavastate file and turn on the lava lamp (and vice versa to turn it off again).

Anyway, hope that helps someone out. Have fun.

index.html was last updated 24 April 2015 and is Copyright (C) 2002-2015 by Scott Gasch (scott.gasch@gmail.com).