Skip to content

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
  1. The Gateway sends hello first. When pairing is required it carries a new challenge per connection (64 hex characters, 256 random bits).
  2. The client answers with pair. response is 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.sh creates 256 bit);
    • message (UTF-8): ume-pair-v1|<device_id>|<challenge> (an empty text between the separators if device_id is missing).
  3. Correct answer → paired, immediately followed by status. From now on every command of the Gateway API applies.
  4. Wrong answer → the Gateway closes the connection with code 4403.
  5. 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
  1. Das Gateway sendet als erste Nachricht hello. Bei Kopplungspflicht enthält es eine neue challenge je Verbindung (64 Hex-Zeichen, 256 Bit Zufall).
  2. Der Client antwortet mit pair. response ist 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.sh erzeugt 256 Bit);
    • Nachricht (UTF-8): ume-pair-v1|<device_id>|<challenge> (bei fehlender device_id ein leerer Text zwischen den Trennzeichen).
  3. Richtige Antwort → paired, danach sofort status. Ab jetzt gelten alle Befehle der Gateway API.
  4. Falsche Antwort → das Gateway schließt die Verbindung mit Code 4403.
  5. 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.