PrismCube — a Weather Reactive Ambient Light Cube

by The Spanner in Circuits > LEDs

1002 Views, 15 Favorites, 0 Comments

PrismCube — a Weather Reactive Ambient Light Cube

5.png
Satisfying Prismcube Teting

Most weather devices tell you what's happening outside. PrismCube lets you feel it.

PrismCube is a minimalist ambient weather display that transforms real-time weather into a living light experience. Rather than showing forecasts on a screen, it expresses the atmosphere through a single, softly diffused glow that continuously evolves with the changing sky—from crisp blue afternoons and golden sunsets to gentle rain, dense fog, and dramatic thunderstorms.

Designed to blend into your space, PrismCube requires almost no interaction. Simply plug it in, and it automatically connects to Wi-Fi, retrieves live weather data, and adapts its lighting throughout the day. There are no buttons, no screen, and no app to check. The only interaction is turning the cube upside down to activate a vibrant Rainbow Mode before seamlessly returning to its weather visualization when placed upright.

Unlike traditional weather displays that assign one color to each condition, PrismCube recreates the feeling of the sky through carefully crafted color palettes that flow naturally over time. Every transition is inspired by the way light behaves in nature, creating a calm, immersive experience instead of simply displaying information.

Built around the Waveshare ESP32-S3 Matrix development board, PrismCube combines embedded systems, IoT, and thoughtful product design into a compact desktop companion. The enclosure was meticulously designed in Autodesk Fusion 360, balancing aesthetics, light diffusion, and functionality to create a clean, modern form that complements any workspace.

PrismCube isn't just a weather device—it's a small piece of the sky, brought indoors through light.

Sponsored By NextPCB

Building a hardware project can get expensive, but NextPCB makes it easier for makers, startups, and engineers. Right now, you can get up to $200 OFF on professional turnkey PCB assembly for orders of 10 units or less.

From PCB manufacturing and component sourcing to full assembly and testing, NextPCB handles everything in one place with reliable quality and fast service. Whether you're building an IoT device, robotics project, or custom electronics product, this offer is a great way to prototype professionally while saving money.

Try it out using my link to unlock a huge discount and claim your $200 OFF on PCBA orders!

Supplies

1.png
2.png
NextPCB.png
Powered by Waveshare

PrismCube is built around the Waveshare ESP32-S3 Matrix, an all-in-one development board featuring an ESP32-S3, an 8×8 RGB LED matrix, and an onboard QMI8658 IMU. Its compact design made it possible to build the entire project without additional hardware, keeping the electronics simple while delivering a polished final product.

If you'd like to build your own PrismCube, you can find the same development board here: Purchase Here

Tools
  1. Bambulab 3D printer
  2. PLA filament (Black and Matte White)
  3. Pliers
  4. Super glue
Software
  1. Arduino IDE
  2. Autodesk Fusion 360
  3. OpenWeatherMap (Free API account)
Github
  1. Repositorie

Designing the Enclosure

Fusion.png

I started by designing the entire enclosure in Autodesk Fusion 360, keeping the design compact, easy to assemble, and optimized for the Waveshare ESP32-S3 Matrix board.

To achieve a smooth and uniform glow, I used a lithophane-inspired diffuser instead of a standard flat diffuser. By varying its thickness, the light spreads more evenly across the surface, helping to reduce visible LED hotspots and creating a much softer ambient effect.

3D Printing the Enclosure

3.png
4.png

With the design complete, I printed all the parts using PLA filament. For the diffuser, I used matte white PLA with a 0.8 mm wall thickness. This provides excellent light diffusion while preserving the lithophane effect, resulting in a smooth, even glow across the entire surface.

For the housing, I used Black PLA with a 3 mm wall thickness to give the enclosure good rigidity and durability while maintaining a clean, minimal appearance.

Hardware Assembly

6.png
7.png

Carefully insert the Waveshare ESP32-S3 Matrix board into the 3D-printed enclosure, making sure it sits flush and the LED matrix aligns with the front opening. The fit should be snug, so avoid applying excessive force during installation. Once everything is aligned, apply a very small amount of super glue around the edges to secure the board in place.

