# Ithaca capture node The software a Jetson runs to join an Ithaca RGBD capture rig: a Python bridge that puts the node on the WebSocket mesh and answers Preview / Record, a C++ preview server that streams each camera's colour (the sensor's own JPEG) and depth (its own 16-bit millimetres, LZ4-compressed) straight to the Unity viewer, and a C++ recorder that writes K4A-format MKV takes on Record — the same format the rest of Ithaca reads. One checkout, one command, on any Jetson: ```sh git clone ~/ithaca-node cd ~/ithaca-node ./install.sh ``` ## Why an install script and not a disk image The rig currently spans two Jetsons with **no JetPack in common**: | Node | SoC | JetPack | Ubuntu / CUDA | |------|-----|---------|---------------| | Jetson Nano (2019) | tegra210 | **4.x only** (EOL) | 18.04 / 10.2 | | Jetson Orin Nano | tegra234 | **5 or 6** | 22.04 / 12.x | tegra210 never got a newer L4T and the Orin refuses the old one, so no single OS image covers both, and no compiled binary is portable between them — CUDA, glibc and the C++ ABI all differ by generation. What *is* portable is the **source and the procedure**. `install.sh` detects the platform and builds the C++ locally against a locally-built OrbbecSDK, so the same command produces a correct binary on each machine. Adding a future Orin NX or a Thor needs nothing new here — the script compiles against whatever toolchain it finds. The bridge is pure Python and already runs identically everywhere; keep it that way by never giving it a dependency that has to be compiled. ## Layout ``` install.sh one entry point, idempotent bridge/ bridge.py the node client — shipped verbatim, tested in place config.template.json @PLACEHOLDERS@ filled per user by install.sh server/ ithaca_rgbd_server.cpp the preview server CMakeLists.txt links a prebuilt libOrbbecSDK, no GStreamer recorder/ depth_color_k4arec_pipeline.cpp the MKV recorder — FTF source, not a stock example CMakeLists.txt built inside the SDK examples tree, links the K4A wrapper systemd/ ithaca-bridge.service.in templated with the user and repo path usbfs-memory.service raises usbfs_memory_mb for the camera bandwidth udev/ 99-obsensor-libusb.rules reference copy (the SDK's own installer wins if present) scripts/ detect_platform.sh L4T / JetPack / SoC, as KEY=value install_deps.sh apt + websockets build_k4a_wrapper.sh reuse or build the Orbbec K4A wrapper (libk4a) build_sdk.sh reuse or build OrbbecSDK v2 + inject/build the recorder build_server.sh cmake + make the preview server ``` ## The three C++ pieces, and how each is built - **Preview server** (`server/`) — our source, built standalone against the prebuilt `libOrbbecSDK.so`. Simple. - **Recorder** (`recorder/`) — FTF's own source, a customised SDK example, so it is injected into the SDK's `examples/` tree and built there (it uses the SDK's own cmake targets). It writes K4A-format MKV, and at runtime dlopens `libk4a.so` / `libk4arecord.so` — provided by the **Orbbec K4A wrapper**, *not* Microsoft's Azure Kinect SDK. Found by rpath, so no `LD_LIBRARY_PATH`. - **Orbbec K4A wrapper** — Orbbec's `libk4a`, backed by their own SDK, cloned and built by `build_k4a_wrapper.sh`. This is what makes Record work while setting the Microsoft SDK aside. ## What install.sh does 1. Detects L4T / JetPack / SoC (`scripts/detect_platform.sh`). 2. Installs build and run dependencies (OpenCV for the recorder; no GStreamer — the direct route encodes no video). 3. Builds (or reuses) the Orbbec K4A wrapper — `libk4a` for the recorder. 4. Builds (or reuses) the OrbbecSDK, injecting and building the recorder example against the wrapper. A node that already has a good build (the Nano) is left untouched; the recorder is added to it only if missing. 5. Builds `ithaca_rgbd_server` against the SDK. 6. Generates `bridge/config.json` for the current user — this is where all the machine-specific paths live, so `bridge.py` itself stays unmodified. 7. Installs the udev rules and the `usbfs-memory` service, and adds the user to the `video` group. 8. Generates and starts the `ithaca-bridge` systemd service. If the K4A wrapper is unavailable, install.sh still completes: the node then does **preview only**, and says so. Record needs the wrapper. Re-run it after a `git pull`: it rebuilds, regenerates the unit and config, and restarts the service. It keeps an existing `config.json` (delete it to regenerate) and an existing SDK build. ## The two source builds to watch on a new JetPack Both scripts reuse an existing build when they find one. On a machine with none they clone and build from source — the steps most likely to need attention on a JetPack they have not been tried on: - **Orbbec K4A wrapper** — `OB_K4A_REPO` / `OB_K4A_REF` pin the source (default `v1.10.4`). If it fails, build it by hand once and re-run with its location: ```sh K4A_WRAPPER_DIR=/path/to/OrbbecSDK-K4A-Wrapper ./install.sh ``` - **OrbbecSDK v2** — `OB_SDK_REPO` / `OB_SDK_REF` pin the source (default `main`). Likewise: ```sh OB_SDK_ROOT=/path/to/OrbbecSDK_v2 ./install.sh ``` Both can be passed together. Neither is Microsoft's Azure Kinect SDK — the recorder gets its `libk4a` from the Orbbec wrapper, which is buildable from source across platforms. ## Operating the node ```sh systemctl status ithaca-bridge # is it up journalctl -u ithaca-bridge -f # live log ``` Runtime state the bridge writes beside itself (git-ignored): - `config.json` — this machine's config (generated). - `cam_settings.json` — per-camera settings, kept across restarts. - `stream_map.txt` — the live composition: line 1 the cameras to stream, line 2 the alignment (`raw` or `compare`). The preview server re-reads it once a second, so a camera can be turned on or off, and Compare switched, without restarting.