| name | elevator-sim |
| description | Run the universal button emulator simulator with preset scenarios for testing elevator integration logic |
/elevator-sim — Run elevator button emulator scenarios
When to use
User wants to test elevator integration logic — ride state machine transitions, arrival verification, degraded-mode fallback, multi-robot contention — without needing real hardware.
Prerequisites
- The headless simulator is a live API on the dev server:
POST /api/elevator/simulate (single ride) and POST /api/elevator/coordinate (fleet scheduling). Start the server first — see /start-dev — then drive the endpoints with curl.
- Both endpoints require auth (JWT cookie). In dev (
NODE_ENV !== production) the requireAuth middleware passes through as an admin, so a plain curl against http://localhost:3000 works with no cookie. Use the actual PORT if it isn't 3000.
- Implementation:
src/services/elevator/{simulator,ride-machine,routing,fleet}.js; HTTP glue in src/routes/elevator.js.
- For live-hardware tests, the E-Box wiring reference is
docs/30-integrations/elevator/keenon-ebox.md.
Scenarios
Ask the user which scenario to run. Each maps to a real endpoint + key:
| # | Scenario | Endpoint | Key / shape | Expected outcome |
|---|
| 1 | Single robot, single call — baseline happy path | /simulate | scenario: "happy" | completed |
| 2 | Two robots, same floor — contention, should batch into one ride | /coordinate | two same-direction requests | one trip, both robotIds, merged stops |
| 3 | Two robots, opposite directions — should serialize | /coordinate | two opposite-direction requests, cars: 1 | two trips on car 0 (serialized); pass cars: 2 to run in parallel |
| 4 | Elevator stuck in WAITING — car never arrives, timeout escalation | /simulate | scenario: "waiting_timeout" | completed_via_fallback |
| 5 | RFID / arrival read failure mid-ride — floor detection fallback | /simulate | scenario: "verify_fail" (recovers) or scenario: "verify_timeout_abort" (fail-closed) | completed_after_reroute / aborted |
| 6 | LoRa / comms loss between E-Box master and slave — degraded mode | /simulate | scenario: "comms_loss" | completed_via_fallback |
Bonus: scenario: "safety_interrupt" → completed_after_safety (safety event interrupts mid-move, resolves, resumes).
/simulate scenario keys (full list): happy, verify_fail, verify_timeout_abort, comms_loss, waiting_timeout, safety_interrupt.
Possible outcomes: completed, completed_after_reroute, completed_via_fallback, completed_after_safety, aborted.
Steps
- Gather inputs — scenario number + any overrides (requested floor, integration path, robot count, car count).
- Confirm the server is running —
curl -s http://localhost:3000/api/elevator/scenarios returns the scenario + path catalog. If it fails, start the dev server first.
- Run the scenario with the matching
curl below.
- Observe the response and check:
outcome matches the expected value in the table above
finalState is a terminal state (RELEASED for completions, ABORTED for fail-closed)
trace shows state transitions in the expected order (each entry is { from, to, event })
- For
/coordinate: same-direction requests batched into one trip; opposite directions serialized (one car) or split across cars (multi-car)
- Record observations in a scratch note or GitHub comment.
curl examples
Single ride — POST /api/elevator/simulate
curl -s http://localhost:3000/api/elevator/simulate \
-H 'Content-Type: application/json' \
-d '{"path":"accelerate_emulator","scenario":"happy","requestedFloor":5}'
curl -s http://localhost:3000/api/elevator/simulate \
-H 'Content-Type: application/json' \
-d '{"scenario":"verify_fail","requestedFloor":5}'
curl -s http://localhost:3000/api/elevator/simulate \
-H 'Content-Type: application/json' \
-d '{"scenario":"verify_timeout_abort"}'
curl -s http://localhost:3000/api/elevator/simulate \
-H 'Content-Type: application/json' \
-d '{"scenario":"comms_loss"}'
curl -s http://localhost:3000/api/elevator/simulate \
-H 'Content-Type: application/json' \
-d '{"scenario":"waiting_timeout"}'
Defaults if omitted: path → accelerate_emulator, scenario → happy, requestedFloor → 5.
Valid path values: accelerate_emulator, keenon_ebox, oem_api.
Fleet coordination — POST /api/elevator/coordinate
curl -s http://localhost:3000/api/elevator/coordinate \
-H 'Content-Type: application/json' \
-d '{"cars":1,"requests":[
{"robotId":"r1","fromFloor":1,"toFloor":5,"priority":"routine"},
{"robotId":"r2","fromFloor":2,"toFloor":7,"priority":"high"}
]}'
curl -s http://localhost:3000/api/elevator/coordinate \
-H 'Content-Type: application/json' \
-d '{"cars":1,"requests":[
{"robotId":"r1","fromFloor":1,"toFloor":8,"priority":"routine"},
{"robotId":"r2","fromFloor":9,"toFloor":2,"priority":"emergency"}
]}'
Response shape: { cars, trips:[ { direction, robotIds, stops, priority, car } ] }.
Trips are ordered highest-priority first (emergency > high > routine), ties broken up-before-down. Each trip is assigned to the least-loaded car.
Notes
- This skill drives the live API — no browser/UI needed. (The static
pages/elevator-button-emulator.html page is a separate planner UI, not the test runner. It's auth-gated and reachable from the admin Command Center under Elevator Research → Hardware.)
- The simulator is pure: each scenario is an ordered event list replayed through the ride state machine in
src/services/elevator/ride-machine.js, so every scenario is implicitly validated against the legal-transition table.
- State machine reference lives in
docs/20-architecture/software-stack.md section 3, and the routing rules in docs/20-architecture/adr/0007-elevator-integration-strategy.md.