Installing the Diffuser

8.png
9.png

Place the 3D-printed diffuser over the LED matrix, ensuring it sits evenly inside the enclosure without putting pressure on the board. Once you're satisfied with the alignment, secure it using a very small amount of super glue around the edges.

OpenWeatherMap API Key

OMW 01.png
OMW 02.png

PrismCube uses OpenWeatherMap to retrieve live weather data. Before configuring the firmware, you'll need a free API key.

Visit the OpenWeatherMap website and create a free account. After verifying your email address, open the My API Keys page, where you'll find a default API key already generated for your account. You can use this key or create a new one if you prefer. Keep your API key handy—you'll need it in the next step when configuring Config.h.

Note: New API keys may take a few minutes to become active. If PrismCube can't fetch weather data immediately after uploading the firmware, wait a short while and try again.

Configuring the Firmware (Config.h)

Before uploading the firmware, you'll need to configure PrismCube to match your own setup. To keep things simple, all user-editable settings are stored in a single file called Config.h. Whether you're connecting to a different Wi-Fi network, using your own weather location, or adjusting the lighting behavior, this is the only file you'll normally need to modify.

Configure Your Wi-Fi & Weather

Start by entering your Wi-Fi credentials and your OpenWeatherMap API key.

#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"

#define OWM_API_KEY "YOUR_OPENWEATHERMAP_API_KEY"

Next, configure the location that PrismCube will use for live weather updates. I recommend using latitude and longitude instead of a city name, as it's more accurate and avoids ambiguity.

#define OWM_USE_LATLON 1

#define OWM_LAT 34.0522
#define OWM_LON -118.2437

The firmware automatically checks for new weather every 10 minutes, providing a good balance between responsiveness and network usage.

Verify the Hardware Configuration

If you're using the Waveshare ESP32-S3 Matrix board, you won't need to change anything in this section.

#define LED_DATA_PIN 14
#define LED_COUNT 64

#define IMU_SDA_PIN 11
#define IMU_SCL_PIN 12

These values define the onboard 8×8 RGB LED matrix and the built-in QMI8658 IMU.

Customize PrismCube

You'll also find several settings that control how PrismCube behaves.

#define BRIGHTNESS_DAY 235
#define BRIGHTNESS_EVENING 153
#define BRIGHTNESS_NIGHT 45

#define BRIGHTNESS_MAX_CAP 235

The maximum brightness is intentionally limited to 235 instead of the full 255 to reduce heat while keeping the cube bright enough for everyday use.

Animation timing can also be adjusted if you'd like to customize how each weather mood feels.

#define CYCLE_CLEAR_MS 16000
#define CYCLE_STORM_MS 7000
#define CYCLE_FOG_MS 24000

Finally, you'll find a few optional settings for features such as automatic rainbow detection and debugging. The default values are already tuned for normal use, so most makers won't need to change them.

The Color Engine (PaletteEngine.h)

With the basic configuration complete, it's time to look at the part that gives PrismCube its personality. The Palette Engine is responsible for translating raw weather data into a lighting experience. Instead of deciding what the LEDs should display, it decides how the weather should feel through carefully designed color palettes and animations.

Defining the Weather Moods

The first thing you'll see is the WeatherMood enum.

enum class WeatherMood {
CLEAR, PARTLY_CLOUDY, CLOUDY, LIGHT_RAIN, RAIN, THUNDERSTORM,
FOG, SNOW, SUNRISE, SUNSET, NIGHT, RAINBOW_SPECIAL
};

Rather than working directly with weather condition IDs throughout the firmware, everything is first converted into one of these predefined moods. This keeps the rest of the code much cleaner and also makes it easy to add your own weather effects later.

Creating a Color Palette

Each mood is described using a simple structure called Palette3.

struct Palette3 {
CRGB top, mid, bottom;
uint16_t fadeMs;
uint16_t cycleMs;
bool rainbowWash = false;
};

