September 2, 2024 at 11:38
This lamp was an idea I had since the Covid lockdowns. My office didn’t have much light at night, and I missed the old high school days filled with blacklight posters, lava lamps, and glow-in-the-dark stars on the ceiling. This was my attempt at modernizing and making that part of my life accessible once again.
There was a debate over how to classify this project. The original structural concept was exactly what you’re seeing above: a shade surrounding a cylindrical pixel grid. While browsing STL hosting sites for unrelated game-themed design ideas, I stumbled across someone who had already designed what I was envisioning. I could have redesigned the exact same thing from scratch, or I could take the ego hit on a 100% custom project and use this as a kick-starter to get the idea completed. Obviously, I didn’t waste the time. In programming and Program Management, if there’s something that solves your problem, why build and test a new solution if you don’t need to? Use what exists and modify it to your own needs.
Over the last few weeks, this house has served as an AliExpress delivery hub. That site stocks absolutely everything for a maker, and I’m in love. On a whim, I grabbed quite a few of these $9 8x32 LED pixel grids (link) just because they’re awesome. They’re so cool and versatile that my imagination explodes with the number of projects they can be added to.
The LED matrices are wrapped around the inside of this pixel grid, controlled by a WLED controller, and powered by a 12V supply. I wanted to give the lamp a proper aesthetic and a place to hide the power cable, so I designed a few bases and a leg to elevate the lamp and snake the wire through. I’m really impressed with how it came out. It feels so 1970s in here.
The best part is that the code matches the schema from my Tron Lights. If you’re unfamiliar, my office “Tron Lights” match the colors of the day as it progresses (example below). So as the lava blobs slowly morph and move 360° around the lamp, the theme will be harmonious with all the other lights and the day’s color temperature outside.
The code mimics Tron Lights in the way everything is addressed, but these LEDs are treated as a grid rather than a linear string. You’ll notice the (0,1)
index addressing in the colors
set below. It’s a bit more complicated than a basic LED string, but the below gets simple if you stare at it long enough.
I did tell WLED to switch over to a calm fireplace animation between 20:00 and 04:00, but it didn’t handle the transitions well. Sometimes the fire would animate from the top down, or the colors on the lava lamp would be off. Eventually, I settled on leaving it as a lava lamp until I can figure out how to consistently handle the transition without freaking out WLED. Oh well… on to another project.
pixel_light.py
import requests
import logging
from datetime import datetime
# WLED device address
address = 'http://192.168.1.5/json/state'
# Test color settings
colors = {
1: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]}]},
2: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]}]},
3: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]}]},
4: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]}]},
5: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[0, 52, 89], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[0, 52, 89], [0, 0, 0]]}]},
6: {'on': True,'bri': 25,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[0, 81, 118], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[0, 81, 118], [0, 0, 0]]}]},
7: {'on': True,'bri': 25,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[7, 92, 133], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[7, 92, 133], [0, 0, 0]]}]},
8: {'on': True,'bri': 25,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[13, 138, 168], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[13, 138, 168], [0, 0, 0]]}]},
9: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[89, 191, 194], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[89, 191, 194], [0, 0, 0]]}]},
10: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[197, 228, 193], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[197, 228, 193], [0, 0, 0]]}]},
11: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[227, 224, 122], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[227, 224, 122], [0, 0, 0]]}]},
12: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[246, 207, 100], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[246, 207, 100], [0, 0, 0]]}]},
13: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[255, 188, 107], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[255, 188, 107], [0, 0, 0]]}]},
14: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[252, 181, 93], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[241, 122, 113], [0, 0, 0]]}]},
15: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[253, 175, 92], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[253, 175, 92], [0, 0, 0]]}]},
16: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[244, 150, 78], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[244, 150, 78], [0, 0, 0]]}]},
17: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[241, 122, 113], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[241, 122, 113], [0, 0, 0]]}]},
18: {'on': True,'bri': 20,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[213, 102, 135], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[213, 102, 135], [0, 0, 0]]}]},
19: {'on': True,'bri': 25,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[124, 59, 133], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[124, 59, 133], [0, 0, 0]]}]},
20: {'on': True,'bri': 30,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[94, 74, 111], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[94, 74, 111], [0, 0, 0]]}]},
21: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[46, 24, 111], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[46, 24, 111], [0, 0, 0]]}]},
22: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]}]},
23: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]}]},
0: {'on': True,'bri': 23,'seg': [{'id': 0,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]},{'id': 1,'fx': 121,'sx': 128,'sp': 128,'ix': 255,'pal': 128,'col': [[33, 37, 101], [0, 0, 0]]}]},
}
def set_color(address, color):
try:
response = requests.post(address, json=color)
if response.status_code == 200:
logging.info(f'Successfully set {address} to {color}')
else:
logging.error(f'Failed to set {address} to {color}, status code: {response.status_code}')
except requests.exceptions.RequestException as e:
logging.error(f'Error setting {address} to {color}: {e}')
# Starty Farty
current_hour = datetime.now().hour
color = colors.get(current_hour)
if color:
set_color(address, color)
logging.info(f'All addresses set to color for hour {current_hour}.')
else:
logging.error(f'No color setting found for hour {current_hour}')
Discussion here: https://x.com/cmcwain/status/1830693442802049252