How I Made a Totally Practical, WiFi-Calibrated Mini Wall Clock For $20

w/MicroPython and ESP8266; not perfect but still works fantastic, beautifully and accurate

I’ve made and programmed quite a few DIY WiFi clocks . The core code is always the same, but you can slap different displays on it. I have made clocks using very small 0.96" OLED display, with shiny HD44780 LCDs, with matrix LED display, even one with a motorized flip clock. I have one in my office using a RGB LCD which change colors like rainbow.

However, I wanted to make something very practical for myself. The problem with my other clocks was that the Maker-oriented display modules are usually too small to see beyond a couple of meters. They are fine on your desk, but not easy to see across the room.

So this is what I’ve came up with:

The Modules

Photo by Rachael Gorjestani on Unsplash
  • Mcrocontroller: a WeMos D1 mini (ESP8266)
  • a TM1637 4-digit 7-segment LED display
  • a NeoPixel (WS2812) ring with 24 RGB LEDs
  • a BH1750 ambient digital light sensor (GY-30 and GY-302 are functionally the same)
  • a DS3231 real time clock (RTS) module
  • a push button
  • a mini breadboard and jumper wires

The clock does the following things:

  1. Look up time with NTP protocol once an hour and save the time in the RTS module. The RTS has a battery so the time will be persistent even if the system is turned off.
  2. Display time on the 7-segment LED display and the NeoPixel ring with the RTS time. Without updates the RTS can stay accurate for some time (it is said DS3231 would be off several minutes per year).
  3. The NeoPixel ring use three dots as the hour, one dot as the minute and a blinking dot as the second.
  4. If you push the button, the 7-segment LED display will display year, month and date for a few seconds.
  5. The light sensor adjusts the brightness of the 7-segment LED display and the NeoPixel ring based on room light.
  6. The onboard LED indicates if it is (still) connected to WiFi.

It’s pretty simple, but this is what really matters: I can see both LED display from another side of the room under any light conditions. It stays deadly accurate as long as WiFi is on (it can even function without WiFi). NeoPixels are notorious for their power consumption but I only need five LEDs at any time and only use 1/5 power at maximum. And the whole thing costed about $20 USD (not include the wood frame in the photo).

Yes — one problem with ESP8266 is that it would reportedly freeze every few days. I’ve noticed that my clock would reboot itself once in a while, but actually not very often.

I suppose you can try to use a ESP32 instead, but the smallness of D1 mini allowed me to assemble everything under the NeoPixel ring.

The Wiring

Photo by ThisisEngineering RAEng on Unsplash
  • D1 (GPIO 5): SCL of BH1750/DS3231
  • D2 (GPIO 4): SDA of BH1750/DS3231
  • D4 (GPIO 2): onboard LED
  • D5 (GPIO 14): CLK of TM1637
  • D6 (GPIO 12): DIO of TM1637
  • D7 (GPIO 13): DIN of NeoPixels
  • D8 (GPIO 15): button (with internal pulled-up)

TM1637, BH1750 and DS3231 are powered by 3.3V. NeoPixels are powered by 5V, which is directly connected to the USB port. I power the whole system with a 5V 1A charger.

The Code

Photo by Ryland Dean on Unsplash

The code is written with MicroPython. I update the firmware whenever there are new version possible. You can find instructions here:

Remember not to select “Erase flash before installing” if you just want to update firmware.

Drivers are also need to be uploaded:

Upload, and to your board.

Photo by Rostyslav Savchyn on Unsplash

Finally, upload the following code (set your own WiFi name and password) to your board as