Although the variables are named top, mid, and bottom, they don't represent different areas of the LED matrix. Since the enclosure uses a frosted diffuser, all 64 LEDs blend together into a single point of light. Instead, these three colors represent the journey the light takes over time, smoothly transitioning from one color to the next before looping back again.

Deciding Which Mood to Display

Whenever new weather data is received, the firmware calls decideMood().

inline WeatherMood PaletteEngine::decideMood(const WeatherState& w) {
if (!w.valid) return WeatherMood::CLEAR;

if (w.sunrise > 0 && w.sunset > 0) {
time_t now = time(nullptr);
const time_t window = 35 * 60;

if (now > 1700000000) {
if (llabs((long long)now - (long long)w.sunrise) < window) return WeatherMood::SUNRISE;
if (llabs((long long)now - (long long)w.sunset) < window) return WeatherMood::SUNSET;
if (!w.isDaytime) return WeatherMood::NIGHT;
}
}

if (looksLikeRainbowConditions(w)) return WeatherMood::RAINBOW_SPECIAL;

int id = w.conditionId;

if (id >= 200 && id <= 232) return WeatherMood::THUNDERSTORM;
if (id >= 300 && id <= 321) return WeatherMood::LIGHT_RAIN;
if (id >= 500 && id <= 531) return WeatherMood::RAIN;
if (id >= 600 && id <= 622) return WeatherMood::SNOW;
if (id >= 701 && id <= 781) return WeatherMood::FOG;
if (id == 800) return WeatherMood::CLEAR;
if (id == 801 || id == 802) return WeatherMood::PARTLY_CLOUDY;
if (id == 803 || id == 804) return WeatherMood::CLOUDY;

return WeatherMood::CLEAR;
}

The order of these checks is intentional. Sunrise, sunset, and nighttime are evaluated first, followed by automatic rainbow conditions, before finally checking the weather condition ID returned by OpenWeatherMap. This ensures the cube always displays the most meaningful visual experience. For example, if it's lightly raining during sunset, PrismCube will still prioritize the sunset colors instead of immediately switching to a rain palette.

Detecting Rainbow Conditions

One feature I wanted to make feel natural was Rainbow Mode. Instead of requiring manual activation, the firmware looks for weather conditions where a real rainbow is likely to appear.

inline bool PaletteEngine::looksLikeRainbowConditions(const WeatherState& w) {
if (!w.valid || !w.isDaytime) return false;

bool lightRain =
(w.conditionId >= 300 && w.conditionId <= 321) ||
(w.conditionId == 500);

return lightRain && w.cloudsPct <= RAINBOW_AUTO_CLOUD_MAX;
}

The logic is simple: if it's daytime, there's light rain, and cloud coverage is low enough for sunlight to break through, PrismCube automatically switches to its Rainbow Special palette. The same palette can also be activated manually at any time by simply flipping the cube upside down. Feel free to experiment with RAINBOW_AUTO_CLOUD_MAX if you want Rainbow Mode to trigger more or less often in your area.

Building Each Weather Palette

Once the mood has been selected, the firmware loads its corresponding palette.

case WeatherMood::RAIN:
p.top = CRGB(0x2E, 0x5D, 0x9E);
p.mid = CRGB(0x3E, 0x9B, 0xB8);
p.bottom = CRGB(0x1D, 0x3E, 0x6B);
p.fadeMs = FADE_RAIN_MS;
p.cycleMs = CYCLE_RAIN_MS;
p.rainbowWash = true;
break;

case WeatherMood::THUNDERSTORM:
p.top = CRGB(0x1A, 0x0F, 0x33);
p.mid = CRGB(0x2B, 0x16, 0x50);
p.bottom = CRGB(0x14, 0x0A, 0x24);
p.fadeMs = FADE_STORM_MS;
p.cycleMs = CYCLE_STORM_MS;
p.stormFlicker = true;
break;

Every weather mood follows the same structure: three colors, a transition speed, a cycle duration, and optional visual effects. This makes it easy to customize the look of PrismCube without changing any animation logic. If you'd like warmer sunsets, brighter rainy days, or more dramatic storms, this is the best place to personalize the project.

