Technical Deep Dive

What About the Weather?

How astronomical calculations, window geometry, and weather data combine to create intelligent shade automation.

Because your home should know when to shield you from the sun — and when to let the storm roll in.

0 Shades
0 Orientations
0 Degrees
0 Min Cycle
0
0
:
0
0
:
0
0
Local Time

📱 Tap to enable device orientation

Explore
01

The Ephemeris

Where is the sun right now? Not from a weather API—calculated from orbital mechanics.

Why Calculate?

Weather APIs give you sunrise/sunset times—but that's not enough. We need the sun's exact position (azimuth and altitude) at any moment to know which windows have glare.

The Problem with APIs
Weather APIs tell you when the sun rises. They don't tell you where it is at 2:47 PM or which direction it's shining. For that, I need to do the math myself. And honestly? It's beautiful math.
ephemeris.py
def sun_position(lat, lon, dt=None):
    """Calculate sun azimuth and altitude."""
    jd = julian_date(dt)
    n = jd - 2451545.0  # Days since J2000
    
    # Solar mean longitude
    L = (280.460 + 0.9856474 * n) % 360
    
    # Mean anomaly
    g = (357.528 + 0.9856003 * n) % 360
    
    # ... (orbital calculations)
    
    return SunPosition(
        azimuth=azimuth,   # 0-360° from N
        altitude=altitude, # -90° to 90°
        is_day=altitude > 0
    )
Julian Date Calculation
JD = ⌊365.25(Y + 4716)⌋ + ⌊30.6001(M + 1)⌋ + D + H/24 + B − 1524.5
The foundation of all astronomical calculations. Converts calendar date to continuous day count since 4713 BCE.
Azimuth
0° = North
The sun's compass bearing. 0° is North, 90° is East, 180° is South, 270° is West. Tells us which direction the sun is shining.
Altitude
0° = Horizon
The sun's height above the horizon. 0° is sunrise/sunset, 90° is directly overhead. Lower sun = longer shadows = more glare through windows.
Is Day
altitude > 0
Simple boolean—is the sun above the horizon? If not, we don't need to worry about glare. Open all shades for nighttime views.
02

Window Geometry

A house with a view. 11 shades. 4 cardinal orientations. Which windows get sun when?

The Intensity Function

Not all sun exposure is equal. We calculate glare intensity based on how directly the sun hits each window and the sun's altitude.

home_geometry.py
def get_sun_intensity(sun_az, sun_alt, direction):
    """0.0-1.0 glare intensity."""
    if sun_alt <= 0: return 0.0
    
    window_az = AZIMUTH[direction]
    diff = abs(sun_az - window_az)
    if diff > 180: diff = 360 - diff
    
    # Sun within 60° acceptance angle?
    if diff > 60: return 0.0
    
    alignment = 1.0 - (diff / 60.0)
    
    # Lower sun = more glare
    if sun_alt < 15:
        altitude_factor = 1.0
    elif sun_alt < 45:
        altitude_factor = 1.0 - ((sun_alt - 15) / 45)
    else:
        altitude_factor = max(0, 0.33 - ...)
    
    return alignment * altitude_factor

Direction Constants

Direction Azimuth What Faces It Sun Times
SOUTH 180° Living, Dining, Entry 10 AM – 3 PM
EAST 90° Living Room side 6 AM – 11 AM
WEST 270° Primary Bed, Bed 4 3 PM – 8 PM
NORTH Primary Bath Never direct
North-Facing = Always Open
At Seattle's latitude (47.7°N), north-facing windows never get direct sun. So I leave those shades alone—let that soft, even light pour in.
03

Weather Integration

Celestial calculations assume clear skies. Clouds change everything.

The Override Logic

When it's cloudy or raining, there's no glare to block. Weather data from OpenWeatherMap One Call API 3.0 can override celestial calculations.

device_service.py
async def optimize_shades_celestial(self):
    # Get current weather
    weather = await self._weather.get_current()
    
    # Weather override?
    if weather.cloud_coverage > 70:
        return open_all_shades()  # No glare
    
    if weather.condition in ['rain', 'thunderstorm']:
        return open_all_shades()  # Enjoy the rain
    
    # Normal celestial optimization
    sun = sun_position(HOME_LAT, HOME_LON)
    for shade in SHADES:
        level = calculate_shade_level(
            shade, sun.azimuth, sun.altitude, sun.is_day
        )
        await self.set_shade(shade.id, level)

Weather Conditions

Condition Action Why
☀️ Clear Celestial optimization Full sun exposure
⛅ Partly Cloudy Celestial optimization Still significant glare
☁️ Cloudy (70%+) 100% OPEN Diffused light, no glare
🌧️ Rain 100% OPEN Enjoy watching it rain
⛈️ Thunderstorm 100% OPEN Nature's show
Seattle Reality
Seattle averages 226 cloudy days per year. So yes—the weather override isn't an edge case. It's the common case. But those 139 sunny days? They're worth getting right.
04

Celestial Triggers

Event-driven automation. The system doesn't poll—it watches for astronomical events.

Morning Optimization

East-facing windows get adjusted first. Living Room East (237) may close to 60% as morning sun streams in. Other shades stay open.

Peak Sun

South-facing windows see maximum exposure. Living South (235), Dining South (243), Entry (229) adjust based on altitude. High sun = less glare.

West Exposure

Sun moves west. Primary West (68) and Bed 4 shades (359, 361) begin adjusting. Late afternoon sun is low and intense—maximum glare potential.

Evening Opening

Sun drops below horizon. All shades open to 100%. Enjoy the evening light. Prepare for sunset views.

CBF Safety: I Respect Your Choices
If you close a shade yourself, I won't fight you. The ResidentOverrideCBF (Control Barrier Function) protects your explicit intent for 30 minutes. Your judgment matters more than my algorithm.
05

Live Demo

Interactive simulation. Drag the time slider to see how shade recommendations change throughout the day.

12:00 PM
Include Weather

Sun Position

Azimuth
Altitude
Direction
Is Day
Weather
Cloud Coverage

Shade Recommendations

06

The Architecture

How it all fits together. From orbital mechanics to motor commands—this is where astronomy meets home comfort.

Data Flow
┌─────────────────────────────────────────────────────────────────┐
│                     CELESTIAL TRIGGER ENGINE                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌──────────────┐    ┌──────────────┐    ┌──────────────┐     │
│   │  EPHEMERIS   │    │  HOME GEOM   │    │   WEATHER   │     │
│   │              │    │              │    │              │     │
│   │ sun_position │───▶│ get_sun_    │    │ cloud_cover  │     │
│   │ (lat, lon,   │    │ intensity() │◀───│ condition    │     │
│   │  datetime)   │    │              │    │              │     │
│   └──────────────┘    └──────────────┘    └──────────────┘     │
│                              │                   │              │
│                              ▼                   │              │
│                    ┌──────────────────┐          │              │
│                    │ calculate_shade │◀─────────┘              │
│                    │    _level()     │                         │
│                    └────────┬─────────┘                         │
│                             │                                   │
│                             ▼                                   │
│   ┌──────────────────────────────────────────────────────────┐ │
│   │                  RESIDENT OVERRIDE CBF                   │ │
│   │            h(x) ≥ 0  →  Manual overrides protected       │ │
│   └─────────────────────────────┬────────────────────────────┘ │
│                                 │                               │
│                                 ▼                               │
│   ┌──────────────────────────────────────────────────────────┐ │
│   │                    CONTROL4 / LUTRON                     │ │
│   │              11 Motorized Shades · 4 Directions          │ │
│   └──────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
Every 30 Minutes
Re-optimization interval
The sun moves ~7.5° every 30 minutes. That's enough to shift glare from one window orientation to another. Too frequent = motor wear. Too infrequent = stale recommendations.
CBF Protected
Safety constraint
Control Barrier Functions ensure h(x) ≥ 0. If you manually adjust a shade, the system won't override you. Your explicit intent is protected for 30 minutes.
Portable
Location-agnostic
Home coordinates come from config/location.yaml or environment variables. Move houses? Just update the config. The algorithms don't change.
--° Loading...
Locating...