The hairobo API
Injected automatically — you do not include a script. window.hairobo exists
before your code runs.
Everything returns a Promise. Calls resolve false rather than throwing when
the host declines them — no permission, no dock, rate limited. Write for that.
const spoke = await hairobo.voice.speak('Hello');
if (!spoke) { /* muted, busy, or not permitted — carry on */ }
voice — needs voice
voice.speak(text) → Promise<bool>
Speaks through the robot in its own voice. Capped at 300 characters, and at
most one line every 0.4s; a call inside the gap resolves false and is
dropped, not queued. Resolves false while the robot is already talking.
eyes — needs eyes
eyes.look(x, y) → Promise<bool>
Points the gaze. Both are roughly −1…1, x right-positive, y up-positive.
eyes.express(name) → Promise<bool>
One of happy, curious, thinking, sad, excited, calm. Unknown names
are ignored rather than erroring.
led — needs led
led.set(color) → Promise<bool>
Hex string, e.g. '#37D2F0'. The dock's ring.
led.reset() → Promise<bool>
Back to whatever the app would show. Call this when you finish — though teardown does it for you if you forget.
motor — needs motor · needs a dock
Degrees are relative to where the head is now. The host clamps them, so ask for what you want and let it decide.
motor.pan(degrees) → Promise<bool>
Turns left/right. Clamped to ±20° per call.
motor.tilt(degrees) → Promise<bool>
Rolls the head. Clamped to ±10°, and refused entirely if it would take the phone more than 25° from level — past that the dock's base can strike the phone. Also refused if the app cannot currently read its own angle.
motor.center() → Promise<bool>
Return to level. Takes a second or two of visible movement.
At most one motor move every 0.35s. Anything faster resolves false and is
dropped, so an animation loop can call freely without building a backlog.
Movement reads best with a deadzone — turning on every small change looks
nervous rather than attentive. See follow-me/.
camera — needs camera
You get positions, never pixels. There is no API that returns a frame, and the image never leaves the phone.
camera.start() → Promise<bool> · camera.stop() → Promise<bool>
Start it, then listen. Stopped automatically when your plugin closes.
Events arrive at roughly 6/second:
hairobo.on('face', (f) => {
if (!f.detected) return;
moveEyes(f.x, f.y); // both roughly -1..1
});
hairobo.on('hand', (h) => { /* h.x only */ });
data — needs data
data.weather() → Promise<object>
Current weather from the app. Your only source of outside information — you have no network of your own.
Always available
info.get() → Promise<object>
App build, whether a dock is connected, which permissions you were granted,
whether the screen is dimmed. Check info.get() before offering robot
features, rather than discovering there is no dock by being refused.
log(message)
Goes to the app's debug log. Invisible in release builds — your own debugging, not a user-facing channel.
wait(ms) → Promise
on(event, fn)
version — the API version this host provides. Currently 2.
Feature-detect anything new
On an older app, a newer namespace is undefined, and the property access
throws before your .catch can run — leaving your plugin silently stuck:
if (!hairobo.camera) {
status.textContent = 'Needs a newer Hai Robo';
return;
}
Either that, or set minAppBuild and refuse to install at all.