SUNRISE LAMP

I wanted a DIY LAN-only light source, the primary function of which is to gradually fade up before my morning alarm goes off. I wanted the project to support multiple, individually addressable instances of the lamp, and to support an extensible set of actions (e.g. pulsing), in addition to a slow fade up. Extras include being sunrise- (and, maybe, calendar-) aware (i.e. can be configured to not switch on after the sun is up and/or if it's a public holiday today).

Physical Assets

The following SVG files can be used to laser-cut the physical components of the 'SunriseLamp' (note that some of the SVG files contain enough parts for three lamps, as I was making multiple lamp instances). The body of the lamp is comprised of:

Assembly is fairly straightforward. I used clear craft glue and white duct tape. Do not secure the rear cone until everything is tested and working. See the following photos for guidance (ignore the internal tabs - I didn't end up using those):

SunriseLamp body (front face + side/edges + LED guides)
SunriseLamp body (front face + side/edges + LED guides)
SunriseLamp body (rear cone)
SunriseLamp body (rear cone)
Sunrise Lamp: rear view
Sunrise Lamp: rear view
Sunrise Lamp: PWM driver circuit
Sunrise Lamp: PWM driver circuit

Rear view: left to 12V power adapter; right to LEDs; 12V→USB/5V adapter has been electrically isolated with a small plastic bag.

Electronic Assets

Electronic Assets: 12V power supply; 12V→USB/5V adapter; ESP8266 microcontroller; LED strip; custom circuit board.

12V power supply: capable of driving the LEDs + 12V→USB/5V adapter; suggest 2Amp+.

12V→USB/5V adapter: used to supply power to the ESP8266 micro controller, via it's USB input; deconstruct/rewire a car cigarette lighter adapter.

ESP8266 microcontroller (or similar): WiFi connected; single GPIO required for PWM output.

LED strip: 12V, warm white; long enough to cover lamp circumference (~120cm); threaded through hole in 'rear cone' and attached to the inside wall of the lamp.

Custom circuit board

We need a 'driver' circuit as the microcontroller can not supply anywhere near enough current to the LEDs by itself.

BOM: 1 x IRF540N MOSFET; 1 x MPS2222A NPN transistor; 2 x 1KΩ resistor; vero board.

Sunrise Lamp vero board layout

Code Assets

SunriseLamp repository on GitHub.

sunriseLamp.py

This is the code that talks to the microcontroller inside the lamp. Note that this code is optional - any MQTT frontend will work.

The C_TRIGGERS array maps a label to a device+action. I've hard-coded this, as it won't change very often, but you could read this in as a JSON file, if that would suit you better.

A 'trigger' includes: a label (CLI argument); the MQTT topic (address of specific lamp instance); the MQTT payload ('b' = brightness target, or "-1" to switch to OTA-mode; 'd' = duration , in seconds; 'p' = number of pulses to finish with; 'f' = final brightness, or "-1" to hold at the target); and an 'always' flag ('false' = don't do this action if the sun is up).

Multiple triggers can be passed into the Python script via the CLI (comma separated): $> python sunriseLamp.py bed1-sunrise,bed2-sunrise

Schedule via cron, e.g.: 0 7 * * * python /home/pi/sunriseLamp.py test <- runs trigger 'test' everyday @ 07h00

Python imports: astral; datetime; paho.mqtt.publish; pytz; socket; sys

SunriseLamp.cpp

This is the code that runs on the microcontroller inside the lamp. You'll need to update the 'build_flags' in your platformio.ini to match your situation (see below).

I'm using a Adafruit Feather HUZZAH ESP8266 microcontroller, but any module with WiFi and a single PWM GPIO will work.

I'm using an MQTT interface to control the lamp - see code comments for examples. Search the code for '###', and replace, as appropriate.

It should be noted that the microcontroller can be sent a new set of instructions at any time (as opposed to an action being triggered, and you having to wait until its fully complete). Amongst other things, this means you can cancel/switch off any action before it has completed.

PlatformIO includes: ArduinoOTA; ESP8266mDNS; ESP8266WiFi; PubSubClient@2.7

My platformio.ini for this project:
[env:huzzah]
platform = espressif8266
board = huzzah
framework = arduino
upload_protocol = espota
upload_port = 192.168.1.###
upload_flags = --auth=### YOUR OTA PASSWORD ###
build_flags =
-DC_ADDRESS=### 4TH IP ADDRESS BLOCK - AS PER upload_port, ABOVE ###
-DC_CLIENT=\"### MQTT CLIENT ID ###\"
-DC_TOPIC=\"### MQTT TOPIC FOR THIS DEVICE ###\"
-DC_SSID=\"### YOUR WIFI SSID ###\"
-DC_SSPW=\"### YOUR WIFI PASSWORD ###\"
-DC_HASH=\"### YOUR OTA PASSWORD HASH, AS PER 'upload_flags = --auth', ABOVE ###\"
lib_deps =
ArduinoOTA
ESP8266mDNS
ESP8266WiFi
PubSubClient@2.7

Brightness

Sunrise Lamp Graph

Version 1 of 'SunriseLamp.cpp' used a constant time step (orange line), which gave a linear brightness increase (green line). In practice, this resulted in a slightly too aggressive ramping of brightness. Version 2 transitioned to a linearly decreasing time step (red line) resulting in a more pleasant brightness increase profile (blue line).

Creative Commons License

All Sunrise Lamp assets by Chris Molloy are licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.