Introduction: Bright Controllable Sunrise Lamp

Did you ever wake up at 7ish, the usual time you need to wake up for work, and found yourself in darkness? Winter is a terrible time, right? You have to wake up in the middle of the night (otherwise why is it so dark?), rip yourself off the bed and send your semiconscious body to the shower.

This project aims to solve one of the issues - morning darkness.

There are lots of cheap sunrise lamps around, but they are all low power and pale. They are more like a night lamp, which is supposed to make you sleep better. Not what I want at all.

At the same time, just turning the bright light on will wake you up immediately, but not gently enough. What I want is a combination of both approaches - light up with a low brightness, slowly get to full speed, then a real alarm goes off and you are not so sleepy anymore. Let's add a bit of a bird song to it, and you wake up in a heaven every morning!

Step 1: Lamp Array

First of all, we need the lamp itself. I have a pretty large room with white walls and ceiling, so I went for 7 GU10 LED lamps, something like 6W each, more than 40W of pure power! That's enough to make you feel like it's daytime already. Also it can be used as a usual room lighting during the day.

It doesn't really matter how you assemble it, which lamps you use with which sockets. Everything that matters - these must be dimmable lamps!

In my case, I have a wood plank with 7 GU10 sockets attached, all connected together. I'll put it into a plastic box later.

Step 2: Dimming Theory

In theory there is no difference between theory and practice. In practice there is.

Controlling a dimmer from ESP32/Arduino appeared to be not as simple as i imagined. I got one of RobotDyn AC Light Dimmer modules. The manufacturer suggests a library for that. It doesn't work on ESP32 (and it's really hard to adapt, because it uses a lot of low level ATMega-specific registry access), sort of works on Arduino Nano, giving a terrible flickering on low-mid brightness. That's why I spent some time investigating how it all works and making my own way.

A bit of a theory.

The chosen dimmer module uses a very popular TRIAC: BTA16. There are lots of articles about it. I'll try to summarise it here.

TRIAC is a module which can transmit an input positive or negative voltage to the output, or can block it. By default it blocks everything. In order to open it, we should give it a high signal on gate input for 100 us. Then it will stay open until the current drops to zero, which happens when an input voltage changes the sign, crossing a zero voltage. Then on the following cycle we should do another 100 us pulse and so on. By choosing when to give a pulse, we control the brightness: do it at the very beginning, and it will be close to 100% power transmission. Do it later, and it will be dimmed. Check out the diagram above, explaining it.

In order to generate pulses in the same point of the cycle, we need to know exactly when it begins. That's why the dimmer module has a Zero-Cross detector built in. It just raises a signal (which we'll catch as a hardware interrupt in Arduino) every time the voltage crosses zero.

Step 3: Dimming Practice

Yeah, that's how you would wake up, if your lamp has no dimming and puts all 40W of power into your sleepy eyes.

Common issues.

There are multiple issues we need to address.


The microcontroller's timing must be really precise in turning the gate output on and off. The library RobotDyn suggests, has a timer interrupt every 100us, and changes the gate level on timer only. It means it can be +/- 50 microseconds off the optimal value. It gives a good result on a high brightness, but flickers a lot on low brightness. Also if the microcontroller does a lot of stuff, it decreases the time accuracy, so ideally a dedicated microcontroller should be used for the dimmer.

Minimal brightness.
LEDs have a built-in power converter, which will just refuse to work having not enough power. My lamps appeared to work fine starting from 10-11%.

Even with this value, some of my lamps refused to light up on start. Even when increasing the brightness later, they stay dark. That's why, when we go from OFF state to some positive brightness, we start with a warm up period of 5 cycles, when we give full power to the lamps. Then we continue with the desired brightness. It's almost unnoticeable, but really helps.

50/60 Hz mains frequency.
You need to know how much to wait before the next zero. It's pretty simple - we just look at the time difference between two last interrupts.

Gradual brightness change.
ESP32 is pretty slow, it takes 0.5 sec to process a trivial HTTP or even WebSocket request, so don't expect a smooth brightness transition, it needs to be implemented somehow on the dimmer level. That's why, when it receives a new brightness from a serial port, it just sets the target, and then slowly approaches it over time.

The solution.

Here is my simple Arduino code for the dimmer. It waits for a command (one byte with the new brightness) from the serial input, handles Zero-Cross interrupts, controls the TRIAC, handling all the issues above.

Step 4: Lamp Controller (ESP32)

Here's the connection schema of all the components I have. The ESP32 board is very different from what I use (Heltec), so the pins chosen look a bit weird, but it should still work fine. Feel free to use different pins in your project.

Here's the code controlling it all. It's pretty straight-forward.

The main features.

The lamp connects to WiFi, starts a WebSocket server on port 81, waits for commands.
Command format is

{"command": "CMD", "data": {<DATA>}}

Just two commands are supported for now: "set_brightness" and "update_settings", which are... pretty self-descriptive.

Getting time from NTP.
I don't want to over-complicate things and add a real time clock to the schema. We have Internet access, which means we can get the real time from some NTP server and then keep track of the current time using the system timers.

Sunrise Alarm.
You can set one alarm. What it actually does: starts with the minimal brightness and gradually goes to the full brightness over 10 minutes. Then it stays on for a couple of hours. Then it gradually turns off over 60 seconds.

All the parameters above are configurable.

Birds singing.
DFPlayer mini is used to play music. There are lots of guides for it, but essentially you just need to plug a MicroSD card, formatted into FAT32, with one file called 0001.mp3. This file can have anything you like, in my case it's 15 minutes of birds singing (it will be looped), and it makes my morning amazing.
Note that there's a huge capacitor on the power, and 1 kOhm resistors on the serial line between ESP32 and DFplayer - they are optional, but help to reduce the noise.

Storing the settings in EEPROM.
All settings are written into EEPROM and loaded on start. It makes it possible to use the lamp with at least an alarm feature without a controller connected.

Rendering some info to the OLED screen.
My Heltec ESP32 has a built-in SSD1306 128X64 I2C screen. All the essential information is rendered on it.
I know, the box looks ugly, I've just 3D-printed some stuff and cut the holes and windows with a drill. Quick, dirty, but it works!

Step 5: Control Panel

That's the heart of the project. A Raspberry Pi with an original 7" display, running some Kivy front-end.

Here's the full source code.

The features.

Written in Python.
I love Kivy, it's a Python framework for user interfaces. Very simple, yet flexible and efficient (uses lots of C code inside for high performance and hardware acceleration).

Show the current temperature and pressure outside. If you connect a remote sensor - inside temperature as well.
It also requests and analyses the weather forecast for the following 12 hours and gives an advise about a probability of the rain.

SunriseLamp controller.
Another panel displays some basic information about the alarm and allows you to adjust the brightness. If you go to the settings, you can configure any parameter of the lamp, including the alarm schedule, max audio volume and so on.

Renders Game of Life on the screen after some period of inactivity.

There used to be more than that, but other things appeared to be useless.


I installed everything manually on Raspbian, and now can say: don't repeat my mistakes. Use the KivyPie, it has everything pre-installed.

Apart from that, just follow the installation guide in the code repository.

Step 6: Enjoy!

Personally I'm happy with the device. I use it as a main lighting at home during a day, and it lets me wake up in the morning, it's amazing.

I know the instructions are not very granular and descriptive. If somebody make the same thing and have issues - I'll be glad to help!