The house spirit had a mood, a colour, a diary and a face, and the face lived only on a phone and, when it felt like it, on the television. So we gave it a wall: a 3.5-inch touch display on a small microcontroller, running the open-source openHASP firmware, mounted in the living room. It shows the current face, the mood word, the one-line diary entry that explains the mood, and a few thoughts about the weather and the music. Nothing on it is interactive by design. It is a portrait that changes its mind.
A small screen with a strong opinion about byte order, a robot face in fifty moods, and a household that wanted the text readable from the couch. What followed was less an integration than a negotiation, and the screen won several rounds.
A screen with no memory to speak of
The board has no external RAM and about half a megabyte of free flash. The firmware decodes PNG files into RAM, which on this hardware means icons, not portraits. It can also read a raw 16-bit bitmap straight from flash, line by line, without holding the whole image in memory. So the faces are converted on the server into that raw format, roughly 100 KB each, and the display never sees a PNG.
Fifty faces at 100 KB do not fit in half a megabyte, and they never will. Instead the display holds exactly two image slots. When the mood changes, the server uploads the new face into whichever slot is not currently on screen, then tells the display to switch. The image being looked at is never half-overwritten, and a mood that comes back does not trigger an upload at all: the server keeps a note of what is in each slot.
Checking the colours without eyes
A raw 16-bit bitmap has two possible byte orders and the documentation does not say which one this build expects. Rather than guess, the first image uploaded was a 60-pixel test card with a red, a green, a blue and a white quadrant. A screenshot came back, four pixels were sampled, and the answer was unambiguous.
The same screenshot path then verified every layout change: the size of the face, the font sizes, the crop, whether the Slovenian č rendered at all (it did) and, later, whether the background of the portrait was truly black or merely dark. It was merely dark. The source portraits carry their own navy card; the converter now paints that card black so the face floats on the screen instead of sitting in a box.
What it says
Three labels sit to the right of the face. The mood word, in the masculine form the spirit uses for itself. Under it the diary line for that mood, a first-person sentence written when the mood was designed, so it explains the why rather than restating the word.
Under that a smaller block of thoughts assembled live: how today compares with yesterday, tomorrow's forecast in plain words with the temperature range, and the artist currently playing, if any. The thoughts refresh quietly when a song or a forecast changes; only a new mood is allowed to wake the screen, so the wall does not flash at every track.
One trap worth writing down: the server hands text to the upload script through an
environment variable serialised as JSON, and that serialisation escapes non-ASCII letters. The
first automatic push showed alergi\u010den in large friendly type. The script now
decodes what it is given before trusting it. The fonts, incidentally, cover the Slovenian
alphabet but not emoji, so the diary loses its sneeze on the way to the wall.
Brightness that follows the room
The firmware has two idle timers: dim after the first, switch off after the second. The second is disabled, so the device never goes dark on its own; it only ever dims, thirty seconds after the last event, to a level that depends on the light in the room. A new mood, a touch, or motion in the room brings it back to the awake level, which also depends on the room.
After five minutes without motion the server switches the backlight off entirely, and switches it back on at the first movement. Both levels are recomputed every five minutes as the daylight changes, so nobody has to notice that the screen became too bright at dusk.
The small annoyances
Two things came with the hardware that nobody asked for. A status LED on the front glowed blue, then red once the blue channel was under control. Its three pins are exposed in the firmware's GPIO table, where one of them refused to be saved through the web form and then quietly accepted the same value over MQTT.
All three are now configured as an inverted mood light that is kept off. And every reconnect popped a message box with the device's IP address for twenty seconds; that was a one-line command file on the device's flash, which now no longer exists. Both are the kind of default that is sensible for a developer and wrong for a wall.
The display now shows a face, a word, an explanation and a thought, in that order, at a size that can be read from three metres by someone who has just woken up. Everything else it might have shown was sacrificed to that, and rightly.
A lamp of its own
The face on the wall got a body: a colour bulb in a lamp that will wear a translucent head. The bulb shipped speaking one protocol and was switched to the other with a ritual of power cycles; factory-fresh it needed only the twelve-cycle half of the ritual, where a previously paired bulb had needed a reset first. Over the mesh it gained what the first protocol had not offered the controller: effects, flashing and smooth transitions.
It does not hold a colour. On every settled mood change it breathes for twenty seconds, five slow swells between a floor and a ceiling, both on sliders, then either goes dark or, when a toggle is on and the room's radar sees somebody, settles into the new colour at the floor level and glows there. A test button plays the whole performance on demand, which is how the sliders get tuned. Time of day, alarms and the spirit's opt-in switch all silence it.
The first performance played to an empty stage. The automation's trace was flawless, every command sent with the right colour and brightness, and the bulb stayed dark, because it was still plugged into the smart socket that the protocol-switch ritual leaves off. "Trace clean, light dead" now has a standing first question: is it powered?