# Inspire hand service + web dashboard, self-contained (aarch64 / Jetson). # Build context is assembled by build_image.sh (pulls unitree_sdk2 + CycloneDDS # from the robot). Run with: --network host --privileged -v /dev:/dev FROM arm64v8/ubuntu:20.04 ENV DEBIAN_FRONTEND=noninteractive TZ=UTC RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential cmake \ libboost-all-dev libspdlog-dev libeigen3-dev \ python3 python3-flask \ iproute2 procps ca-certificates \ && rm -rf /var/lib/apt/lists/* # CycloneDDS (ddscxx + iceoryx) headers & runtime libs from the robot's /usr/local COPY deps/usr_local_include/ /usr/local/include/ COPY deps/usr_local_lib/ /usr/local/lib/ RUN ldconfig # unitree_sdk2 headers + prebuilt static lib (for build.sh linking) COPY deps/unitree_sdk2/ /opt/unitree_sdk2/ # Record/replay panel: the recorder is a python child process that speaks DDS itself, so # the image needs the bindings as well as the script. Skipped cleanly when build_image.sh # could not find the sources (empty dirs) — the panel then reports "recorder not found". # NOTE: this layer pip-installs, so the robot needs network access at BUILD time. Offline # alternative: copy the robot's own site-packages in the same way the C libs are copied. COPY deps/recorder/ /opt/recorder/ COPY deps/unitree_sdk2_python/ /opt/unitree_sdk2_python/ # cyclonedds is pinned to what unitree_sdk2py demands (0.10.2) — installing anything else # just gets downgraded when the SDK lands. It is built from sdist here, so it compiles # against the robot's own CycloneDDS headers (0.10.5) and links its libddsc. ARG CYCLONEDDS_PY=0.10.2 ENV CYCLONEDDS_HOME=/usr/local # The SDK is COPIED into dist-packages rather than pip-installed, and both alternatives # were tried on the robot first: # * a wheel install loses four subpackages — b2, comm, g1, h1 ship with no __init__.py, # so find_packages() drops them and the package's own # `from . import idl, utils, core, rpc, go2, b2` dies with "cannot import name 'b2'"; # * `pip install -e` reports success (runs setup.py develop) but never lands on sys.path # in this image — the very next line still gets "No module named 'unitree_sdk2py'". # A copy is deterministic and keeps every subdirectory, including the ones that resolve as # implicit namespace packages. numpy comes from apt; opencv-python (its other declared # dependency, ~100MB+) is skipped because nothing in the SDK imports cv2 — verified. # The destination is derived from the freshly-installed cyclonedds rather than from # sysconfig: on Debian/Ubuntu sysconfig's purelib is /usr/lib/python3.8/site-packages, # which is not even on sys.path (it is dist-packages here), so the copy landed nowhere. RUN if [ -d /opt/unitree_sdk2_python/unitree_sdk2py ]; then \ apt-get update && apt-get install -y --no-install-recommends \ python3-pip python3-dev python3-numpy && \ pip3 install --no-cache-dir "cyclonedds==${CYCLONEDDS_PY}" && \ cp -r /opt/unitree_sdk2_python/unitree_sdk2py \ "$(python3 -c 'import cyclonedds,os;print(os.path.dirname(os.path.dirname(cyclonedds.__file__)))')/" && \ python3 -c "import unitree_sdk2py, cyclonedds; print('python DDS ok')" && \ rm -rf /var/lib/apt/lists/* ; \ else echo "no unitree_sdk2_python in context — record/replay disabled"; fi ENV RECORDER_DIR=/opt/recorder RECORDER_PY=python3 # the service sources (everything except web/), then build the binaries in-image COPY app/ /opt/hand/ WORKDIR /opt/hand RUN chmod +x build.sh docker/start.sh manage.sh 2>/dev/null; \ UNITREE_SDK2_DIR=/opt/unitree_sdk2 ./build.sh # The dashboard goes in LAST, on purpose: it is the part that changes every other day, and # from here a redeploy is a seconds-long layer copy instead of a full C++ recompile. COPY app_web/ /opt/hand/web/ ENV HAND_REPO=/opt/hand EXPOSE 8088 CMD ["/opt/hand/docker/start.sh"]