Home automation field notes All concepts

house spirit

A face on the wall

Fifty faces on a display with room for two, a byte order settled by a screenshot, and a lamp that breathes. The spirit gets a body, and immediately gets opinions about brightness.

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.

The wall display: Sparky's green robot face on the left, on the
right the mood word in large type, a one-line explanation of the mood and two smaller lines about
yesterday's weather, tomorrow's forecast and what is playing
Captured from the device itself, not photographed. The firmware can screenshot its own framebuffer over HTTP, which turned out to be the most useful feature it has: the machine colleague that built this page could see every layout attempt without anyone walking to the wall.

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.

Where the intelligence lives. The display knows nothing about moods, faces or the house. It knows two file names and a page layout. Everything else, including the decision of what to show and when to wake up, runs on the server, which is the only place where it can be changed without touching the wall.

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

Two curves of backlight level against room illuminance
on a logarithmic axis: the awake level rising from 40 in darkness to 255 in daylight, the dimmed
level rising from 5 to 50
Backlight against illuminance measured by the living room motion sensor. A logarithmic ratio, because a screen at 255 in a dark room is a lamp, and one at 40 in daylight is a mirror.

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.

Verify the outcome, not the configuration. Every change to this display ended with a screenshot pulled from the device and looked at. The byte order, the missing font glyph, the escaped diacritic and the not-quite-black background were all invisible in the configuration and obvious in the picture. A device that can show you what it is showing is worth more than one that can only tell you what it was told.

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?