🌈 Rainbow Matrix Rain Clock — RGB LED Matrix Raspberry Pi Project | techlogics.net
Build a rainbow Matrix rain digital clock on a 64×32 RGB LED panel using Raspberry Pi, Adafruit RGB Matrix HAT and PCF8523 RTC. NTP sync with offline RTC fallback. Full Python source, wiring guide and install steps.
# ============================================================
# Rainbow Matrix Rain Clock
# Generated by techlogics.net
# https://techlogics.net/electronics/rainbow-matrix-rain-clock.php
# ============================================================
#
# CAUTION: Review this code fully before running on any
# hardware. Verify all pin connections and power requirements
# match YOUR specific components. We are not responsible for
# damage to hardware, fire, or injury resulting from incorrect
# wiring, faulty components, or misuse of this code.
#
# Generated on: 20 Jul 2026
# ============================================================
#!/usr/bin/env python3
"""
Rainbow Matrix Rain Clock
Raspberry Pi + Adafruit RGB Matrix HAT + RTC
64x32 RGB LED panel · HUB75 ribbon connector
Time source priority:
1. NTP (WiFi) — syncs at startup and hourly
2. PCF8523 RTC (I2C) — used when WiFi offline
3. System clock — free-runs in between syncs
Display:
Background: rainbow-cycling Matrix rain columns
Foreground: HH:MM:SS digital clock (large pixel font)
Date line: DD Mon YYYY below the clock
"""
import time, datetime, threading, math, random, colorsys
from rgbmatrix import RGBMatrix, RGBMatrixOptions
from PIL import Image, ImageDraw, ImageFont
# ── Panel configuration ─────────────────────────────────────
COLS, ROWS = 64, 32
HARDWARE_MAPPING = 'adafruit-hat'
GPIO_SLOWDOWN = 4
BRIGHTNESS = 60
ROW_ADDRESS_TYPE = 0
MULTIPLEXING = 1
# ── Time configuration ──────────────────────────────────────
UTC_OFFSET_HOURS = 5.5
USE_24_HOUR = True
SHOW_DATE = True
NTP_HOST = 'pool.ntp.org'
# ── Rain configuration ──────────────────────────────────────
RAIN_SPEED = 10
RAIN_DENSITY = 1
RAIN_TRAIL_LEN = 8
RAINBOW_SPEED = 0.15
# ── Matrix options ──────────────────────────────────────────
options = RGBMatrixOptions()
options.rows = ROWS
options.cols = COLS
options.hardware_mapping = HARDWARE_MAPPING
options.gpio_slowdown = GPIO_SLOWDOWN
options.brightness = BRIGHTNESS
options.row_address_type = ROW_ADDRESS_TYPE
options.multiplexing = MULTIPLEXING
options.disable_hardware_pulsing = False
# ── NTP time sync ───────────────────────────────────────────
import socket, struct
def get_ntp_time(host=NTP_HOST, timeout=5):
try:
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.settimeout(timeout)
data = b'\x1b' + 47 * b'\0'
client.sendto(data, (host, 123))
data, _ = client.recvfrom(1024)
t = struct.unpack('!12I', data)[10]
t -= 2208988800 # NTP epoch to Unix epoch
return datetime.datetime.utcfromtimestamp(t)
except Exception:
return None
finally:
client.close()
# ── RTC read (PCF8523 via I2C) ──────────────────────────────
def bcd_to_dec(v): return (v >> 4) * 10 + (v & 0x0F)
def get_rtc_time():
try:
from smbus2 import SMBus
with SMBus(1) as bus:
d = bus.read_i2c_block_data(0x68, 0x03, 7)
return datetime.datetime(
2000 + bcd_to_dec(d[6]),
bcd_to_dec(d[5] & 0x1F),
bcd_to_dec(d[3] & 0x3F),
bcd_to_dec(d[2] & 0x3F),
bcd_to_dec(d[1] & 0x7F),
bcd_to_dec(d[0] & 0x7F)
)
except Exception:
return None
# ── Shared time state ───────────────────────────────────────
_time_lock = threading.Lock()
_base_utc = datetime.datetime.utcnow()
_base_mono = time.monotonic()
_time_src = 'system'
def current_local():
with _time_lock:
drift = time.monotonic() - _base_mono
utc = _base_utc + datetime.timedelta(seconds=drift)
return utc + datetime.timedelta(hours=UTC_OFFSET_HOURS)
def sync_from_ntp():
global _base_utc, _base_mono, _time_src
t = get_ntp_time()
if t:
with _time_lock:
_base_utc = t
_base_mono = time.monotonic()
_time_src = 'NTP'
return True
t = get_rtc_time()
if t:
with _time_lock:
_base_utc = t
_base_mono = time.monotonic()
_time_src = 'RTC'
return True
return False
def time_sync_thread():
while True:
sync_from_ntp()
time.sleep(3600)
# ── Rain engine ─────────────────────────────────────────────
class RainDrop:
def __init__(self, col):
self.col = col
self.y = random.uniform(-ROWS, 0)
self.speed = random.uniform(*RAIN_SPEED) if isinstance(RAIN_SPEED, (list,tuple)) else RAIN_SPEED
self.len = RAIN_TRAIL_LEN
def step(self, dt):
self.y += self.speed * dt
if self.y - self.len > ROWS:
self.y = random.uniform(-self.len, 0)
self.speed = random.uniform(6, 14)
# Build initial drops — RAIN_DENSITY drops per column
rains = []
for c in range(COLS):
for _ in range(RAIN_DENSITY if isinstance(RAIN_DENSITY, int) else 1):
rains.append(RainDrop(c))
# ── Main display loop ────────────────────────────────────────
def main():
matrix = RGBMatrix(options=options)
canvas = matrix.CreateFrameCanvas()
# Start time sync in background
sync_from_ntp()
threading.Thread(target=time_sync_thread, daemon=True).start()
hue = 0.0
prev_t = time.monotonic()
while True:
now_t = time.monotonic()
dt = now_t - prev_t
prev_t = now_t
hue = (hue + RAINBOW_SPEED * dt) % 1.0
img = Image.new('RGB', (COLS, ROWS), (0, 0, 0))
draw = ImageDraw.Draw(img)
# Draw rain
for drop in rains:
drop.step(dt)
for i in range(drop.len):
py = int(drop.y) - i
if 0 <= py < ROWS:
# Hue shifts across columns, brightness fades along trail
h = (hue + drop.col / COLS) % 1.0
v = max(0, 1 - i / drop.len)
r, g, b = colorsys.hsv_to_rgb(h, 1.0, v)
draw.point((drop.col, py),
(int(r*255), int(g*255), int(b*255)))
# Draw clock overlay (dark region behind text)
local = current_local()
if USE_24_HOUR:
t_str = local.strftime('%H:%M:%S')
else:
t_str = local.strftime('%I:%M:%S').lstrip('0') or '0'
d_str = local.strftime('%d %b %Y') if SHOW_DATE else ''
# Render text (small font fits 64px wide)
try:
fnt = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', 8)
except Exception:
fnt = ImageFont.load_default()
# Background rect for readability
tw = draw.textlength(t_str, font=fnt)
tx = int((COLS - tw) / 2)
ty = 6 if SHOW_DATE else 12
draw.rectangle([tx-1, ty-1, tx+tw+1, ty+9], fill=(0,0,0))
draw.text((tx, ty), t_str, font=fnt, fill=(255,255,255))
if SHOW_DATE and d_str:
dw = draw.textlength(d_str, font=fnt)
dx = int((COLS - dw) / 2)
draw.rectangle([dx-1, 17, dx+dw+1, 26], fill=(0,0,0))
draw.text((dx, 18), d_str, font=fnt, fill=(200,200,200))
canvas.SetImage(img)
canvas = matrix.SwapOnVSync(canvas)
if __name__ == '__main__':
main()
# ============================================================
# End of generated code — techlogics.net
# Found this useful? Visit https://techlogics.net/electronics/rainbow-matrix-rain-clock.php
# for more free CCTV, networking, electronics & finance tools.
#
# Questions or issues? https://techlogics.net/contact.php
# ============================================================
Step 1: Assemble hardware — stack the Adafruit RGB Matrix HAT + RTC on your Pi GPIO header. Connect the 64×32 LED matrix HUB75 ribbon cable to the HAT. Power the matrix panel from a dedicated 5V 4A supply, not the Pi rail. Insert a CR1220 coin cell into the RTC holder.
Step 2: Install the rpi-rgb-led-matrix library:
git clone https://github.com/hzeller/rpi-rgb-led-matrix.git
cd rpi-rgb-led-matrix/bindings/python
sudo make build-python PYTHON=$(which python3)
sudo make install-python PYTHON=$(which python3)
Step 3: Install Python dependencies:
sudo $(which python3) -m pip install --break-system-packages pillow smbus2
Step 4: Set the RTC time (first run only):
sudo $(which python3) -c "from smbus2 import SMBus; print('RTC OK')"
Step 5: Fill in your config values using the form above, then download your customised file.
Step 6: Run as root (required for GPIO):
sudo $(which python3) rainbow_matrix_rain_clock.py
To run on boot, add to /etc/rc.local before exit 0:
sudo $(which python3) /home/admin/rainbow_matrix_rain_clock.py &