Skip to the build guide
ElsewhereMachines

01 / 09 · THE PARTS

Send a rescue signal.

One press lights a beacon, starts a scan, and changes what the story knows.

Build it with Otherwhere OS. We’ll go from loose parts to a complete scenario, with the configuration beside the thing it controls.

  • 01 Raspberry Pi with a 40-pin header, power supply, and storage
  • 02 Two momentary buttons: action and push-to-talk
  • 03 An LED, a 1 kΩ resistor, breadboard, and jumper wires
  • 04 A supported microphone and powered speaker

This guide uses the reference board, orion.console@3. The wiring is shared by both adventures. Changing the physical layout means defining your own board.

Just here for the software? Start with the local exercise Wire it

02 / 09 · WIRE IT

One connection at a time.

Disconnect the Pi’s power before wiring. Choose a part to see its connection and the matching board configuration.

components:
  primary_button:
    type: orion.momentary_button@1
    config:
      pin: 16
      active_low: true
      debounce_ms: 40
board.yaml · components excerpt

GPIO signals are 3.3 V. Keep 5 V away from these connections, and keep the resistor in series with the LED. Check your Pi’s header orientation ↗

Speaker, microphone, and power

Use a supported powered speaker or amplifier and microphone. The board expects ALSA devices named speaker and microphone; these aliases must match your Pi’s audio setup. The GPIO header does not drive the speaker directly.

Use the supported Pi image and power supply for your build. Device and audio setup →

Name it

03 / 09 · NAME IT

Give the parts useful names.

The board owns the pins. The scenario asks for a button, a light, a speaker, and a way to talk. That is what lets the same wiring become a different machine.

capabilities:
  primary_action: primary_button
  push_to_talk: talk_button
  status_indicator: alert_light
  main_audio: speaker
  microphone: microphone
board.yaml / scenario.yaml · root excerpts
BCM 16primary_buttonprimary_action

A button has no permanent sound or story role. Adding another supported button usually means adding a component and binding. New hardware primitives may need a driver.

The story

04 / 09 · THE STORY

Give the signal a reason.

Write who needs help and what the player can choose. Turning on a beacon is a fact. Someone receiving it is another part of the story.

characters:
  orion:
    name: Commander Orion
    personality: Calm, curious, and encouraging under pressure.
    prompt: You coordinate a friendly space rescue. Let the child choose who to help
      and how to explore.
    voice: orion_voice
    initial_presence: rescue_bridge
scenario.yaml · root excerpts

Start from the complete download, then change the character, rules, and starting facts together. Give your version its own scenario identity. Published versions are immutable.

A press

05 / 09 · A PRESS

One press. Three things.

The button turns the lamp on, starts a named scan, and sends beacon_requested. A seven-second guard keeps repeat presses from starting another sequence.

- id: activate_beacon
  on: primary_action.pressed
  when:
    - { path: state.ready, op: eq, value: true }
  actions:
    - state.set: { path: ready, value: false }
    - light.set: { target: status_indicator, value: true }
    - sound.play: { asset: rescue_scan, key: scan }
    - timer.start: { timer: cooldown, delay_ms: 7000 }
    - server.emit: { event: beacon_requested }
scenario.yaml · device.maps.rescue.routes / root assets / device declarations

The sounds are included in the download: verified prototype cues from the existing inventory. An asset alias chooses audio; it does not generate it.

Use your own sound

Choose or create a WAV, measure its SHA-256, byte count, and PCM duration, and add it to assets/catalog.json. Its file path is relative to that catalog. Point the scenario’s asset alias to that hash, then rerun the local exercise. The helper checks the actual bytes.

A response

06 / 09 · A RESPONSE

Remember that you called.

The server handles beacon_requested with two explicit effects: remember that the beacon is active, and publish its lamp state. The scan’s completion plays the confirmation chirp.

- id: scan_finished
  on: sound.completed
  when:
    - { path: event.key, op: eq, value: scan }
  actions:
    - sound.play: { asset: beacon_chirp, key: confirmation }
scenario.yaml · device.maps.rescue.routes / root inputs

The light reacts locally right away. The server’s accepted effect makes the fact durable. Beacon active does not mean explorers rescued.

What if the story is already speaking?

A non-record input can be accepted as busy_recorded during an active turn. It is recorded without queuing a surprise story action for later. Device observations remain diagnostics; only declared story inputs take this path.

Recovery

07 / 09 · RECOVERY

Pick up where you left off.

On reconnect, restore the saved beacon value with an absolute light command. Repeating it is harmless. Reopen temporary controls without replaying the scan or confirmation.

- id: beacon_restored
  on: server.beacon_sync
  actions:
    - light.set: { target: status_indicator, value_from: event.enabled }
scenario.yaml · playable-map routes / device.state_sync
Keep push-to-talk available
routes:
  - id: capture_begin
    on: push_to_talk.pressed
    actions:
      - timer.cancel:
          timer: cooldown
      - state.set:
          path: ready
          value: true
      - capture.begin: {}
  - id: capture_stop
    on: push_to_talk.released
    actions:
      - capture.stop: {}
scenario.yaml · device.maps.rescue.routes

Every playable map keeps press and release capture bindings unconditional. Talking clears the temporary cooldown while preserving the saved beacon state.

Local cues yield to narration and capture. Keep the completion, timeout, reconnect, and talk routes together when you adapt the example.

Try it locally

08 / 09 · TRY IT LOCALLY

Run the actual sequence.

Download the whole build: board, complete scenario, audio, catalog, and interaction trace. Extract the builds folder into your supplied pi-exp checkout.

Download the complete build 942 KB

Use Bun 1.4.0, Python 3.12, and uv 0.12.0. The prototype runtime comes with the supplied checkout; this download contains the build files. Run these commands from the checkout root.

bun --no-env-file tools/install-typescript-dependencies.ts
uv sync --project apps/device-agent --frozen --all-groups
terminal · trace tab shows the opening inputs

The exercise builds the package, verifies the audio, and runs the real reactor with fake hardware. Read each step’s commands, local state, and server intents. Completing a fake sound is explicit; advancing time only drives timers.

Your Pi

09 / 09 · YOUR PI

Put it on your build.

With your Pi enrolled against the matching board, upload the audio, publish the scenario, and build its package. Otherwhere OS downloads and verifies the package before activating it.

This prototype uses the supplied installation and enrollment workflow, plus a configured server and authorized admin CLI. Set up and enroll your Pi → Keep credentials in your local runtime file.

bun --env-file=.env apps/admin-cli/src/main.ts asset upload builds/rescue/assets/audio/rescue_scan.wav --media-type audio/wav --kind PACKAGE_ASSET --json
bun --env-file=.env apps/admin-cli/src/main.ts asset upload builds/rescue/assets/audio/beacon_chirp.wav --media-type audio/wav --kind PACKAGE_ASSET --json
terminal · connected application commands

Use the actual package UUID returned by the build for PACKAGE_ID, and your enrolled Pi’s UUID for DEVICE_ID. Confirm orion.console@3 is published and matches that device. If you changed the layout or story, publish the new board/scenario identity and use those references instead.

How do I know it is running?

Inspect the package’s exact board, scenario, assets, and hash. After selecting it, inspect the device again and wait for desired and active package IDs to agree. An existing story session can retain its current scenario; preserve it or end it deliberately before switching.

Then try the real button, lamp, speaker, and push-to-talk. The local exercise does not prove physical wiring, microphone capture, server effects, or a heard story response. Package and device reference →

Same parts. Another story.Make a sonar panel