Adding a Subtle Temperature Tint

As a final touch, every palette receives a small color adjustment based on the current temperature.

static CRGB tintForTemp(CRGB c, float tempC) {
if (tempC <= 10.0f) {
c.b = qadd8(c.b, 12);
c.g = qadd8(c.g, 4);
}
else if (tempC >= 32.0f) {
c.r = qadd8(c.r, 14);
c.g = qadd8(c.g, 6);
}

return c;
}

Instead of changing the entire palette, this function simply nudges colder weather slightly toward blue and warmer weather toward amber. Since the tint is applied after the weather palette has been selected, the original mood remains unchanged while subtly reflecting the outdoor temperature.

Animating the Colors (GradientAnimator.h)

The GradientAnimator is responsible for turning a selected palette into a smooth, continuous animation. It's the only module that directly controls the LEDs, ensuring every transition feels soft and natural rather than abrupt.

Creating the Color Journey

The animation starts with the sampleJourney() function.

inline CRGB GradientAnimator::sampleJourney(const Palette3& p, unsigned long nowMs) {
uint16_t cycle = (p.cycleMs > 0) ? p.cycleMs : 1;

float phase = (float)(nowMs % cycle) / (float)cycle;

int seg = (int)(phase * 3.0f);
if (seg > 2) seg = 2;

float local = easeInOut(phase * 3.0f - seg);

CRGB stops[3] = { p.top, p.mid, p.bottom };

return lerpColor(stops[seg], stops[(seg + 1) % 3], local);
}

Instead of instantly changing between three colors, this function continuously blends them together. The animation loops through the palette in the following order: Top → Mid → Bottom → Top

Since the movement is eased instead of linear, the color naturally slows down before reaching each new tone, giving the cube its calm breathing appearance.

Switching Between Weather Conditions

Whenever the weather changes, the firmware loads a new palette. Instead of jumping directly to the new colors, setTarget() creates a smooth crossfade.

inline void GradientAnimator::setTarget(const Palette3& target) {

unsigned long now = millis();

if (!_hasRendered) {
_active = target;
_hasRendered = true;
_fading = false;
return;
}

_fadeFromColor = sampleJourney(_active, now);

_active = target;
_fadeStartMs = now;
_fadeDurationMs = target.fadeMs;
_fading = true;
}

Notice that the current color is sampled at the exact moment the weather changes. This means the cube always fades from whatever color is currently visible instead of restarting the animation from the beginning, making every transition feel seamless. During these transitions, the breathing effect is briefly reduced so the crossfade remains smooth and natural before gradually returning.

Rendering the Animation

Once the active color has been calculated, the render() function updates the LEDs.

inline void GradientAnimator::render() {

if (!_leds || _count == 0) return;

unsigned long nowMs = millis();

CRGB journey = sampleJourney(_active, nowMs);

// ...rendering code...
}

Every frame follows the same sequence: sample the current palette, blend smoothly if a weather transition is happening, apply the breathing effect, add optional effects such as lightning or rainbow wash, apply gamma correction, adjust the brightness, and finally send the same color to all 64 LEDs.

Although the board contains an 8×8 LED matrix, the frosted diffuser blends every LED into a single, soft point of light. That's why the entire matrix always displays one carefully calculated color. Before the LEDs are updated, the final color is also gently smoothed to eliminate tiny fluctuations, keeping the animation stable and fluid. Lightning effects intentionally bypass this smoothing so each flash remains crisp and instantaneous.

Making the Colors Look Natural

Before sending the color to the LEDs, PrismCube applies gamma correction.

inline CRGB GradientAnimator::gammaCorrect(CRGB c) {

auto videoScale = [](uint8_t x) -> uint8_t {

uint8_t r = ((uint16_t)x * x) >> 8;

return (x && !r) ? 1 : r;

};

return CRGB(
videoScale(c.r),
videoScale(c.g),
videoScale(c.b)
);
}

LED brightness isn't perceived linearly by our eyes. Without gamma correction, lower brightness levels often look washed out or uneven. Applying this correction produces much smoother fades, especially during nighttime animations and slow breathing effects.

Getting Live Weather Data (WeatherManager.h)

The WeatherManager handles everything related to Wi-Fi and OpenWeatherMap, converting the online weather data into a simple WeatherState structure that the rest of the firmware can understand.

Connecting to Wi-Fi

The first task is connecting PrismCube to your Wi-Fi network using the credentials stored in Config.h.

WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

During startup, the firmware continuously attempts to establish a connection. Once connected, it automatically synchronizes the system time and prepares to request live weather data. If Wi-Fi isn't available, PrismCube continues running and keeps trying to reconnect in the background instead of stopping completely.

Requesting the Weather

Once connected, the firmware sends a request to the OpenWeatherMap API.

http.begin(url);
int httpCode = http.GET();

The request includes your configured location and API key, returning the latest weather information in JSON format. To balance responsiveness with network usage, PrismCube checks for new weather every 10 minutes, which also matches OpenWeatherMap's update interval.

Parsing the Response

After receiving the response, ArduinoJson extracts only the information needed by PrismCube.

WeatherState state;

state.conditionId = ...
state.tempC = ...
state.cloudsPct = ...
state.sunrise = ...
state.sunset = ...

Rather than storing the entire JSON response, the firmware keeps only the values required by the color engine, including the weather condition, temperature, cloud coverage, sunrise, sunset, and whether it's currently daytime. If any field is unavailable, safe default values are used to keep the firmware running reliably.

Passing the Weather to the Color Engine

Once the WeatherState structure has been filled, it's passed to the Palette Engine, where the current conditions are translated into one of PrismCube's predefined weather moods. From this point onward, the rest of the firmware works entirely with WeatherState, keeping the networking code separate from the animation system.

Detecting Orientation (IMUHandler.h)

PrismCube doesn't use buttons or a touchscreen. Instead, it uses the onboard QMI8658 6-axis IMU to detect how the cube is placed. The only interaction is simply flipping the cube upside down to activate Rainbow Mode.

Initializing the IMU

When the cube starts, the firmware initializes the IMU and automatically calibrates its resting position.

bool IMUHandler::begin() {

if (!_imu.begin()) return false;

calibrate();

return true;
}

During calibration, the firmware records the direction of gravity while the cube is resting normally. This becomes the reference used for all future orientation detection, making the firmware work reliably without depending on fixed axis values.

Reading the Sensor

The IMU continuously measures acceleration along the X, Y, and Z axes.

_imu.readAcceleration(&_ax, &_ay, &_az);

Rather than reacting instantly to every movement, the firmware continuously updates the sensor readings and checks whether the cube has settled into a stable position.

Detecting the Orientation

Instead of checking whether a single axis reaches a specific value, PrismCube compares the current gravity vector with the calibrated reference captured during startup.

CubeOrientation IMUHandler::orientation() const {

return _orientation;

}

When the cube remains in a new position for a short time, the orientation is updated automatically. This approach makes detection much more reliable, even if different board revisions or assembly tolerances slightly change the IMU's alignment.

Activating Rainbow Mode

Once the orientation changes, the main firmware simply checks whether the cube is upside down.

if (imu.orientation() == CubeOrientation::UPSIDE_DOWN) {
rainbowForced = true;
}
else {
rainbowForced = false;
}

Place the cube upright and it displays the live weather. Flip it upside down for about a second, and it smoothly transitions into Rainbow Mode. Turning it upright again automatically returns to the current weather.

Stable and Reliable Detection

To prevent accidental mode changes while picking up the cube, the firmware only accepts a new orientation after it has remained stable for a short period. This filtering eliminates false triggers and makes the interaction feel natural, allowing the cube to respond only to deliberate movements.

Automatic Brightness (BrightnessController.h)

PrismCube automatically adjusts its brightness throughout the day, making it bright enough during daylight while remaining comfortable to look at during the evening and night. This happens entirely in the background, so there's nothing to configure once the cube is running.