SSID = 'your_wifi_name'
PW = 'your_wifi_password'
HOUR_OFFSET = 0 # time zone hour offset
MAX_LUX_LEVEL = 400 # room lux is usually 300-600
MAX_NEO_LEVEL = 50 # max 255
MAX_DIGIT_LEVEL = 7 # max 7
HOUR_COLOR = (0, 91, 187)
MINUTE_COLOR = (255, 213, 0)
SECOND_COLOR = (255, 255, 255)
import network, urequests, ntptime, utime, gc
from machine import Pin, I2C, Timer
from neopixel import NeoPixel
import bh1750fvi
from tm1637 import TM1637
from DS3231 import DS3231
gc.enable()i2c = I2C(scl=Pin(5), sda=Pin(4))
ds = DS3231(i2c)
btn = Pin(15, Pin.IN, Pin.PULL_UP)
led = Pin(2, Pin.OUT, value=1)
tm = TM1637(clk=Pin(14), dio=Pin(12))
np = NeoPixel(Pin(13, Pin.OUT), 24)
tm.numbers(0, 0)
np.fill((1, 1, 1))
lux = 0
s_prev = 0
colon_flag = False
def translate(value, l1, l2, r1, r2):
leftSpan = l2 - l1
rightSpan = r2 - r1
valueScaled = float(value - l1) / float(leftSpan)
return r1 + (valueScaled * rightSpan)
def color(color):
return tuple([round(MAX_NEO_LEVEL * (c / 255) * (max(lux, 10) / MAX_LUX_LEVEL))
for c in color])
def rtcUpdate(timer):
while wifi.isconnected():
dt = utime.localtime(utime.time() + HOUR_OFFSET * 3600)
ds.DateTime([dt[0], dt[1], dt[2], dt[6], dt[3], dt[4], dt[5]])
def lightLevelUpdate(timer):
global lux

lux = min(bh1750fvi.sample(i2c), MAX_LUX_LEVEL)
tm_level = round(translate(lux, 0, MAX_LUX_LEVEL, 1, MAX_DIGIT_LEVEL))

led.value(not wifi.isconnected())
def clockUpdate(timer):
global s_prev, colon_flag
now = ds.DateTime()

if btn.value():
np.fill((0, 0, 0))
for d in now[:3]:
tm.write([0, 0, 0, 0])

h, mi, s = now[4], now[5], now[6]
np.fill((0, 0, 0))
np_h = (h % 12) * 2 + (1 if mi >= 30 else 0)
np.mi = round(mi / 2.5) % 24
np[np_h] = color(HOUR_COLOR)
np[(np_h - 1) if (np_h - 1) >= 0 else 23] = color(HOUR_COLOR)
np[(np_h + 1) if (np_h + 1) < 24 else 0] = color(HOUR_COLOR)
np[np.mi] = color(MINUTE_COLOR)

if s_prev != s:
colon_flag = not colon_flag
np[round(s / 2.5) % 24] = color(SECOND_COLOR)
tm.numbers(h, mi, colon=colon_flag)

s_prev = s
wifi = network.WLAN(network.STA_IF)
wifi.connect(SSID, PW)
t = utime.ticks_ms()
while wifi.status() == network.STAT_CONNECTING:
if utime.ticks_diff(utime.ticks_ms(), t) >= 30000:
rtcUpdate(None)timer_rtc, timer_lightlvl, timer_display = Timer(-1), Timer(-1), Timer(-1)
timer_rtc.init(mode=Timer.PERIODIC, period=3600000, callback=rtcUpdate)
timer_lightlvl.init(mode=Timer.PERIODIC, period=2000, callback=lightLevelUpdate)
timer_display.init(mode=Timer.PERIODIC, period=100, callback=clockUpdate)
Photo by CHUTTERSNAP on Unsplash

The core of the code is consisted of three timers (asynchronous functions running on schedule): one for updating the time, one for displaying the time, and one for measuring the brightness level.

Since the NeoPixel ring has 24 LEDs, every LED represents 2 to 3 minutes/seconds of a normal clock. The hour uses 3 LEDs and shifts forward 1 LED every half an hour. This is close enough to represent clock hands.

The Result

Later I bought a wooden photo frame and glued the mini breadboard on the glass plate, hanged it on the wall. It runs smoothly for almost one and half years now (and have went through several version of firmware) when I wrote this article.

If you are interested making your own MicroPython clock, I have two basic script (one use NTP and one use a RESTful API) that can be the basis of something more complicated.




Technical writer, former translator and IT editor. My writing power is 100% generated by coffee.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Introduction to Java Servlet

Lobster Daily #66 — Daily Recap — May 5:

Flutter Installation on Mac 🧑‍💻 —  zsh: command not found: flutter, how to solve this easily.

Understanding Scheduler Resilience in JD Edwards

The OpenClassrooms iOS app crash rate

Alan Wang

Alan Wang

Technical writer, former translator and IT editor. My writing power is 100% generated by coffee.

More from Medium

Weekly Technology Newsletter — Issue #18

Macaca App Inspector Installation — Mac Air M1

Address cleansing & geocoding for parcel delivery logistics

| Parcel delivery driver in front mobile device with map and pin

5 Best Jasper AI Alternatives In 2022