Skip to content
abradburnePublic

About

frame

Resources

Stars

20 stars

Watchers

0 watching

Forks

Latest commit

 

History

41 Commits

Folders and files

Repository files navigation

Mado

Mado (窓, "window") turns an old Android photo frame into a display your other machines and agents can send things to. It is not a browser kiosk: the frame runs a small native app, so it stays smooth on very modest hardware.

The dashboard: a painting of the day with the time, weather and projects

What it does

  • A dashboard that is a painting. Each day the hub picks a public-domain painting from the Art Institute of Chicago, or from the other sources and kinds of work you tick. The frame shows it with the time, the weather and your projects in one of four layouts, all coloured from the painting itself. Tap the label to read about it and get a QR code for the museum's page; tap elsewhere for the next layout. You choose what it draws from: a period, a subject, and what to leave out.
  • Weather in a sentence. "Rain easing by three", with the temperature and sunset. Other places you care about get a line each, with their local time. The frame fetches forecasts itself, so weather keeps working when the hub is down. Tap the weather for a page with the place's sky, the next 24 hours, the week, and a tab for each place.
  • Project status from your agents. An agent reports where a project stands and the dashboard keeps the latest word on each, beside its icon (or its initials, if it has none). Tap them for a page of every project with its whole note.
  • Liquid alerts. Anything on your network can put a message on the glass: a whisper (a bead at the edge), a nudge (one line that drains away) or urgent (stays until someone taps it). The frame keeps the last 200, and the rail's Messages page lists them with when each came and whether it was read.
  • Now playing. The cover of whatever Apple Music or Spotify is playing on your Mac takes over the screen while music plays. A small menu bar app on the Mac does the reporting. Tap the cover to fill the screen with it. Under the progress bar are previous, play or pause, and next, which work the player on the Mac; previous starts the track again unless it has only just begun. "Close" in the corner goes back to the dashboard. While a track is on, the dashboard names it on a line by the date; tap that to come back.
  • MCP and HTTP. Agents get tools; scripts get plain endpoints.
  • The rail. Tap or swipe the left edge for scenes, the weather, projects and messages pages, brightness and the way back to the launcher.
Editorial split layout Pigments layout
Learn more about the painting A nudge

An urgent alert

Tama, a small creature, lives on the glass above every scene. It wanders, amuses itself (draws in fogged glass, stacks pebbles, chases dust, naps), dresses for the weather, bops to music, frets beside urgent alerts, and can be poked, carried and tilted. The rail has switches to hide it and to mute it.

Tama is also a small pet. It gets hungry and holds out its bowl; it catches the sniffles, the hiccups, a tummy ache or, from too long in the arcade, a glitch. A tap on it feeds it or gives it its medicine. Nothing ever goes worse than a mope: it gets better by itself, and on its quests it forages when nobody feeds it.

Those quests are Tama's Quest, a page on the rail: an RPG that plays itself in big pixels. Every quest is set in a realm made of two genres mashed together (a Hardboiled Pantry, a Haunted Space Station, a Cosmic Karaoke Box), with creatures made up on the spot, loot, riddles, shops, friends who join the party, and a boss. Nobody dies; foes are befriended, and Tama faints and is carried back to camp. It goes on whether or not anyone watches, slowly, and faster while the page is open; tap the scene to cheer Tama on in a fight. The two worlds leak into each other: Tama wears its best hat on the glass, celebrates its level-ups there, and now and then a quest creature wanders out onto the dashboard and has to be shooed back in. The real night, weather, music and painting of the day turn up in the quest, and a nap on the glass is a nap in the quest.

At night the frame sleeps: a deep amber clock on black with the backlight turned right down, and Tama asleep. Whispers and nudges wait for the morning; urgent alerts still show. Touch it or knock twice on the desk and it comes back for a moment. The hours are set on the hub's page (23:00 to 07:00 unless you change them), the rail has a Sleep switch, and the glass also turns itself down between sunset and sunrise.

Planned: canvases that agents can draw on.

How it fits together

 senders                         hub                          frame
 ───────                         ───                          ─────
 Mac menu bar app ─────────┐
 agents over MCP ──────────┼──▶  mado-hub  ◀── one socket ──  Mado app
 curl, scripts, cron ──────┘     (Rust)       frame dials out (Kotlin)
Folder What it is
hub/ mado-hub, the brain. Rust. Serves the frame's WebSocket, an HTTP API, MCP, and a web page where all configuration lives. Runs on a home server (Docker or not) or on a Mac.
frame-app/ The Android app that draws everything. Kotlin, plain Canvas views, minSdk 23.
mac/ Mado for Mac, a menu bar app that reports what Apple Music or Spotify is playing. Swift.

The frame holds no secrets and listens on no ports: it opens one outbound connection to the hub and draws what it is told. Its clock and weather keep working when the hub is down.

Setting it up

With an agent: point your coding agent at docs/agent-setup.md. It covers checking the device, building, installing, starting at power-on and wiring up MCP, and it says what the agent must ask you before doing. docs/boot-splash.md replaces the vendor's boot logo and animation with Mado's, if you want the frame to be Mado from the moment it gets power.