Selecting the Target Brightness

The firmware first determines the appropriate brightness based on the current time of day.

if (!weather.isDaytime)
_target = BRIGHTNESS_NIGHT;
else if (weather.isGoldenHour)
_target = BRIGHTNESS_EVENING;
else
_target = BRIGHTNESS_DAY;

The target brightness is updated automatically whenever new weather information is received, allowing PrismCube to transition naturally between daytime, sunset, and nighttime.

Smooth Brightness Transitions

Instead of jumping directly to the new brightness level, the firmware gradually moves toward the target.

float step = BRIGHTNESS_CHANGE_PER_SEC * dt;

if (_current < _target)
_current = min(_current + step, _target);
else
_current = max(_current - step, _target);

Because the transition depends on the elapsed time (dt) instead of the number of frames, the brightness changes remain smooth and consistent regardless of how fast the main loop is running.

Protecting the LEDs

Before sending the brightness value to the animation engine, the firmware applies a final safety limit.

_current = min(_current, (float)BRIGHTNESS_MAX_CAP);

This ensures the LED matrix never exceeds the maximum brightness defined in Config.h, helping reduce heat while still providing a bright and comfortable display for everyday use.

The Main Sketch (PrismCube.ino)

With all the individual modules complete, the main sketch simply brings everything together. Rather than containing complex logic itself, PrismCube.ino coordinates each module in the correct order, allowing them to work together as a single system.

Initializing the System

When PrismCube starts, it initializes every module before entering the main loop.

void setup() {

weather.begin();
imu.begin();
brightnessCtl.begin();

animator.begin(leds, LED_COUNT);

}

After the IMU is initialized, the firmware briefly allows the sensor to settle before reporting its orientation.

if (imu.begin()) {
for (int i = 0; i < 6; i++) {
imu.loop();
delay(40);
}

Serial.printf("%-14s%s\n",
"Orientation",
orientationLabel(imu.orientation()));
}

This ensures the first reported orientation is stable and avoids false readings immediately after startup.

A Non-Blocking Main Loop

Once initialization is complete, the firmware continuously updates each module.

void loop() {

weather.loop();
imu.loop();
brightnessCtl.loop(weather.state());

applyMoodIfChanged();

animator.setBrightness(brightnessCtl.currentBrightness());
animator.render();

FastLED.show();

}

Each module performs only the work it needs before returning immediately. For example, the weather manager only contacts OpenWeatherMap every 10 minutes, while the animation and IMU continue updating smoothly every loop. Since nothing blocks the main loop, PrismCube remains responsive while maintaining fluid animations.

Making Debugging Easier

To make development and testing easier, the firmware prints readable names instead of raw numbers.

const char* moodName(WeatherMood m);
const char* orientationLabel(CubeOrientation o);

Instead of displaying values such as Mood = 5, the Serial Monitor shows descriptive names like Thunderstorm, Rainbow Special, or Top Up (Weather Mode), making it much easier to understand what the firmware is doing.

When DEBUG_VERBOSE is enabled, PrismCube can also display both the calculated LED color and the final color sent to the LEDs after gamma correction and brightness adjustment. This makes it much easier to verify that the animation behaves exactly as intended during development.

Uploading the Firmware

Before uploading the firmware, you'll need to install a couple of libraries and configure the Arduino IDE for the Waveshare ESP32-S3 Matrix board.

Install the Required Libraries

Open Sketch → Include Library → Manage Libraries... and install the following libraries:

  1. FastLED
  2. ArduinoJson
Important: Use ArduinoJson 6.21.x instead of version 7.x. The firmware uses the 6.x API, and the project will not compile correctly with ArduinoJson 7.

The remaining libraries, such as WiFi.h, HTTPClient.h, Wire.h, and time.h, are included with the ESP32 board package, so there's nothing else to install.

Configure the Arduino IDE

Connect PrismCube to your computer using a USB-C cable, then select: Tools → Board → ESP32 → Waveshare ESP32-S3-Matrix

