Inspiration
Priority scheduling with aging is a core idea in operating systems, used to stop a scheduler from starving low-priority processes while it keeps favoring busier ones. QNX is built around exactly that kind of real-time, deterministic decision-making. Instead of demoing that idea with a slide and a formula, we wanted to build something a judge could watch happen: a physical elevator that decides which floor to serve next based on how many people are waiting and how long they've waited, running on the actual OS the concept comes from.
What it does
QNX_Elevator is a 3-floor, 3D-printed elevator model powered by a Raspberry Pi 5 running QNX SDP 8.0. A side-mounted USB webcam watches all three floors at once, and a custom vision pipeline counts how many people (represented by Lego heads) are waiting on each floor. Four physical call buttons, wired in standard elevator convention (bottom floor: up only, middle floor: up and down, top floor: down only), register when a floor actually wants service. A dispatcher process combines the two: it only considers floors with an active call, and picks the next floor using
priority(floor) = head_count(floor) + aging_factor * wait_time_elapsed(floor)
so a floor with fewer people waiting still eventually outranks a busier one if it's been waiting long enough. A servo and pulley system then physically moves the car to the chosen floor.
How we built it
The system is four separate QNX processes talking over named FIFOs, deliberately split so each piece could be tested in isolation:
vision_service (C) — grabs frames from the USB webcam through QNX's Video Capture framework, splits the frame into three zones (one per floor), and runs a from-scratch color-threshold + connected-component blob counter to get a headcount per zone. No OpenCV: QNX has no working port, so the detection logic is custom. floor_input (Python, QNX's rpi_gpio module) — reads the four call buttons and tracks which floors have an active request and how long they've been waiting. dispatcher (Python) — runs the priority+aging formula and decides where the car goes next. motor_control (Python, rpi_gpio PWM) — drives the servo to the target floor's calibrated angle.
We built the priority algorithm and its test coverage before writing a single line of hardware code: a simulation mode fed synthetic floor states through the real dispatcher logic, let us tune the aging factor, and gave us a mutation-tested integration suite (52 assertions across two suites) that we could trust before ever touching a servo.
Challenges we ran into
Almost every challenge came from the same root cause: assuming QNX behaves like Linux when it doesn't, and having to verify instead of guess.
QNX SDP 8.0 dropped the io-pkt networking stack entirely in favor of io-sock — the exact command we started with (io-pkt-v6-hc) doesn't exist on this version. The Raspberry Pi Camera Module 3's CSI interface has no QNX driver in the BSP for either the Pi 4 or Pi 5 — we pivoted to a USB UVC webcam instead of writing a sensor driver from scratch. QNX has no working OpenCV port (an open, unresolved upstream issue), which meant "face recognition" had to become honest, scoped-down color/shape blob counting written in plain C. The board has no internet access, only LAN — deployment meant figuring out scp/ssh over the local network from scratch, including tripping over a nonexistent destination path and an unnecessary sudo. A strict -std=c99 build flag hid struct timespec and nanosleep() on host glibc entirely; fixed with an explicit _POSIX_C_SOURCE feature-test macro, tuned twice after the first value turned out to hide snprintf on a different libc. QNX's rpi_gpio module looks like Linux's RPi.GPIO but isn't a drop-in replacement — bouncetime-based debouncing doesn't exist anywhere in its event handling. We only found this from a live TypeError on the actual board, then confirmed it against QNX's own API comparison documentation, which also told us several other Linux-familiar functions (add_event_callback, wait_for_edge, event_detected) simply aren't available.
Accomplishments that we're proud of
Getting the algorithm and IPC layer fully verified — via simulation, then a real integration test hand-injecting messages through the actual named-FIFO protocol — before it ever touched hardware, so every hardware-side bug we hit was isolated to exactly one flagged, already-documented unknown instead of a tangle of unrelated failures. Also proud of never letting the project overclaim what it does: it's blob counting, not face recognition, and we said so once we knew, rather than let the flashier framing stand.
What we learned
That porting anything to QNX means re-verifying it, not assuming it. Linux muscle memory (RPi.GPIO signatures, -std=c99 behavior, POSIX message queues being available in Python) was wrong often enough that "check the actual QNX docs" became the default move rather than the fallback. We also came away with a strong preference for testing scheduling/decision logic in simulation before it's anywhere near a motor — every algorithm bug we found, we found for free that way.
What's next for QNX_Elevator
Finishing on-device servo calibration (measuring real per-floor angles once the pulley mechanism is fully assembled), running the vision pipeline against real Lego heads under real lighting to tune the color thresholds, and live-tuning the aging factor so the starvation-prevention effect is clearly visible in a live demo. Longer term: more floors, a second car to explore multi-elevator dispatch, and swapping the button-based "call" signal for something that reflects real intent to travel rather than just presence.
Built With
- 3d-printing
- bash
- c
- computer-vision
- embedded-systems
- gpio
- ipc
- makefile
- multiprocessing
- named-pipes
- posix
- pwm
- python
- qnx
- qnx-neutrino
- raspberry-pi
- raspberry-pi-5
- real-time-os
- robotics
- rtos
- scheduling-algorithms
- servo-motor
- ssh
- usb-video-class
Log in or sign up for Devpost to join the conversation.