uMe Gateway — discovery and pairing (API 1.1)¶
Status as of 31.10.2026. In development — network stage 1 is adopted in the app repository;
the follow-up changes (installation guide version 2.3) are still in development, not yet
adopted. Not yet installed on any device. Source: gateway/gateway_v2.py,
gateway/install.sh and gateway/INSTALL.md in the repository robotics-group-app. The German
version follows below: Deutsch. Set-up for technicians:
uMe: connection without IP search, and pairing.
All other Gateway commands: Gateway API (WebSocket).
For IT, technicians and integration partners
Technical reference for IT departments, URG technicians and trained partners. It is not written as operating instructions for end users.
Version note — API 1.0 → 1.1¶
| API 1.0 | API 1.1 | |
|---|---|---|
hello |
type, api_version, server, profile |
additionally device_id, pairing_required, and challenge when pairing is required |
| Pairing | none | challenge-response with HMAC-SHA256, message pair, answer paired |
| Before pairing | all commands | only ping and pair; no status, no camera image |
| Discovery | enter an address | mDNS service _ume-gw._tcp; additionally host name ume-<kennung>.local where the robot can publish it |
This is a MINOR increase: a client checks only the MAJOR version. A hello without the new fields
(Gateway 1.0) is read as device_id = null, pairing_required = false. An API 1.0 client cannot
control a Gateway that requires pairing; every command is answered with the error
pairing_required and a displayable text in message.
Endpoint¶
| Address | ws://<host>:8765/ws; <host> is the target of the SRV record of _ume-gw._tcp, ume-<kennung>.local (if published), or the address found by the search in the own network |
| Transport | WebSocket, ws://, unencrypted, local network only |
| Encoding | UTF-8 JSON, one object per message |
ID <kennung> |
1–20 characters a-z, 0-9, -, not starting or ending with -; regular expression ^[a-z0-9]([a-z0-9-]{0,18}[a-z0-9])?$, checked by install.sh when the ID is entered (the Gateway takes UME_DEVICE_ID as it is); never a serial number |
mDNS service record¶
install.sh creates the record only if avahi-daemon is present. The robot keeps its previous
mDNS host name; install.sh does not rename it.
| Field | Value |
|---|---|
| Service type | _ume-gw._tcp |
| Port | 8765 |
| Host name (SRV target) | the robot's previous mDNS host name, unchanged |
| Extra host name | ume-<kennung>.local, published additionally only if avahi-publish is present on the robot |
| Instance name | display name, default uMe <kennung> (1–63 characters, one line) |
TXT api |
API version of the Gateway, e.g. 1.1 |
TXT id |
ID (device_id) |
TXT name |
display name |
Example (ID messe1; <host> stands for the robot's previous host name):
uMe messe1._ume-gw._tcp.local. SRV 0 0 8765 <host>.local.
uMe messe1._ume-gw._tcp.local. TXT "api=1.1" "id=messe1" "name=uMe messe1"
The mDNS record is only a hint. A client treats a hit as a uMe only after a valid hello with a
matching API MAJOR arrives on ws://<host>:8765/ws. The app searches in parallel: last connected
robot; mDNS (PTR query for _ume-gw._tcp and, if the ID of the last robot is known, A query for
ume-<kennung>.local); probe of the own private IPv4 subnet (only /24 or smaller) on port 8765.
Without the extra host name the app still finds the robot through the service record and the
subnet search.
Same device_id at two addresses. The app does not connect to either of them, neither
automatically nor by a tap. On the customer page both entries show "ID found more than once in the
network – please call service" (German app text: "Kennung mehrfach im Netz – bitte den Service
rufen"); the technician area names the ID.
Pairing sequence¶
Client Gateway
| --- open WebSocket --------------------> | 5 s deadline starts
| <-- hello {device_id, pairing_required, |
| challenge} ------------------ |
| --- pair {response} -------------------> |
| <-- paired ----------------------------- | deadline cancelled
| <-- status ----------------------------- | from now on status, camera, commands
- The Gateway sends
hellofirst. When pairing is required it carries a newchallengeper connection (64 hex characters, 256 random bits). - The client answers with
pair.responseis HMAC-SHA256 as lower-case hex text:- key: the bytes of the pairing key (the key is stored as hex text and is hex-decoded
first; at least 128 bit,
install.shcreates 256 bit); - message (UTF-8):
ume-pair-v1|<device_id>|<challenge>(an empty text between the separators ifdevice_idis missing).
- key: the bytes of the pairing key (the key is stored as hex text and is hex-decoded
first; at least 128 bit,
- Correct answer →
paired, immediately followed bystatus. From now on every command of the Gateway API applies. - Wrong answer → the Gateway closes the connection with code
4403. - No correct answer within 5 s after the connection opened → the Gateway closes the connection
with code
4401.
pair on a connection that is already paired, or with pairing switched off, is answered with
paired. The key never appears in hello, in error texts or in the log.
Messages¶
hello with pairing required (placeholders in angle brackets):
{
"type": "hello",
"api_version": "1.1",
"server": "uMe Gateway",
"profile": { "...": "capability profile as in API 1.0" },
"device_id": "messe1",
"pairing_required": true,
"challenge": "<64 hex characters>"
}
Without pairing (UME_REQUIRE_PAIRING=0): "pairing_required": false, no challenge field;
device_id is the ID from the service file or null.
pair:
{ "type": "pair", "response": "<64 hex characters, HMAC-SHA256>" }
paired:
{ "type": "paired" }
Computing the answer in Python (the key comes from the password manager, never from code):
import hashlib
import hmac
def pair_response(key_hex: str, device_id: str | None, challenge: str) -> str:
key = bytes.fromhex(key_hex)
msg = f"ume-pair-v1|{device_id or ''}|{challenge}".encode("utf-8")
return hmac.new(key, msg, hashlib.sha256).hexdigest()
What is allowed before pairing¶
| Message | Before pairing |
|---|---|
hello (Gateway → client) |
sent |
ping |
allowed, answer {"type": "pong", "api_version": "1.1"} |
pair |
allowed |
every other known command (move, head, mode, estop, action, persona, get_profile, list_skills, save_skill, delete_skill, run_skill, cancel_skill) |
refused with pairing_required, not executed |
status, camera frames (frame) (Gateway → client) |
not sent |
When an unpaired client disconnects, the robot is left untouched; only the end of a paired connection (or of a connection without pairing) puts it into the safe state.
No software stop before pairing. estop is refused like every other command, so an unpaired
client cannot stop the robot. The app shows the notice "Not paired – stop only on the robot or the
handheld remote" and disables its stop button while it is not paired. The emergency stop on the
device and the handheld remote remain the safety layer (decision of 31.10.2026).
Errors and close codes¶
Error messages have type: "error" and a readable text in message. The pairing error carries its
identifier in reason, validation errors in code:
| Case | Message |
|---|---|
| command before pairing | {"type": "error", "reason": "pairing_required", "message": "Dieses Tablet ist nicht mit dem uMe gekoppelt. Bitte die App aktualisieren und das Tablet im Servicebereich koppeln."} |
| not valid JSON | {"type": "error", "code": "bad_json", "message": "…"} |
| unknown type or invalid fields | {"type": "error", "code": "bad_request", "message": "…"} |
Validation errors (bad_json, bad_request) are also reported before pairing, because the Gateway
validates every message first. message is currently in German; an API 1.0 client shows the text
as it is.
| WebSocket close code | Reason | Meaning |
|---|---|---|
4401 |
pairing timeout |
no correct pair answer within 5 s |
4403 |
pairing failed |
wrong pair answer |
Gateway switches¶
In ~/.config/systemd/user/ume-gateway.service on the robot:
| Variable | Meaning | Default |
|---|---|---|
UME_DEVICE_ID |
ID, appears as device_id |
entered at installation |
UME_REQUIRE_PAIRING |
pairing required; 0, false, no, nein, off, aus switch it off (behaviour as API 1.0) |
1 (on) |
UME_PAIRING_KEY_FILE |
path of the pairing key (hex text, permissions 0600) |
~/.config/ume-gateway/pairing.key |
If pairing is required and the ID or the key is missing, or the key is not hex text or shorter than
128 bit, the Gateway does not start. install.sh rewrites the service file on every run, always
with UME_REQUIRE_PAIRING=1: a switch set to 0 by hand lasts only until the next install.sh run.
Limits¶
Pairing protects against foreign clients in the same network. It does not protect against an
active attacker in the network: ws:// is unencrypted and the client does not verify the Gateway.
Anyone who can read or redirect the traffic can take over a paired session. TLS or mutual
authentication are a later stage.
uMe Gateway — Suche und Kopplung (API 1.1)¶
Stand 31.10.2026. In Entwicklung — Netzwerk Stufe 1 ist im App-Repository übernommen; die
Folgeänderungen (Installationsanleitung Fassung 2.3) sind noch in Entwicklung, noch
nicht übernommen. Noch auf keinem Gerät eingespielt. Quelle: gateway/gateway_v2.py,
gateway/install.sh und gateway/INSTALL.md im Repository robotics-group-app. Einrichtung für
Techniker:
uMe: Verbindung ohne IP-Suche und Kopplung.
Übrige Befehle des Gateways: Gateway API (WebSocket).
Versionshinweis — API 1.0 → 1.1¶
| API 1.0 | API 1.1 | |
|---|---|---|
hello |
type, api_version, server, profile |
zusätzlich device_id, pairing_required, bei Kopplungspflicht challenge |
| Kopplung | keine | Challenge-Response mit HMAC-SHA256, Nachricht pair, Antwort paired |
| Vor der Kopplung | alle Befehle | nur ping und pair; kein status, kein Kamerabild |
| Erkennung im Netz | Adresse eingeben | mDNS-Dienst _ume-gw._tcp; zusätzlich Rechnername ume-<kennung>.local, wo der Roboter ihn veröffentlichen kann |
Die Änderung ist eine MINOR-Erhöhung: Ein Client prüft nur die MAJOR-Version. Ein hello ohne die
neuen Felder (Gateway 1.0) gilt als device_id = null, pairing_required = false. Ein Client der
API 1.0 kann ein Gateway mit Kopplungspflicht nicht steuern; er bekommt auf jeden Befehl den
Fehler pairing_required mit einem anzeigbaren Text in message.
Endpunkt¶
| Adresse | ws://<host>:8765/ws; <host> ist das Ziel des SRV-Eintrags von _ume-gw._tcp, ume-<kennung>.local (falls veröffentlicht) oder die Adresse aus der Suche im eigenen Netz |
| Transport | WebSocket, ws://, unverschlüsselt, nur im lokalen Netz |
| Kodierung | UTF-8-JSON, ein Objekt je Nachricht |
Kennung <kennung> |
1–20 Zeichen a-z, 0-9, -, nicht mit - beginnend oder endend; regulärer Ausdruck ^[a-z0-9]([a-z0-9-]{0,18}[a-z0-9])?$, geprüft von install.sh bei der Eingabe (das Gateway übernimmt UME_DEVICE_ID unverändert); nie eine Seriennummer |
mDNS-Diensteintrag¶
install.sh legt den Eintrag nur an, wenn avahi-daemon vorhanden ist. Der Roboter behält
seinen bisherigen mDNS-Rechnernamen; install.sh benennt ihn nicht um.
| Feld | Wert |
|---|---|
| Dienstart | _ume-gw._tcp |
| Port | 8765 |
| Rechnername (SRV-Ziel) | bisheriger mDNS-Rechnername des Roboters, unverändert |
| Zusatzname | ume-<kennung>.local, nur zusätzlich veröffentlicht, wenn avahi-publish auf dem Roboter vorhanden ist |
| Instanzname | Anzeigename, Vorgabe uMe <kennung> (1–63 Zeichen, eine Zeile) |
TXT api |
API-Version des Gateways, z. B. 1.1 |
TXT id |
Kennung (device_id) |
TXT name |
Anzeigename |
Beispiel (Kennung messe1; <host> steht für den bisherigen Rechnernamen des Roboters):
uMe messe1._ume-gw._tcp.local. SRV 0 0 8765 <host>.local.
uMe messe1._ume-gw._tcp.local. TXT "api=1.1" "id=messe1" "name=uMe messe1"
Der mDNS-Eintrag ist nur ein Hinweis. Ein Client wertet einen Fund erst als uMe, wenn auf
ws://<host>:8765/ws ein gültiges hello mit passender API-MAJOR kommt. Die App sucht dazu
gleichzeitig: zuletzt verbundener Roboter; mDNS (PTR-Anfrage nach _ume-gw._tcp und, wenn die
Kennung des letzten Roboters bekannt ist, A-Anfrage nach ume-<kennung>.local); Prüfung des
eigenen privaten IPv4-Teilnetzes (nur /24 oder kleiner) auf Port 8765. Ohne den Zusatznamen findet
die App den Roboter trotzdem über den Diensteintrag und die Suche im Teilnetz.
Gleiche device_id unter zwei Adressen. Die App verbindet mit keinem der beiden, weder
automatisch noch per Antippen. Auf der Kundenseite zeigen beide Einträge „Kennung mehrfach im Netz
– bitte den Service rufen“; der Technikerbereich nennt die Kennung.
Ablauf der Kopplung¶
Client Gateway
| --- WebSocket öffnen ------------------> | Frist 5 s beginnt
| <-- hello {device_id, pairing_required, |
| challenge} ------------------ |
| --- pair {response} -------------------> |
| <-- paired ----------------------------- | Frist beendet
| <-- status ----------------------------- | ab jetzt Status, Kamerabild, Befehle
- Das Gateway sendet als erste Nachricht
hello. Bei Kopplungspflicht enthält es eine neuechallengeje Verbindung (64 Hex-Zeichen, 256 Bit Zufall). - Der Client antwortet mit
pair.responseist HMAC-SHA256 als Hex-Text in Kleinbuchstaben:- Schlüssel: die Bytes des Kopplungsschlüssels (der Schlüssel liegt als Hex-Text vor und
wird vorher hex-dekodiert; mindestens 128 Bit,
install.sherzeugt 256 Bit); - Nachricht (UTF-8):
ume-pair-v1|<device_id>|<challenge>(bei fehlenderdevice_idein leerer Text zwischen den Trennzeichen).
- Schlüssel: die Bytes des Kopplungsschlüssels (der Schlüssel liegt als Hex-Text vor und
wird vorher hex-dekodiert; mindestens 128 Bit,
- Richtige Antwort →
paired, danach sofortstatus. Ab jetzt gelten alle Befehle der Gateway API. - Falsche Antwort → das Gateway schließt die Verbindung mit Code
4403. - Keine richtige Antwort innerhalb von 5 s nach Verbindungsaufbau → das Gateway schließt die
Verbindung mit Code
4401.
pair auf einer schon gekoppelten Verbindung oder bei abgeschalteter Kopplungspflicht wird mit
paired beantwortet. Der Schlüssel steht nie in hello, in Fehlertexten oder im Log.
Nachrichten¶
hello mit Kopplungspflicht (Platzhalter in spitzen Klammern):
{
"type": "hello",
"api_version": "1.1",
"server": "uMe Gateway",
"profile": { "...": "Fähigkeitsprofil wie bei API 1.0" },
"device_id": "messe1",
"pairing_required": true,
"challenge": "<64 Hex-Zeichen>"
}
Ohne Kopplungspflicht (UME_REQUIRE_PAIRING=0): "pairing_required": false, kein Feld
challenge; device_id ist die Kennung aus der Dienstdatei oder null.
pair:
{ "type": "pair", "response": "<64 Hex-Zeichen, HMAC-SHA256>" }
paired:
{ "type": "paired" }
Berechnung der Antwort in Python (der Schlüssel kommt aus dem Passwortmanager, nie aus dem Code):
import hashlib
import hmac
def pair_response(key_hex: str, device_id: str | None, challenge: str) -> str:
key = bytes.fromhex(key_hex)
msg = f"ume-pair-v1|{device_id or ''}|{challenge}".encode("utf-8")
return hmac.new(key, msg, hashlib.sha256).hexdigest()
Was vor der Kopplung erlaubt ist¶
| Nachricht | Vor der Kopplung |
|---|---|
hello (Gateway → Client) |
wird gesendet |
ping |
erlaubt, Antwort {"type": "pong", "api_version": "1.1"} |
pair |
erlaubt |
alle anderen bekannten Befehle (move, head, mode, estop, action, persona, get_profile, list_skills, save_skill, delete_skill, run_skill, cancel_skill) |
abgewiesen mit pairing_required, nicht ausgeführt |
status, Kamerabilder (frame) (Gateway → Client) |
werden nicht gesendet |
Schließt ein nicht gekoppelter Client die Verbindung, bleibt der Roboter unberührt; nur das Ende einer gekoppelten Verbindung (oder einer Verbindung ohne Kopplungspflicht) versetzt ihn in den sicheren Zustand.
Kein Software-Stopp vor der Kopplung. estop wird wie jeder andere Befehl abgewiesen; ein nicht
gekoppelter Client kann den Roboter also nicht anhalten. Die App zeigt dann den Hinweis „Nicht
gekoppelt – Stopp nur am Gerät oder Handsender“ und sperrt ihre Stopp-Taste, solange sie nicht
gekoppelt ist. Not-Aus am Gerät und Handsender bleiben die Sicherheitsebene (Entscheidung vom
31.10.2026).
Fehler und Schließcodes¶
Fehlermeldungen haben type: "error" und einen lesbaren Text in message. Der Fehler der Kopplung
trägt sein Kennzeichen in reason, die Prüffehler in code:
| Fall | Nachricht |
|---|---|
| Befehl vor der Kopplung | {"type": "error", "reason": "pairing_required", "message": "Dieses Tablet ist nicht mit dem uMe gekoppelt. Bitte die App aktualisieren und das Tablet im Servicebereich koppeln."} |
| kein gültiges JSON | {"type": "error", "code": "bad_json", "message": "…"} |
| unbekannter Typ oder ungültige Felder | {"type": "error", "code": "bad_request", "message": "…"} |
Prüffehler (bad_json, bad_request) werden auch vor der Kopplung gemeldet, weil das Gateway
jede Nachricht zuerst prüft. message ist derzeit deutsch; ein Client der API 1.0 zeigt den Text
unverändert an.
| WebSocket-Schließcode | Grund (reason) |
Bedeutung |
|---|---|---|
4401 |
pairing timeout |
keine richtige pair-Antwort innerhalb von 5 s |
4403 |
pairing failed |
falsche pair-Antwort |
Schalter am Gateway¶
In ~/.config/systemd/user/ume-gateway.service auf dem Roboter:
| Variable | Bedeutung | Vorgabe |
|---|---|---|
UME_DEVICE_ID |
Kennung, erscheint als device_id |
bei der Installation eingegeben |
UME_REQUIRE_PAIRING |
Kopplungspflicht; 0, false, no, nein, off, aus schalten sie ab (Verhalten wie API 1.0) |
1 (an) |
UME_PAIRING_KEY_FILE |
Pfad des Kopplungsschlüssels (Hex-Text, Rechte 0600) |
~/.config/ume-gateway/pairing.key |
Fehlen bei Kopplungspflicht Kennung oder Schlüssel, ist der Schlüssel kein Hex-Text oder kürzer als
128 Bit, startet das Gateway nicht. install.sh schreibt die Dienstdatei bei jeder Ausführung neu,
immer mit UME_REQUIRE_PAIRING=1: ein von Hand auf 0 gesetzter Schalter gilt nur bis zur
nächsten Ausführung von install.sh.
Grenzen¶
Die Kopplung schützt gegen fremde Clients im selben Netz. Sie schützt nicht gegen einen
aktiven Angreifer im Netz: ws:// ist unverschlüsselt, und der Client prüft das Gateway nicht.
Wer den Verkehr mitlesen oder umleiten kann, kann eine gekoppelte Sitzung übernehmen. TLS bzw.
gegenseitige Anmeldung sind eine spätere Stufe.