If the Waveshare board isn't listed, simply choose ESP32S3 Dev Module, since all the hardware pins are configured manually in Config.h. Next, select the correct serial port from Tools → Port. Before uploading, verify the following settings:

  1. USB CDC On Boot: Enabled
  2. Partition Scheme: Huge APP (3MB No OTA/1MB SPIFFS)
  3. Flash Size: 4MB (32Mb)
  4. Flash Mode: QIO 80MHz
  5. CPU Frequency: 240MHz (WiFi)
  6. PSRAM: Disabled
  7. Core Debug Level: None
Upload the Firmware

Once everything is configured, click Upload. After the firmware is uploaded successfully, open the Serial Monitor to verify that the board connects to Wi-Fi and starts fetching live weather data.

Testing

Satisfying Prismcube Teting

Power on PrismCube and wait a few moments for it to boot. It should automatically connect to your Wi-Fi network, retrieve the latest weather data, and begin displaying the corresponding lighting effect. The transition into the first weather animation should be smooth and gradual, not instantaneous.

Compare the cube with the current weather at your configured location. The displayed colors should reflect the overall atmosphere—for example, clear skies should produce soft blue and warm golden tones, while rain and thunderstorms should shift toward cooler blues or deeper purple shades. The animation should continuously evolve rather than remain static.

Finally, flip the cube upside down and keep it still for about a second. It should smoothly transition into Rainbow Mode with a more vibrant color animation. Place it upright again, and it should automatically return to displaying the live weather.

Understanding the Weather Moods

PrismCube doesn't assign a single color to each weather condition. Instead, every weather type is represented by a carefully designed color palette that slowly transitions between three tones, creating a more natural and calming representation of the sky.

Each mood also has its own animation speed. Calm weather transitions slowly, while more dynamic conditions, such as thunderstorms, animate more quickly to better reflect the atmosphere.

  1. ☀ Clear Soft: white → Sky blue → Warm gold
  2. 🌤 Partly Cloudy: White → Sky blue → Soft amber
  3. ☁ Cloudy: Cool white → Silver → Light gray
  4. 🌦 Light Rain: Pale blue → Blue → Soft cyan
  5. 🌧 Rain: Deep blue → Cyan → Dark navy
  6. ⛈ Thunderstorm: Indigo → Violet → Near black with lightning flashes
  7. 🌫 Fog: Soft gray → Pastel violet → Pastel mint
  8. ❄ Snow: Bright white → Icy blue → Cool blue
  9. 🌅 Sunrise: Purple → Pink → Orange
  10. 🌇 Sunset: Deep purple → Magenta → Burnt orange
  11. 🌙 Night: Deep indigo → Dark violet → Almost black
  12. 🌈 Rainbow Mode: Rainbow Effect

Rather than jumping instantly between colors, PrismCube continuously blends each palette with smooth transitions, creating the feeling of a living light instead of a static display. Whenever the weather changes, the cube gently fades into the new mood, making every transition feel seamless and natural.

The result is a desktop companion that doesn't just display the weather—it quietly captures the atmosphere outside through light.

Final Thoughts

PrismCube began with a simple idea: what if weather could be experienced through light instead of displayed on a screen? What started as a concept gradually evolved into a complete product, combining industrial design, embedded systems, IoT, and thoughtful interaction into a single desktop companion.

Bringing this project to life has been an incredibly rewarding experience. From designing the enclosure in Autodesk Fusion 360 and developing the firmware to refining the lighting effects and user experience, every stage presented new challenges and valuable learning opportunities. While this project reflects my vision and design philosophy, it wouldn't have been possible without the support and collaboration of an amazing team throughout the development process.

I hope this guide helps you build your own PrismCube and encourages you to experiment with your own ideas. Whether you customize the color palettes, create new weather modes, or extend the firmware with additional features, I'd love to see where you take it.

Thank you for following along and being part of this journey. If you build your own PrismCube, feel free to share your results or improvements in the comments—they may inspire future versions of the project and help fellow makers in the community.

Happy building!