By hand, the short version:

  1. The hub, on any machine the frame can reach:

    docker compose up -d --build        # or: cd hub && cargo run --release

    It listens on port 7878 (MADO_BIND) and keeps its settings in ./data (MADO_DATA). Open http://HOST:7878/ for the web page.

  2. The frame app. With the frame connected over USB and adb working:

    cd frame-app && ./run.sh

    A frame that has no hub looks for one on its network and connects by itself (see "Finding the hub" below). To name the hub yourself instead, run ./run.sh ws://HOST:7878/frame; either way the address is remembered. Put machine-specific setup such as JAVA_HOME or ANDROID_SERIAL in frame-app/env.local.sh.

  3. The menu bar app (optional), on the Mac with the music. It needs Xcode and XcodeGen:

    cd mac && scripts/build.sh install

    Or download the notarized Mado.zip from the latest release and drag Mado to Applications.

    Choose "Hub Address…" from its menu and enter the hub's address. macOS asks once for permission to control Music or Spotify; the app never launches either. The menu also has a switch for each player, one to pause sharing, and "Start at Login".

You need adb access to the frame. For most photo frames that means rooting it first, which depends on the device and is not covered here.

Talking to the hub

The hub speaks both plain HTTP and MCP; they do the same things.

curl -X POST http://HOST:7878/api/notify \
  -H 'content-type: application/json' \
  -d '{"text": "Backup finished", "level": "nudge", "source": "nas"}'
HTTP
POST /api/notify text, optional level (whisper, nudge, urgent), source, ttl_s (urgent ignores it and stays until tapped)
POST /api/sleep, POST /api/wake Night mode from now, or day from now, until the frame's hours next change over. Optional source
POST /api/companion action (as for frame_companion below), optional ttl_s for a mood or hiding, source
POST /api/project name, status (on_track, needs_you, blocked, done, archived), optional note, progress 0 to 1, and icon (the URL of a PNG or JPEG, or the image in base64). Send the icon once: later reports keep it, and "" removes it.
GET /api/look JPEG of what the frame is showing
GET /api/state Connected frames, now playing, today's painting, projects, config, recent activity
POST /api/now_playing What a player is doing: state (playing, paused, stopped), title, artist, album, source, position_s, duration_s, and art_b64 (the cover, JPEG or PNG) when the track changes. Repeat it at least every 30 seconds while playing.
MCP tool
frame_notify Show an alert
frame_sleep, frame_wake Put the frame into night mode, or end it
frame_companion Ask Tama for a gesture (wave, nod, celebrate), a mood (happy, worried, calm), to hide, a hobby, or care (feed, medicine)
frame_project Report where a project stands
frame_look Get a picture of the frame's screen, so an agent can see its own work
frame_state The same as GET /api/state

The MCP endpoint is http://HOST:7878/mcp (streamable HTTP). For Claude Code:

claude mcp add --transport http mado http://HOST:7878/mcp

Security

The hub has no authentication. Run it where only your own devices can reach it, for example bound to a Tailscale address, and never expose it to the internet. The frame does not need to be on the tailnet; it only needs to reach the hub.

Finding the hub

The hub announces itself on the local network over mDNS (as a _mado._tcp service, the way printers and speakers do), and a frame with no hub looks for that announcement. One hub found: the frame connects to it. Several: it asks which, on its own screen. A hub found this way is remembered by name as well as address, so the frame finds it again if the address changes. MADO_NAME sets the name people see (default "Mado on" and the machine's name).

Tap the hub's corner at the bottom of the rail to choose another hub or to type an address (192.168.1.20, hub.local:7878); adb shell am start -n jp.co.xenocode.mado/.MainActivity --es hub ws://HOST:7878/frame still works, and --es hub none makes the frame forget its hub and look again.

A hub with more than one address (a server on both a cable and Wi-Fi, say) can be given all of them, separated by commas, typed or with --es hub. The frame moves to the next address whenever its link drops, so it follows the server from one to the other.

The announcement does not leave a container on Docker's default network, so the Docker image keeps quiet (MADO_ADVERTISE=off) and a frame is given the address by hand. To have discovery with Docker on Linux, run the container with network_mode: host and MADO_ADVERTISE=on; compose.yaml shows how. Some networks, guest Wi-Fi especially, block mDNS altogether; then type the address.

If the frame sits on a network you would rather not open the whole hub to, set MADO_FRAME_BIND (for example 0.0.0.0:7879). The hub then opens a second port that serves only the frame's socket and the images it fetches, and you can keep the main port, with the web page, API and MCP, somewhere private. Point the frame at the second port (it is the one the hub announces). Anything that can reach that port can pose as a frame and read what a frame is sent (alerts, projects, weather places), but cannot send or change anything.

Hardware

Developed on a Uhale 102KZ: Rockchip RK3126, Mali-400, 1 GB RAM, Android 6.0.1, 1280×800. The app targets that class of device: no Compose, no large frameworks, and images are resized on the hub so the frame only has to draw them. Layouts currently assume a 1280×800 landscape screen.

Development

cd hub && cargo test                          # hub
cd frame-app && ./gradlew testDebugUnitTest   # forecast wording
cd mac && scripts/build.sh test               # menu bar app

cargo fmt and cargo clippy are expected to be clean. To sign the Mac app with your own Developer ID and notarize it, see mac/Signing.xcconfig and mac/scripts/notarize.sh. See CHANGELOG.md for what changed.

Credits

Paintings and their descriptions come from the Art Institute of Chicago's public API; only works it marks as public domain are used unless you choose otherwise on the hub's page. The other sources there are the Cleveland Museum of Art's Open Access API and WikiArt, whose most viewed paintings include many still in copyright: fine on a frame at home, not for showing in public. Forecasts and city search come from Open-Meteo. Album artwork that Music does not have locally is looked up in the iTunes catalogue.

Licence

MIT. The paintings shown by default are in the public domain; Mado's licence covers the software, not the artwork or the museum's descriptions.

About

frame

Resources

Stars

20 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages