Andy Stanish

July 8, 2026

Warming My LG TV from Sunset to Sunrise

I'm an early riser, on screens earlier in the morning than I should be, and a TV throwing cool blue-white light across a still-dark room before dawn is about the least gentle way to start a day. My LG C9 actually ships with a fix — Eye Comfort Mode, which warms the picture and pulls the blue out, the same idea as f.lux on a laptop. The catch is you have to remember to turn it on at night and off after sunrise, and I never do: either the room's blasting blue at 5 AM, or the afternoon IndyCar race looks like it was shot at sunset. So I handed the job to a Raspberry Pi.

lg-tv-enhancer is a small daemon that flips Eye Comfort Mode on at sunset and off at sunrise, every day, on its own. It talks to the TV over the LAN with bscpylgtv (webOS's local API), works out sunrise and sunset from my coordinates with astral, and runs as a systemd unit configured entirely from the environment. That's the whole thing — but the part worth writing down is how it decides when to act.

A reconcile loop, not a scheduler

The obvious build is a scheduler: fire a "turn it on" job at sunset and a "turn it off" job at sunrise. I didn't, because a scheduled job fires exactly once — and my TV is usually off at sunset. Miss that one instant (TV asleep, Pi rebooting, a network blip) and the whole night goes by cool and blue with no second chance.

So instead of firing on events, the daemon runs a reconcile loop — the same shape a Kubernetes controller uses. Every tick, sixty seconds by default, it computes the current solar phase and asks one question: is the TV in the mode it should be in right now? If yes, do nothing. If no, fix it. Desired state is a pure function of the sun, and the loop just drags reality toward it. When the TV was off at sunset, the correction simply lands on the first tick after it rejoins the network. There's no moment to miss, because it isn't watching for a moment — it's watching for a mismatch.

It's not a nag

The flip side of "always correcting mismatches" is that a naive version would fight me. Turn Eye Comfort off by hand one night because a film's real colors matter more than my eyeballs for a couple of hours, and a loop that re-asserts every tick would slam it back on sixty seconds later, and again, and again. Infuriating. So it doesn't do that.

The rule is: apply a phase once, when it changes, then leave the TV alone until the next transition. Night falls, it sets Eye Comfort on, and that's the last it touches the setting until sunrise. Override it in between and the override stands — the daemon has already had its say for the night. Automation that honors a manual override is the whole difference between a tool and a poltergeist.

The setting you can write but can't read

webOS turned out to be a slippery thing to automate, in three specific ways.

First, Eye Comfort Mode is write-only on this firmware. My first cut did the textbook thing: read the current value, decide whether it needs changing, write, then read back to confirm. Except asking the C9 for that setting returns Some keys are not allowed for the request. ( eyeComfortMode ) — so the read failed every single tick and nothing was ever applied. The TV will happily accept the write; it just won't tell you what the value currently is. So a refused read now degrades to a blind write that's simply trusted — send it, assume it took — while a real timeout still propagates, so a dead connection keeps retrying instead of being mistaken for success.

Second, this TV will vanish from the network without closing its TCP connection. It just stops answering, and a naive await then hangs forever waiting on a reply that's never coming. Every call to the TV is wrapped in a timeout, so a silent disappearance turns into a retry on the next tick instead of a wedged daemon.

Third, the one that cost me a genuinely confusing evening: the webOS client saves its pairing key after you pair, but never reads it back on startup. Leave the key unset and every restart throws the "allow this device?" prompt up on the TV as if it's meeting the Pi for the first time. The fix is to pin the key yourself in config so the daemon reuses it silently. Small thing, obvious in hindsight, invisible until it isn't.

What's next: a ramp instead of a switch

Right now it's a clean binary — cool all day, warm all night, flipped at the horizon. The version I actually want is circadian: instead of one hard step at sunset, gently ramp the color temperature warmer across the whole evening, the way daylight itself falls off. I have it half-built and parked, because the honest answer is that it's messier than it sounds — driving color temperature per picture mode fights the TV's own ISF Dark/Bright mode switching, and I'd rather ship the simple thing that works than a clever thing that ambushes me at 5 AM before I've had coffee. The binary flip captures most of the benefit anyway. The smooth ramp is the someday version, waiting on a branch for a morning I feel like untangling it.