Show the code in these languages

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 chime

Screen and faceHeadLEDsSpeaker

it on your robot from this page
curl 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"}'
Python 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")
JavaScript 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 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)
	}
}
MCP
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 back

Questions and menusScreen and face

it on your robot from this page
curl 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
Python 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")
JavaScript 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 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)
}
MCP
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 shake

Screen and faceLEDsSpeakerHead

it on your robot from this page
curl 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"
Python 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)
JavaScript 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 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)
}
MCP
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 it

Questions and menusScreen and face

it on your robot from this page
curl 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
Python 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!")
JavaScript 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 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!")
}
MCP
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 tapped

Questions and menusScreen and face

it on your robot from this page
curl 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
Python 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")
JavaScript 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 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)
}
MCP
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 text

What people doScreen and faceSpeaker

it on your robot from this page
curl 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
Python 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)
JavaScript 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 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)
			}
		}
	}
}
MCP
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"}