Skip to content

Building API — /api/gebaeude/**

The server-side interface behind the Building tab of the technician page: modules, calls, doors and lift of a site. Everything on this page is read from the code of tablet-ui, branch zugriffsschutz, uGo service 1.0.4 (build id 1788703020348). BELEGT marks a statement taken from the code and its tests; OFFEN marks something the code does not do yet.

Status of this page: 12.09.2026.

Software state and what has been re-checked

The frozen software state of the platform is uGo 1.0.8, kiosk 1.0.3, network guard 1.1.9 (frozen 10.09.2026). The route descriptions below were read from uGo service 1.0.4 and have not yet been re-checked against 1.0.8 — that re-check is an open item (A0c of the building findings, state 06.09.2026). No build id is on record for 1.0.8; the one above belongs to 1.0.4.

Where a value on this page differs from the building interface description 1.1 (12.09.2026, six languages, see Integrations), the interface description is the leading document for the site side and this page for the routes.

Ports on the site computer (rule of 11.09.2026)

The principle of the rule: manufacturer services keep their ports; the services of United Robotics Group move out of the way. The one substantive change is the lift simulator of United Robotics Group, which moved from 8085 to 8185; ports 8080 and 8085 of the manufacturer service stay unchanged. The default of ebenenwechselAdresse below is unaffected by the rule.

The port table lives in one place

The full table is on Site computer and building § 5.1. It used to be repeated here as well; since 12.09.2026 it is not, so that a port change has exactly one place on this portal to be made instead of three. Offline, the building interface description 1.1 carries the table in chapter 11.1 and remains the leading document for the values.

Three rules that apply to every route on this page

  1. Every route requires the technician role — reading included. BELEGT Behind this tab lies the customer's building equipment: addresses and serial numbers of the modules, doors, the lift. All routes go through technikerAbsage()schreibabsage(): 401 {"fehler":"Nicht angemeldet."} without a session, 403 {"fehler":"Ändern darf nur der Techniker."} for an operator session and for the service key. From the device (loopback) with no password configured at all, the routes are open, like everything else on the device.
  2. The browser never talks to a module or a service. Every call to a u-IoT module (/ping, /health, /io/status, /io/do, /config, /actions, /call-me/*) and to the level-change service (/health-check, /points) is made by the uGo server on the site computer. config/export and config/import of a module are never called — the export contains passwords in clear text.
  3. No drive command. No route under /api/gebaeude/** writes to /chassis/moves or to POST /moves of the level-change service. The adapter for POST /moves exists in aufzug.ts and is not wired to any route in this stage. BELEGT

All responses are application/json with cache-control: no-store. Unreadable JSON in a request body yields 400 {"fehler":"Unlesbare Anfrage."}. Error bodies always carry a fehler sentence in German — the technician page shows it verbatim.

Storage and settings

One file on the site computer, daten/gebaeude.json (override with GEBAEUDE_DATEI), version field fassung: 1, written side-file-then-rename. All entities go through sanitisers on read and write: an unusable field is reset to its default, an entity that makes no sense without the field (floor without map, channel without id) is dropped. No secret is ever stored: a lift carries sharedKeyRef (a reference into the password store), never a value; a sharedKey field sent by a client is silently discarded. BELEGT

Setting (einstellungen) Default Meaning
suchbereich "" (= the /24 of the chassis address) scan range for modules: a.b.c.0/24, a.b.c.10-60, a.b.c.10-a.b.c.60 or a single address; at most 1024 addresses
modulPort 80 port the modules answer on (1–65535)
ebenenwechselAdresse http://127.0.0.1:3100 base address of the level-change service (http(s)://host[:port], no path)
roboterKennung robot-1 robotId of this robot towards the level-change service

The level-change service key is not a setting: it comes from the environment of the uGo server (EBENENWECHSEL_SCHLUESSEL) and is sent as Authorization: Bearer …. It appears in no response. BELEGT

Data model

Entity Fields Rules
Modul id, name, serialNumber, address, mapUid?, x?, y?, role (coordinator|client|standalone), lastSeenAt (epoch ms), firmwareBuild? {project, idfVersion, buildDate, buildTime} name required and unique per site (case- and whitespace-insensitive); serialNumber required — it is the key for recognising a module, not the address; address must be http(s)://host[:port]; location only stored when mapUid, x and y are all present
Anschluss moduleId, channel (DI_1|DI_2|DO_1|DO_2), label, purpose (door|light|sound|button|unused) dropped when channel is unknown; entries for unknown modules are dropped on save
Ruf id, moduleId, channel (DI_1|DI_2), pointName, mapUid, actions[] (drive|sound|light), soundOutput? / lightOutput? {moduleId, channel: DO_x} pointName = point_id of the firmware = properties.name of the map overlay
Tuer id, name, mapUid, moduleId, outputChannel (DO_1|DO_2|""), zoneId, openPointName, closePointName, discoveredDeviceId (MAC, upper-cased, or ""), armed armed is derived: name, mapUid and the five mandatory fields moduleId, outputChannel, zoneId, openPointName, closePointName all set
Aufzug id, label, adapter (default ebenenwechsel), configJson (≤ 20 000 chars), sharedKeyRef, cabinSpeedMps, thresholdMode (standard|cabin) cabinSpeedMps clamped to 0.2…0.5 by the model (default 0.35) and additionally rejected above the upper limit by the route (see /aufzug); thresholdMode default cabin
EtageKarteBindung elevatorId, floor (integer ≥ 1), mapUid, waitPointName, doors[] (≤ 8 strings) dropped when floor, mapUid or elevatorId is missing

Map identity everywhere is mapUid. The level-change service still keys its point catalogue by mapId (String(map.id)); the routes pass through whatever id the caller gives and convert nothing. OFFEN (open question 2 of the requirements document).

Routes

GET /api/gebaeude/module

Module list of the site with channels and the tab's settings.

{
  "module": [ { "id": "…", "name": "Eingang Nord", "serialNumber": "…", "address": "http://192.168.0.21",
                "role": "standalone", "lastSeenAt": 1788700000000,
                "firmwareBuild": { "project": "…", "idfVersion": "…", "buildDate": "…", "buildTime": "…" } } ],
  "anschluesse": [ { "moduleId": "…", "channel": "DO_1", "label": "Tür Labor", "purpose": "door" } ],
  "einstellungen": { "suchbereich": "", "modulPort": 80, "ebenenwechselAdresse": "http://127.0.0.1:3100", "roboterKennung": "robot-1" },
  "suchbereichVorgabe": "192.168.0.0/24"
}

PUT /api/gebaeude/module

Body: any of module[], anschluesse[], einstellungen{} (partial merge). Response as GET. BELEGT

Status Body
200 the stored state (channels of unknown modules removed)
400 Jedes Modul in der Liste braucht einen Namen. · Der Name „…" ist zweimal vergeben. · Das Modul „…" hat keine Seriennummer — ohne sie ist es nicht wiedererkennbar.

GET /api/gebaeude/module/suche · POST /api/gebaeude/module/suche

The scan runs on the server, exactly one at a time, and outlives the page. Per address: GET /ping (1.5 s) must answer {"status":"pong"}; only then GET /health and GET /version (3 s). A device without a serial number is not a find. 16 addresses in parallel; a /24 takes just under half a minute. BELEGT

POST {"aktion":"start","bereich"?:"192.168.0.10-60"}202 with the scan state; POST {"aktion":"abbrechen"}200. A typed range that parses is remembered as einstellungen.suchbereich. Without a range the stored one applies, else the /24 of the chassis address.

{ "laeuft": true, "bereich": "192.168.0.0/24", "geprueft": 37, "gesamt": 254,
  "funde": [ { "address": "http://192.168.0.21", "serialNumber": "…", "macAddress": "…", "uhrGestellt": true,
               "funknetzAn": false, "firmwareBuild": { "…": "…" }, "gefundenAm": 1788700000000 } ],
  "abgebrochen": false, "fehler": "", "begonnenAm": 1788700000000 }
Status Body
400 Erwartet wird aktion: start oder abbrechen. · Der Suchbereich „…" ist nicht lesbar oder zu gross. … · scan state with fehler set (empty or unreadable range)
409 Es gibt keinen Suchbereich: die Geräteadresse ist keine IPv4-Adresse. …

GET /api/gebaeude/module/{id}

Live state of one module from /health and /io/status (3 s each). If either answer is missing the module is not reachable and no value is returned — no frozen state. On success lastSeenAt is updated (at most once a minute) and firmwareBuild is fetched once via /version if the module was added by hand. BELEGT

{ "modulId": "…", "erreichbar": true,
  "eingaenge": [ { "kanal": "DI_1", "aktiv": false }, { "kanal": "DI_2", "aktiv": true } ],
  "ausgaenge": [ { "kanal": "DO_1", "an": true }, { "kanal": "DO_2", "an": false } ],
  "uhrGestellt": true, "funknetzAn": false,
  "serialNumber": "…", "macAddress": "…", "stationsAdresse": "…", "firmwareBuild": { "…": "…" } }

Not reachable: { "modulId": "…", "erreichbar": false, "eingaenge": [], "ausgaenge": [], "uhrGestellt": false, "funknetzAn": false, "serialNumber": "", "macAddress": "", "stationsAdresse": "" }. Unknown id → 404 Dieses Modul ist nicht in der Liste.

POST /api/gebaeude/module/{id}

One action per call. Any module error → 502 {"fehler": "<the module's error sentence>"}.

Body What the server does 200 response
{"aktion":"ausgang","kanal":"DO_1"\|"DO_2","zustand":"ein"\|"aus"\|"um"} POST /io/do {output_id, state: high\|low\|toggle}, then re-reads /io/status {"kanal":"DO_1","an":true,"ausgaenge":[…]} — the state shown is the re-read one
{"aktion":"funknetz","an":true\|false} reads /config, writes back only the access_point branch (enabled, apsta_rescue_enabled, ssid, channel — never a password field) via PUT /config {"uebernommen":true,"neustartNoetig":false}
{"aktion":"sicherung","handlung":"sichern"\|"wiederherstellen"\|"werkszustand"} POST /config/backup · /config/restore · /config/reset (8 s) {"status":"…","sicherungVorhanden":true}

400 sentences: Erwartet werden kanal (DO_1, DO_2) und zustand (ein, aus, um). · Erwartet wird an: true oder false. · Erwartet wird handlung: sichern, wiederherstellen oder werkszustand. · Unbekannte Handlung. The confirmation prompts of the UI (door output, two-step factory reset) are not re-checked here — the route checks that the caller may, the UI asks whether they meant it.

GET /api/gebaeude/rufe · GET /api/gebaeude/rufe?nur=offene

Coordinator, stored call buttons and the open calls from /call-me/status. If a coordinator is set, only it is asked; otherwise every module with an address. A module that does not answer contributes no calls and no error. Calls that were completed locally are filtered out. BELEGT

{ "koordinatorId": "…",
  "rufe": [ { "id": "…", "moduleId": "…", "channel": "DI_1", "pointName": "Labor 2", "mapUid": "…",
              "actions": ["drive","sound"], "soundOutput": { "moduleId": "…", "channel": "DO_2" } } ],
  "offene": [ { "requestId": "…", "pointName": "Labor 2", "state": "queued", "offen": true,
                "cancellable": true, "createdAtMs": 0, "updatedAtMs": 0, "robotId": "ugo", "message": "",
                "moduleId": "…" } ] }

moveId (number) and angenommenAm (epoch ms) are present only when set; sicherungVorhanden on the module route is never set in this stage. offen is false for the firmware end states succeeded, failed, cancelled, cancelled_by_user, error. With ?nur=offene only {"offene":[…]} is returned (used for the one-second poll).

POST /api/gebaeude/rufe

Body What the server does 200 response
{"aktion":"koordinator","moduleId"} reads the call_me branch of the new module's /config, writes it back with role: coordinator, coordinator_base_url: ""; if u_logs is empty, enters this robot (u_log_id: "ugo", base_url = the chassis address uGo uses). Then every previous coordinator becomes client with coordinator_base_url = the new module's address. Fields whose name matches pass\|secret\|token\|key are never written back. {"koordinatorId":"…","module":[…]}
{"aktion":"taster","ruf":{moduleId, channel, pointName, mapUid, actions, soundOutput?, lightOutput?}} POST /actions on the module: {action_id:"ugo_ruf_di_1", display_name, enabled:true, action_type:"call_me_request", trigger_source:"digital_input", linked_inputs:[channel], call_me_action:{point_id}} — the id is stable per input so a second assignment replaces the first. Firmware first, storage second. {"rufe":[…]}
{"aktion":"taster-entfernen","id"} removes the entry from the storage — the action in the module is left as is OFFEN {"rufe":[…]}
{"aktion":"annehmen","requestId"} in-memory note angenommenAm (lost on restart; the firmware has no notion of accepting) {"angenommen":"…"}
{"aktion":"erledigen","requestId","moduleId","cancellable":true\|false} if cancellable: POST /call-me/cancel {request_id}; a 400 from the firmware means "no longer cancellable" and falls through to the local note. Never a cancel at the chassis. {"erledigt":"…","weg":"firmware"\|"oertlich"}

Errors: 404 Das Modul des Tasters ist nicht in der Liste. · 400 Es ist kein Punkt gewählt. · 400 Es fehlt die Kennung des Rufs. · 400 Es fehlen die Kennung des Rufs oder das Modul. · 502 <module error>.

Stored, not executed

actions, soundOutput and lightOutput of a call button are stored only. Nothing in this stage drives to the point, switches the sound or the light on a call. The call itself is dispatched by the module firmware to the robot entered in its u_logs. OFFEN

GET /api/gebaeude/tueren · PUT /api/gebaeude/tueren

All doors of all maps, each with fehlt[] — the mandatory fields still empty, in form order (moduleId, outputChannel, zoneId, openPointName, closePointName). PUT {"tueren":[…]} (at most 200) stores the list. A door without a zone is stored but not armed; rejected is only what cannot coexist on one map. BELEGT

{ "tueren": [ { "id": "…", "name": "Labortür", "mapUid": "…", "moduleId": "…", "outputChannel": "DO_1",
                "zoneId": "", "openPointName": "vor Labor", "closePointName": "hinter Labor",
                "discoveredDeviceId": "", "armed": false, "fehlt": ["zoneId"] } ] }
Status Body
400 Erwartet wird tueren: [...] · Die Tür „…" hat keinen Namen. · … gehört zu keiner Karte. · … trägt einen Namen, den auf dieser Karte schon eine andere Tür trägt. · … schaltet einen Ausgang, den auf dieser Karte schon eine andere Tür schaltet. — plus befunde: [{tuerId, grund}] with grundname-fehlt, karte-fehlt, name-doppelt, ausgang-doppelt

No switching, no test run on this route. The door list is not handed to the door service (door-control) in this stage. OFFEN

GET /api/gebaeude/aufzug

Lifts, floor ↔ map bindings, findings, reachability of the level-change service (GET /health-check, 2.5 s) and the current upper limit for the cabin approach speed. BELEGT

{ "aufzuege": [ { "id": "…", "label": "Aufzug Haus 2", "adapter": "ebenenwechsel", "configJson": "",
                  "sharedKeyRef": "", "cabinSpeedMps": 0.35, "thresholdMode": "cabin" } ],
  "etagen": [ { "elevatorId": "…", "floor": 1, "mapUid": "…", "waitPointName": "warte-eg", "doors": ["1"] } ],
  "befunde": [ { "elevatorId": "…", "floor": 2, "grund": "wartepunkt-fehlt" } ],
  "dienst": { "erreichbar": true },
  "obergrenze": { "mps": 0.4, "quelle": "fahrwerte", "fahrzeugMps": 0.4 },
  "einstellungen": { "ebenenwechselAdresse": "http://127.0.0.1:3100", "roboterKennung": "robot-1" } }

obergrenze.mps = min(0.5 m/s fixed limit, |max_forward_velocity| from the vehicle's drive values). If the drive values cannot be read within 2.5 s, quelle is vorgabe and mps is 0.5. befunde.grundkarte-doppelt, etage-doppelt, wartepunkt-fehlt.

PUT /api/gebaeude/aufzug

Body: any of aufzuege[], etagen[], einstellungen{} (partial merge). Floors of unknown lifts are dropped. Response as GET.

Status Body
400 Etage N: diese Karte ist schon einer anderen Etage zugeordnet. Eine Karte darf nur zu einer Etage gehören. · Etage N ist zweimal angelegt. — with befunde
400 <label>: 0,5 m/s liegt über der Obergrenze 0,4 m/s. Das ist die Höchstgeschwindigkeit, die das Fahrzeug selbst führt; sie wird aus dem Reiter Gebäude nicht angehoben. (or … Das ist die feste Obergrenze für die Fahrt in die Kabine.) — with obergrenze

The upper limit is read fresh from the vehicle on every save, not taken from the client. A speed above it is rejected, not clamped — the rule "no raising drive values remotely". Missing waiting points are stored and reported, not rejected. BELEGT

The drive profile has no effect on the level-change service yet

cabinSpeedMps and thresholdMode are stored and validated here. The level-change service drives its cabin legs with the cabinProfile of its own MapLevelProfile (defaults CABIN_ENTRY_SPEED_MPS 0.35 m/s, cap MAX_SPEED_MPS 0.5 m/s, thresholdMode cabin). Its POST /moves schema does not accept a cabinProfile field; the adapter in aufzug.ts would send one, but is not wired. OFFEN

GET /api/gebaeude/aufzug/punkte[?mapId=…]

Point catalogue of the level-change service, GET /points[?mapId=], read only, with Authorization: Bearer <EBENENWECHSEL_SCHLUESSEL>. mapId is the service's id (today String(map.id)), not mapUid. gelesenAm lets the UI mark an outdated catalogue. BELEGT

{ "gelesenAm": 1788700000000,
  "punkte": [ { "pointId": "…", "mapId": "68", "alias": "warte-eg", "x": 12.4, "y": 3.1 } ] }
Status Body
502 Es ist keine Adresse für den Ebenenwechseldienst eingetragen. · Der Ebenenwechseldienst hat den Punktkatalog nicht geliefert (401). · fetch error text

What is not on this page — because it is not in the code

Function Requirement State
test ride floor X (POST /moves) G-T-09 adapter ebenenwechselAnfordern() exists, no route calls it; UI button disabled OFFEN
door test run G-T-05 not implemented; a door output is switched via POST /module/{id} with a UI prompt OFFEN
lift config validation (/elevatorControl/config/validate) G-T-06 configJson and sharedKeyRef are stored only; no call to the lift service OFFEN
lift session state (queuedcompleted) G-T-10 UI shows "Kein Ebenenwechsel aktiv"; no route reads a session OFFEN
door handover to the door service (POST /doors) requirements §5 not implemented OFFEN
call actions drive / sound / light G-R-05 stored only OFFEN
setting a time source at the module G-M-07 no firmware endpoint; UI shows the state only OFFEN