Examples
Whole programs, each in every language. Set the three variables first (a new key on Your robots shows them), then run one in a clone of the repository: git clone https://github.com/i41-eu/q41eu-dev && cd q41eu-dev. Run does the same from this page, on your robot.
hello
a card, green LEDs, a nod, a chimeit on your robot from this page
sh examples/curl/hello.shDownload#!/bin/sh
# hello: a card on the robot's screen, its LEDs, a nod and a chime.
# export SDEV_URL=… SDEV_ROBOT=… SDEV_KEY=… (Dev's page shows them with a new key)
# sh hello.sh
set -eu
act() { # act <action> <JSON>: one action on the robot
curl -sS --fail-with-body -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/$1" \
-H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" -d "$2"
echo
}
act show '{"title":"Hello","text":"from curl","note":"q41eu-dev examples","color":"green"}'
act leds '{"color":"green"}'
act move '{"gesture":"nod"}'
act sound '{"chime":"tada"}'
python3 examples/python/hello.pyDownload#!/usr/bin/env python3
"""hello: a card on the robot's screen, its LEDs, a nod and a chime.
export SDEV_URL=… SDEV_ROBOT=… SDEV_KEY=… (Dev's page shows them with a new key)
python3 hello.py
Only the standard library.
"""
import json, os, urllib.request
URL, ROBOT, KEY = os.environ["SDEV_URL"].rstrip("/"), os.environ["SDEV_ROBOT"], os.environ["SDEV_KEY"]
def act(action, **args):
"""One action on the robot: POST /v1/robots/<robot>/<action> with the arguments as JSON."""
req = urllib.request.Request(f"{URL}/v1/robots/{ROBOT}/{action}", data=json.dumps(args).encode(),
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as res:
return json.load(res)
act("show", title="Hello", text="from Python", note="q41eu-dev examples", color="green")
act("leds", color="green")
act("move", gesture="nod")
act("sound", chime="tada")
print("done: look at the robot")
node examples/js/hello.mjsDownload#!/usr/bin/env node
// hello: a card on the robot's screen, its LEDs, a nod and a chime.
//
// export SDEV_URL=… SDEV_ROBOT=… SDEV_KEY=… (Dev's page shows them with a new key)
// node hello.mjs
// Node 18 or newer, nothing to install: only fetch.
const { SDEV_URL, SDEV_ROBOT, SDEV_KEY } = process.env;
if (!SDEV_URL || !SDEV_ROBOT || !SDEV_KEY) {
console.error("set SDEV_URL, SDEV_ROBOT and SDEV_KEY (Dev's page shows them with a new key)");
process.exit(2);
}
// act: one action on the robot: POST /v1/robots/<robot>/<action> with the arguments as JSON.
async function act(action, args = {}) {
const res = await fetch(`${SDEV_URL.replace(/\/$/, "")}/v1/robots/${SDEV_ROBOT}/${action}`, {
method: "POST",
headers: { Authorization: `Bearer ${SDEV_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(args),
});
const out = await res.json().catch(() => ({ ok: false, error: res.statusText }));
if (!res.ok || out.ok === false) throw new Error(`${action}: ${out.error || res.status}`);
return out;
}
await act("show", { title: "Hello", text: "from JavaScript", note: "q41eu-dev examples", color: "green" });
await act("leds", { color: "green" });
await act("move", { gesture: "nod" });
await act("sound", { chime: "tada" });
console.log("done: look at the robot");
go run ./examples/go/helloDownload// hello: a card on the robot's screen, its LEDs, a nod and a chime.
//
// export SDEV_URL=… SDEV_ROBOT=… SDEV_KEY=… (Dev's page shows them with a new key)
// go run ./examples/go/hello
package main
import (
"context"
"log"
"github.com/i41-eu/q41eu-dev/client"
)
func main() {
ctx := context.Background()
robot, err := client.FromEnv()
if err != nil {
log.Fatal(err)
}
must(robot.Show(ctx, client.Card{Title: "Hello", Text: "from Go", Note: "q41eu-dev examples", Color: "green"}))
must(robot.LEDs(ctx, "green"))
must(robot.Gesture(ctx, "nod"))
must(robot.Chime(ctx, "tada"))
log.Println("done: look at the robot")
}
func must(err error) {
if err != nil {
log.Fatal(err)
}
}
hello: a card on the robot's screen, its LEDs, a nod and a chime.
Ask your agent (Dev added as an MCP server: getting started, step 5):
Say hello on the robot: a green card "Hello", green LEDs, a nod and the tada chime.
The tool calls it makes:
robot_show {"title": "Hello", "text": "from an agent", "note": "q41eu-dev examples", "color": "green"}
robot_leds {"color": "green"}
robot_move {"gesture": "nod"}
robot_sound {"chime": "tada"}
ask
a question with buttons on the robot; the answer tapped there comes backit on your robot from this page
sh examples/curl/ask.shDownload#!/bin/sh
# ask: a question on the robot's screen; the answer the person taps comes back (up to 30 s).
# sh ask.sh
set -eu
curl -sS --fail-with-body -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/ask" \
-H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
-d '{"question":"Coffee or tea?","answers":["Coffee","Tea"]}'
echo
python3 examples/python/ask.py "Deploy?" Yes NoDownload#!/usr/bin/env python3
"""ask: a question on the robot's screen; the person at the robot taps an answer.
python3 ask.py "Coffee or tea?" Coffee Tea
"""
import json, os, sys, urllib.request
URL, ROBOT, KEY = os.environ["SDEV_URL"].rstrip("/"), os.environ["SDEV_ROBOT"], os.environ["SDEV_KEY"]
def act(action, **args):
req = urllib.request.Request(f"{URL}/v1/robots/{ROBOT}/{action}", data=json.dumps(args).encode(),
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as res:
return json.load(res)
question, answers = (sys.argv[1], sys.argv[2:]) if len(sys.argv) > 3 else ("Coffee or tea?", ["Coffee", "Tea"])
out = act("ask", question=question, answers=answers)
print(out["answer"] or "no answer within 30 s")
node examples/js/ask.mjs "Deploy?" Yes NoDownload#!/usr/bin/env node
// ask: a question on the robot's screen; the person at the robot taps an answer.
//
// node ask.mjs "Coffee or tea?" Coffee Tea
// Node 18 or newer, nothing to install: only fetch.
const { SDEV_URL, SDEV_ROBOT, SDEV_KEY } = process.env;
if (!SDEV_URL || !SDEV_ROBOT || !SDEV_KEY) {
console.error("set SDEV_URL, SDEV_ROBOT and SDEV_KEY (Dev's page shows them with a new key)");
process.exit(2);
}
async function act(action, args = {}) {
const res = await fetch(`${SDEV_URL.replace(/\/$/, "")}/v1/robots/${SDEV_ROBOT}/${action}`, {
method: "POST",
headers: { Authorization: `Bearer ${SDEV_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(args),
});
const out = await res.json().catch(() => ({ ok: false, error: res.statusText }));
if (!res.ok || out.ok === false) throw new Error(`${action}: ${out.error || res.status}`);
return out;
}
const [question, ...answers] = process.argv.length > 4 ? process.argv.slice(2) : ["Coffee or tea?", "Coffee", "Tea"];
const out = await act("ask", { question, answers });
console.log(out.answer || "no answer within 30 s");
go run ./examples/go/ask "Deploy?" Yes NoDownload// ask: a question on the robot's screen; the person at the robot taps an answer.
//
// go run ./examples/go/ask "Coffee or tea?" Coffee Tea
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/i41-eu/q41eu-dev/client"
)
func main() {
question, answers := "Coffee or tea?", []string{"Coffee", "Tea"}
if len(os.Args) > 3 {
question, answers = os.Args[1], os.Args[2:]
}
robot, err := client.FromEnv()
if err != nil {
log.Fatal(err)
}
answer, err := robot.Ask(context.Background(), question, answers...)
if err != nil {
log.Fatal(err)
}
if answer == "" {
fmt.Println("no answer within 30 s")
os.Exit(1)
}
fmt.Println(answer)
}
ask: a question on the robot's screen; the person at the robot taps an answer.
Ask your agent (Dev added as an MCP server: getting started, step 5):
Before you push, ask me on the robot: "Push now?" with Yes and Not yet.
The tool call it makes, and the answer it gets back when you tap Yes:
robot_ask {"question": "Push now?", "answers": ["Yes", "Not yet"]}
-> {"answer": "Yes", "index": 0}
build status
around any command (your tests): blue while it runs, then green and a chime, or red and a shakeit on your robot from this page
sh examples/curl/build_status.sh make testDownload#!/bin/sh
# build_status: runs a command and shows on the robot how it went: blue while it runs, then a
# green card and a chime when it passed, a red card and a shake when it failed. Its exit code is
# the command's.
# sh build_status.sh make test
set -u
act() { # act <action> <JSON>: one action on the robot
curl -sS --fail-with-body -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/$1" \
-H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" -d "$2"
echo
}
[ $# -gt 0 ] || set -- make test
name=$(printf '%s' "$*" | sed 's/\\/\\\\/g; s/"/\\"/g') # the command, as a JSON string
act show "{\"title\":\"Running\",\"text\":\"$name\",\"color\":\"blue\"}"
act leds '{"effect":"breathe","color":"blue"}'
start=$(date +%s)
if "$@"; then code=0; else code=$?; fi
took="$name · $(($(date +%s) - start)) s"
if [ "$code" -eq 0 ]; then
act show "{\"title\":\"Passed\",\"text\":\"All good\",\"note\":\"$took\",\"color\":\"green\",\"progress\":1}"
act leds '{"color":"green"}'
act sound '{"chime":"tada"}'
act move '{"gesture":"nod"}'
else
act show "{\"title\":\"Failed\",\"text\":\"Have a look\",\"note\":\"$took\",\"color\":\"red\"}"
act leds '{"color":"red"}'
act sound '{"chime":"error"}'
act move '{"gesture":"shake"}'
fi
exit "$code"
python3 examples/python/build_status.py make testDownload#!/usr/bin/env python3
"""build_status: runs a command and shows on the robot how it went: blue while it runs, then a
green card and a chime when it passed, a red card and a shake when it failed.
python3 build_status.py make test
"""
import json, os, subprocess, sys, time, urllib.request
URL, ROBOT, KEY = os.environ["SDEV_URL"].rstrip("/"), os.environ["SDEV_ROBOT"], os.environ["SDEV_KEY"]
def act(action, **args):
req = urllib.request.Request(f"{URL}/v1/robots/{ROBOT}/{action}", data=json.dumps(args).encode(),
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as res:
return json.load(res)
cmd = sys.argv[1:] or ["make", "test"]
name = " ".join(cmd)
act("show", title="Running", text=name, color="blue")
act("leds", effect="breathe", color="blue")
start = time.time()
code = subprocess.call(cmd)
took = f"{name} · {time.time() - start:.0f} s"
if code == 0:
act("show", title="Passed", text="All good", note=took, color="green", progress=1)
act("leds", color="green")
act("sound", chime="tada")
act("move", gesture="nod")
else:
act("show", title="Failed", text="Have a look", note=took, color="red")
act("leds", color="red")
act("sound", chime="error")
act("move", gesture="shake")
sys.exit(code)
node examples/js/build_status.mjs make testDownload#!/usr/bin/env node
// build_status: runs a command and shows on the robot how it went: blue while it runs, then a
// green card and a chime when it passed, a red card and a shake when it failed. Its exit code is
// the command's.
//
// node build_status.mjs make test
// Node 18 or newer, nothing to install: only fetch.
import { spawnSync } from "node:child_process";
const { SDEV_URL, SDEV_ROBOT, SDEV_KEY } = process.env;
if (!SDEV_URL || !SDEV_ROBOT || !SDEV_KEY) {
console.error("set SDEV_URL, SDEV_ROBOT and SDEV_KEY (Dev's page shows them with a new key)");
process.exit(2);
}
async function act(action, args = {}) {
const res = await fetch(`${SDEV_URL.replace(/\/$/, "")}/v1/robots/${SDEV_ROBOT}/${action}`, {
method: "POST",
headers: { Authorization: `Bearer ${SDEV_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(args),
});
const out = await res.json().catch(() => ({ ok: false, error: res.statusText }));
if (!res.ok || out.ok === false) throw new Error(`${action}: ${out.error || res.status}`);
return out;
}
const cmd = process.argv.length > 2 ? process.argv.slice(2) : ["make", "test"];
const name = cmd.join(" ");
await act("show", { title: "Running", text: name, color: "blue" });
await act("leds", { effect: "breathe", color: "blue" });
const start = Date.now();
const run = spawnSync(cmd[0], cmd.slice(1), { stdio: "inherit" });
const code = run.status ?? 1;
const took = `${name} · ${Math.round((Date.now() - start) / 1000)} s`;
if (code === 0) {
await act("show", { title: "Passed", text: "All good", note: took, color: "green", progress: 1 });
await act("leds", { color: "green" });
await act("sound", { chime: "tada" });
await act("move", { gesture: "nod" });
} else {
await act("show", { title: "Failed", text: "Have a look", note: took, color: "red" });
await act("leds", { color: "red" });
await act("sound", { chime: "error" });
await act("move", { gesture: "shake" });
}
process.exit(code);
go run ./examples/go/buildstatus go test ./...Download// buildstatus runs a command and shows on the robot how it went: blue while it runs, then a
// green card and a chime when it passed, a red card and a shake when it failed. Its exit code is
// the command's.
//
// go run ./examples/go/buildstatus go test ./...
package main
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"strings"
"time"
"github.com/i41-eu/q41eu-dev/client"
)
func main() {
args := os.Args[1:]
if len(args) == 0 {
args = []string{"go", "test", "./..."}
}
ctx := context.Background()
robot, err := client.FromEnv()
if err != nil {
log.Fatal(err)
}
name := strings.Join(args, " ")
robot.Show(ctx, client.Card{Title: "Running", Text: name, Color: "blue"})
robot.Do(ctx, "leds", map[string]any{"effect": "breathe", "color": "blue"}, nil)
start := time.Now()
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
runErr := cmd.Run()
took := fmt.Sprintf("%s · %.0f s", name, time.Since(start).Seconds())
if runErr == nil {
robot.Show(ctx, client.Card{Title: "Passed", Text: "All good", Note: took, Color: "green", Progress: client.Progress(1)})
robot.LEDs(ctx, "green")
robot.Chime(ctx, "tada")
robot.Gesture(ctx, "nod")
return
}
robot.Show(ctx, client.Card{Title: "Failed", Text: "Have a look", Note: took, Color: "red"})
robot.LEDs(ctx, "red")
robot.Chime(ctx, "error")
robot.Gesture(ctx, "shake")
if exit, ok := runErr.(*exec.ExitError); ok {
os.Exit(exit.ExitCode())
}
log.Fatal(runErr)
}
build status: runs a command and shows on the robot how it went: blue while it runs, then a
green card and a chime when it passed, a red card and a shake when it failed.
Ask your agent (Dev added as an MCP server: getting started, step 5):
Run make test, and show on the robot how it goes: blue while it runs, then green with
the tada chime and a nod, or red with the error chime and a shake.
The tool calls it makes around the command (here: the tests passed):
robot_show {"title": "Running", "text": "make test", "color": "blue"}
robot_leds {"effect": "breathe", "color": "blue"}
robot_show {"title": "Passed", "text": "All good", "note": "make test · 42 s", "color": "green", "progress": 1}
robot_leds {"color": "green"}
robot_sound {"chime": "tada"}
robot_move {"gesture": "nod"}
menu
a snack menu of four tiles on the robot; prints the one tapped, and the robot says itit on your robot from this page
sh examples/curl/menu.shDownload#!/bin/sh
# menu: a snack menu of four tiles on the robot's screen; prints the one tapped (up to 30 s) and
# the robot says it.
# sh menu.sh
set -eu
act() { # act <action> <JSON>: one action on the robot; prints its answer
curl -sS --fail-with-body -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/$1" \
-H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" -d "$2"
}
out=$(act menu '{"title":"Snack?","items":["Apple","Cake","Carrot","Milk"]}')
echo "$out"
item=$(echo "$out" | sed -n 's/.*"item":"\([^"]*\)".*/\1/p')
if [ -z "$item" ]; then echo "no choice within 30 s"; exit 1; fi
act show '{"clear":true}' >/dev/null # the speech bubble is on the face
act say "{\"text\":\"$item, yum!\"}" >/dev/null
python3 examples/python/menu.pyDownload#!/usr/bin/env python3
"""menu: a snack menu of four tiles on the robot's screen; prints the one tapped and the robot
says it.
python3 menu.py
"""
import json, os, sys, urllib.request
URL, ROBOT, KEY = os.environ["SDEV_URL"].rstrip("/"), os.environ["SDEV_ROBOT"], os.environ["SDEV_KEY"]
def act(action, **args):
req = urllib.request.Request(f"{URL}/v1/robots/{ROBOT}/{action}", data=json.dumps(args).encode(),
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as res:
return json.load(res)
out = act("menu", title="Snack?", items=["Apple", "Cake", "Carrot", "Milk"])
if not out["item"]:
sys.exit("no choice within 30 s")
print(out["item"])
act("show", clear=True) # the speech bubble is on the face
act("say", text=f"{out['item']}, yum!")
node examples/js/menu.mjsDownload#!/usr/bin/env node
// menu: a snack menu of four tiles on the robot's screen; prints the one tapped and the robot
// says it.
//
// node menu.mjs
// Node 18 or newer, nothing to install: only fetch.
const { SDEV_URL, SDEV_ROBOT, SDEV_KEY } = process.env;
if (!SDEV_URL || !SDEV_ROBOT || !SDEV_KEY) {
console.error("set SDEV_URL, SDEV_ROBOT and SDEV_KEY (Dev's page shows them with a new key)");
process.exit(2);
}
async function act(action, args = {}) {
const res = await fetch(`${SDEV_URL.replace(/\/$/, "")}/v1/robots/${SDEV_ROBOT}/${action}`, {
method: "POST",
headers: { Authorization: `Bearer ${SDEV_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(args),
});
const out = await res.json().catch(() => ({ ok: false, error: res.statusText }));
if (!res.ok || out.ok === false) throw new Error(`${action}: ${out.error || res.status}`);
return out;
}
const out = await act("menu", { title: "Snack?", items: ["Apple", "Cake", "Carrot", "Milk"] });
if (!out.item) {
console.log("no choice within 30 s");
process.exit(1);
}
console.log(out.item);
await act("show", { clear: true }); // the speech bubble is on the face
await act("say", { text: `${out.item}, yum!` });
go run ./examples/go/menuDownload// menu: a snack menu of four tiles on the robot's screen; prints the one tapped and the robot
// says it.
//
// go run ./examples/go/menu
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/i41-eu/q41eu-dev/client"
)
func main() {
robot, err := client.FromEnv()
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
item, err := robot.Menu(ctx, "Snack?", client.MenuItem{Label: "Apple"}, client.MenuItem{Label: "Cake"},
client.MenuItem{Label: "Carrot"}, client.MenuItem{Label: "Milk"})
if err != nil {
log.Fatal(err)
}
if item == "" {
fmt.Println("no choice within 30 s")
os.Exit(1)
}
fmt.Println(item)
robot.Clear(ctx) // the speech bubble is on the face
robot.Say(ctx, item+", yum!")
}
menu: a snack menu of four tiles on the robot's screen; the tile tapped comes back.
Ask your agent (Dev added as an MCP server: getting started, step 5):
Ask me on the robot which snack I want: apple, cake, carrot or milk. Then say it in a bubble.
The tool calls it makes, and the answer when you tap Carrot:
robot_menu {"title": "Snack?", "items": ["Apple", "Cake", "Carrot", "Milk"]}
-> {"item": "Carrot", "index": 2, "label": "Carrot"}
robot_show {"clear": true}
robot_say {"text": "Carrot, yum!"}
picture
a picture of yours on the robot (or four numbered quarters); prints the quarter tappedit on your robot from this page
sh examples/curl/picture.sh photo.jpgDownload#!/bin/sh
# picture: a picture of yours on the robot's screen (or four numbered quarters), then the quarter
# the person taps (up to 30 s). Dev fits the picture to the screen; up to about 1.5 MB.
# sh picture.sh [photo.jpg]
set -eu
pic=${1:-$(dirname "$0")/../img/quarters.png}
quarters='[{"id":"1","rect":[0,0,160,120]},{"id":"2","rect":[160,0,160,120]},{"id":"3","rect":[0,120,160,120]},{"id":"4","rect":[160,120,160,120]}]'
printf '{"image":"%s","regions":%s}' "$(base64 < "$pic" | tr -d '\n')" "$quarters" |
curl -sS --fail-with-body -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/clickmap" \
-H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" --data-binary @-
echo
python3 examples/python/picture.py photo.jpgDownload#!/usr/bin/env python3
"""picture: a picture of yours on the robot's screen (or four numbered quarters), then the quarter
the person taps. Dev fits the picture to the screen; up to about 1.5 MB.
python3 picture.py [photo.jpg]
"""
import base64, json, os, sys, urllib.request
URL, ROBOT, KEY = os.environ["SDEV_URL"].rstrip("/"), os.environ["SDEV_ROBOT"], os.environ["SDEV_KEY"]
def act(action, **args):
req = urllib.request.Request(f"{URL}/v1/robots/{ROBOT}/{action}", data=json.dumps(args).encode(),
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as res:
return json.load(res)
path = sys.argv[1] if len(sys.argv) > 1 else os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "img", "quarters.png")
with open(path, "rb") as f:
image = base64.b64encode(f.read()).decode()
quarters = [{"id": str(i + 1), "rect": [160 * (i % 2), 120 * (i // 2), 160, 120]} for i in range(4)]
out = act("clickmap", image=image, regions=quarters)
print(f"quarter {out['region']} (at {out['x']}, {out['y']})" if out["region"] else "no tap within 30 s")
node examples/js/picture.mjs photo.jpgDownload#!/usr/bin/env node
// picture: a picture of yours on the robot's screen (or four numbered quarters), then the quarter
// the person taps. Dev fits the picture to the screen; up to about 1.5 MB.
//
// node picture.mjs [photo.jpg]
// Node 18 or newer, nothing to install: only fetch.
import { readFile } from "node:fs/promises";
const { SDEV_URL, SDEV_ROBOT, SDEV_KEY } = process.env;
if (!SDEV_URL || !SDEV_ROBOT || !SDEV_KEY) {
console.error("set SDEV_URL, SDEV_ROBOT and SDEV_KEY (Dev's page shows them with a new key)");
process.exit(2);
}
async function act(action, args = {}) {
const res = await fetch(`${SDEV_URL.replace(/\/$/, "")}/v1/robots/${SDEV_ROBOT}/${action}`, {
method: "POST",
headers: { Authorization: `Bearer ${SDEV_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(args),
});
const out = await res.json().catch(() => ({ ok: false, error: res.statusText }));
if (!res.ok || out.ok === false) throw new Error(`${action}: ${out.error || res.status}`);
return out;
}
const image = (await readFile(process.argv[2] || new URL("../img/quarters.png", import.meta.url))).toString("base64");
const regions = [0, 1, 2, 3].map((i) => ({ id: String(i + 1), rect: [160 * (i % 2), 120 * Math.floor(i / 2), 160, 120] }));
const out = await act("clickmap", { image, regions });
console.log(out.region ? `quarter ${out.region} (at ${out.x}, ${out.y})` : "no tap within 30 s");
go run ./examples/go/picture photo.jpgDownload// picture: a picture of yours on the robot's screen (or four numbered quarters), then the quarter
// the person taps. Dev fits the picture to the screen; up to about 1.5 MB.
//
// go run ./examples/go/picture [photo.jpg]
package main
import (
"context"
"fmt"
"io/fs"
"log"
"os"
"github.com/i41-eu/q41eu-dev/client"
"github.com/i41-eu/q41eu-dev/examples"
)
func main() {
pic, err := fs.ReadFile(examples.FS, "img/quarters.png")
if len(os.Args) > 1 {
pic, err = os.ReadFile(os.Args[1])
}
if err != nil {
log.Fatal(err)
}
robot, err := client.FromEnv()
if err != nil {
log.Fatal(err)
}
var quarters []client.Region
for i := range 4 {
quarters = append(quarters, client.Region{ID: fmt.Sprint(i + 1), Rect: []int{160 * (i % 2), 120 * (i / 2), 160, 120}})
}
tap, err := robot.Clickmap(context.Background(), pic, quarters...)
if err != nil {
log.Fatal(err)
}
if tap.Region == "" {
fmt.Println("no tap within 30 s")
os.Exit(1)
}
fmt.Printf("quarter %s (at %d, %d)\n", tap.Region, tap.X, tap.Y)
}
picture: a picture on the robot's screen, then the quarter the person taps comes back.
Ask your agent (Dev added as an MCP server: getting started, step 5); it reads the picture itself
and sends it as base64:
Show examples/img/quarters.png on the robot, ask me to tap one of its quarters, and tell me which.
The tool call it makes (the picture's base64 cut short here), and the answer when you tap the
bottom right:
robot_clickmap {"image": "iVBORw0KGgo…", "regions": [{"id": "1", "rect": [0, 0, 160, 120]}, {"id": "2", "rect": [160, 0, 160, 120]}, {"id": "3", "rect": [0, 120, 160, 120]}, {"id": "4", "rect": [160, 120, 160, 120]}]}
-> {"region": "4", "index": 3, "x": 241, "y": 187}
events
what people do at the robot; a pat makes it chirp and smile, a tag shows its textit on your robot from this page
sh examples/curl/events.shDownload#!/bin/sh
# events: waits up to 30 s for the next thing a person does at the robot (tap, pat, a tag, …).
# The answer's last_seq goes into after= next time:
# sh events.sh (from the last ones kept)
# AFTER=12 sh events.sh
set -eu
curl -sS --fail-with-body "$SDEV_URL/v1/robots/$SDEV_ROBOT/events?after=${AFTER:-0}&seconds=30" \
-H "Authorization: Bearer $SDEV_KEY"
echo
python3 examples/python/events.pyDownload#!/usr/bin/env python3
"""events: prints what people do at the robot and answers some of it: a pat on the head makes it
chirp and smile, a tag held near shows what the tag says. Ctrl+C ends it.
python3 events.py
"""
import json, os, time, urllib.request
URL, ROBOT, KEY = os.environ["SDEV_URL"].rstrip("/"), os.environ["SDEV_ROBOT"], os.environ["SDEV_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
def act(action, **args):
req = urllib.request.Request(f"{URL}/v1/robots/{ROBOT}/{action}", data=json.dumps(args).encode(), headers=HEADERS)
with urllib.request.urlopen(req, timeout=180) as res:
return json.load(res)
def events(after, seconds):
"""The events after a sequence number; waits up to seconds for the next one."""
req = urllib.request.Request(f"{URL}/v1/robots/{ROBOT}/events?after={after}&seconds={seconds}", headers=HEADERS)
with urllib.request.urlopen(req, timeout=seconds + 30) as res:
return json.load(res)
last = events(0, 0)["last_seq"] # start from now
print("touch the robot: tap its screen, pat its head, hold a tag near it…")
while True:
try:
got = events(last, 30)
except OSError as err:
print(err)
time.sleep(3)
continue
last = got["last_seq"]
for e in got["events"]:
print(e["name"], e.get("data", {}))
if e["name"] in ("pat", "pet", "stroke"):
act("face", emotion="happy")
act("sound", chime="chirp")
elif e["name"] == "tag":
act("show", title="A tag", text=e["data"].get("text") or "(no text)", note=e["data"].get("uid", ""))
elif e["name"] == "tap":
act("show", clear=True)
node examples/js/events.mjsDownload#!/usr/bin/env node
// events: prints what people do at the robot and answers some of it: a pat on the head makes it
// chirp and smile, a tag held near shows what the tag says. Ctrl+C ends it.
//
// node events.mjs
// Node 18 or newer, nothing to install: only fetch.
const { SDEV_URL, SDEV_ROBOT, SDEV_KEY } = process.env;
if (!SDEV_URL || !SDEV_ROBOT || !SDEV_KEY) {
console.error("set SDEV_URL, SDEV_ROBOT and SDEV_KEY (Dev's page shows them with a new key)");
process.exit(2);
}
const base = `${SDEV_URL.replace(/\/$/, "")}/v1/robots/${SDEV_ROBOT}`;
const headers = { Authorization: `Bearer ${SDEV_KEY}`, "Content-Type": "application/json" };
async function act(action, args = {}) {
const res = await fetch(`${base}/${action}`, { method: "POST", headers, body: JSON.stringify(args) });
const out = await res.json().catch(() => ({ ok: false, error: res.statusText }));
if (!res.ok || out.ok === false) throw new Error(`${action}: ${out.error || res.status}`);
return out;
}
// events: the events after a sequence number; waits up to seconds for the next one.
async function events(after, seconds) {
const res = await fetch(`${base}/events?after=${after}&seconds=${seconds}`, { headers });
const out = await res.json();
if (!res.ok) throw new Error(`events: ${out.error || res.status}`);
return out;
}
let last = (await events(0, 0)).last_seq; // start from now
console.log("touch the robot: tap its screen, pat its head, hold a tag near it…");
for (;;) {
let got;
try {
got = await events(last, 30);
} catch (err) {
console.log(err.message);
await new Promise((ok) => setTimeout(ok, 3000));
continue;
}
last = got.last_seq;
for (const e of got.events) {
const data = e.data || {};
console.log(e.name, data);
if (["pat", "pet", "stroke"].includes(e.name)) {
await act("face", { emotion: "happy" });
await act("sound", { chime: "chirp" });
} else if (e.name === "tag") {
await act("show", { title: "A tag", text: data.text || "(no text)", note: data.uid || "" });
} else if (e.name === "tap") {
await act("show", { clear: true });
}
}
}
go run ./examples/go/eventsDownload// events prints what people do at the robot and answers some of it: a pat on the head makes it
// chirp and smile, a tag held near shows what the tag says. Ctrl+C ends it.
//
// go run ./examples/go/events
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/i41-eu/q41eu-dev/client"
)
func main() {
ctx := context.Background()
robot, err := client.FromEnv()
if err != nil {
log.Fatal(err)
}
_, last, err := robot.Events(ctx, 0, 0) // start from now: skip the ones from before
if err != nil {
log.Fatal(err)
}
fmt.Println("touch the robot: tap its screen, pat its head, hold a tag near it…")
for {
events, seq, err := robot.Events(ctx, last, 30*time.Second)
if err != nil {
log.Println(err)
time.Sleep(3 * time.Second)
continue
}
last = seq
for _, e := range events {
fmt.Println(e.Name, e.Data)
switch e.Name {
case "pat", "pet", "stroke":
robot.Face(ctx, "happy")
robot.Chime(ctx, "chirp")
case "tag":
robot.Show(ctx, client.Card{Title: "A tag", Text: fmt.Sprint(e.Data["text"]), Note: fmt.Sprint(e.Data["uid"])})
case "tap":
robot.Clear(ctx)
}
}
}
}
events: what people do at the robot, and answers to some of it: a pat on the head makes it
chirp and smile, a tag held near shows what the tag says.
Ask your agent (Dev added as an MCP server: getting started, step 5):
For the next five minutes, watch the robot: when someone pats its head, smile and
chirp; when a tag is held near it, show what the tag says. Then tell me what happened.
The tool calls it makes, in a loop (each answer's last_seq goes into the next after):
robot_events {"after": 0, "seconds": 0}
-> {"events": [...], "last_seq": 41}
robot_events {"after": 41, "seconds": 30}
-> {"events": [{"seq": 42, "name": "pat", "data": {"ms": 300}}], "last_seq": 42}
robot_face {"emotion": "happy"}
robot_sound {"chime": "chirp"}