Skip to main content

wuji-macos

Experimental Apple Silicon bindings for WUJI Hand 2, targeting the public wuji_sdk Python API. Downloads, derived binaries, environments, SDK state, and test output stay in this repository.

Status

The wheel now provides import wuji_sdk, the Hand 2 Python classes and resource interfaces, native SDK manager initialization, discovery, connection handling, and SDK user selection. It is not yet a complete SDK replacement. A Python method existing is not evidence that its native operation works.

Layer Evidence and remaining limits
Python API Hand 2 example imports and callback/async/publisher ownership tested with an explicitly injected test backend. Full SDK inventory still has missing classes and methods.
Native manager Real initialization, discovery, native connection errors, disconnect-all, user CRUD/selection, and tactile storage-path lookup tested. Successful native handshake and disconnect proven against a localhost UDP protocol fixture.
Native resource operations Installed Python API reads and writes a fixture resource over the actual native transport. Device info and node/resource metadata are exercised end to end. Fingertip sensor metadata is unavailable: SDK 2026.9.22 removed WujiHand2::get_fingertip_info.
Payloads Native header codec; native command/MIT/effort/control-mode and read-frame fixtures; strict Python Postcard codec. Unsupported write layouts are rejected.
Streaming A native physical realtime stream feeds the SDK's actual Hand 2 virtual joint-state pipeline, then reaches Python recv_async() with verified fields. Rate dispatch is implemented; successful rate negotiation still needs a capable fixture or device.
Commands Publishing calls the SDK's own Device::publish::<JointCommandFrame> and JointCommandPublisher, which own UDP negotiation, serialization, sequence counters and cancellation. Exact wire bytes pass a localhost UDP fixture and a real Zenoh peer, including all 101 commands from the original MIT sweep. Hand 2 masks, gains, origin and tactile dispatch are checked against pinned native behavior. Motor control was exercised on a physical hand with the 2026.8.31 build; the 2026.9.22 build has been checked live read-only (connect, 1 kHz joint states, publisher open/close).
Other SDK features Glove, Hand 1, recording, offline pipelines, and retargeting do not yet have complete macOS bindings.

The exact Python surface is recorded in the runtime coverage report, with per-method information in its JSON companion. Missing native dispatch raises NativeCapabilityError; the runtime never substitutes simulated devices.

Live validation against a physical Hand 2 over Zenoh is summarized in the Commands row above. The current machine is macOS 26.5.2 with ARM64 Python 3.14.4. macOS 11.0 is the binary deployment target; older macOS and other Python versions are not run-tested.

Current API limits include flash-log export, device log configuration and parameter persistence, user-data ZIP operations, global topics, and the other-device features listed above. Native errors are reported as WujiException; the specialized NodeOfflineError mapping is not complete. Publishers follow upstream behavior: publishers on one stream are reference-counted, sends are queued rather than awaited on the wire, and cancellation runs in the background, so close() always releases the handle.

Joint feed-forward compensation (SDK 2026.9.22, firmware 2.10.0+) is bound as upstream: FeedForwardMode, hand.set_constant(mode, joints=None), hand.set_position_table(on, joints=None), and per joint hand.joint(k).constant().set(mode) / .position_table().set(on). Like the Linux C SDK, each joint gets a raw SET of a little-endian u32 on {finger}/S{n}/od/action_set_constant or od/action_set_position_table, in index order, stopping at the first failure. The firmware applies it in RAM and does not support reading it back. Firmware 2.10.0 accepted both on a live hand.

Build and verify

Requires native ARM64 macOS, Python 3.11+ for build scripts, and Apple's Xcode command-line tools. The resulting package requires Python 3.10+.

python3 -m venv .venv
.venv/bin/python -m pip install --cache-dir .cache/pip -r requirements-build.txt
.venv/bin/python scripts/build_macos.py
.venv/bin/python scripts/verify_wheel.py

The wheel is written to dist/wuji_macos-0.1.0-py3-none-macosx_11_0_arm64.whl. The verifier installs that artifact into .venv-verify/, runs an offline suite with network and IOKit access denied, then runs a separate native protocol suite with only localhost network access. Both restrict filesystem writes to this repository. Results are saved to build/verification.json. The Zenoh fixture uses a pinned eclipse-zenoh wheel installed only in the verification environment. These synthetic peers are test code; the installed SDK uses the native transport and does not require the Python Zenoh package.

This distribution is named wuji-macos, but its SDK import is wuji_sdk. Use a fresh environment; do not install it alongside the Linux wuji-sdk distribution, which owns the same import namespace.

Python examples

The upstream Python examples are copied into examples/python, including the unchanged Hand 2 replay data. examples/upstream.json pins their source commit and individual SHA-256 hashes and records the local opposition pacing fix. Its original source is retained in examples/upstream_sources/opposition.py. Their upstream README describes the API, not the current completion status of this macOS port.

For a first read-only connection test from this checkout:

.venv-verify/bin/python examples/macos/inspect_hand2.py

The inspection script prints device metadata and five joint-state frames. Use --address IP:50001 or --sn SERIAL to select a specific hand, and --frames 0 for metadata only. It disables device sharing for this inspection.

Read-only Hand 2 examples:

.venv-verify/bin/python examples/python/wuji_hand_2/0.subscribe_callback.py
.venv-verify/bin/python examples/python/wuji_hand_2/1.subscribe_async.py

Both originals have been run through actual native discovery under the offline sandbox and exit with No devices found. The connected subscription API also passes the local native protocol fixture. The original scripts have not been run on hardware. Example 7's unchanged command generator and send loop are exercised against a synthetic Zenoh peer, including all 101 command payloads. Examples 2, 6, and 7 enable motors; physical motion remains unvalidated. Example 5 requires flash-log export, which is still explicitly unavailable in this build.

Joint PD controller

examples/macos/pd_controller.py is a minimal joint PD controller with an aiofranka/aioflexiv-style API (WujiController, start/stop, set_freq, set("q_desired", q), move). The PD law runs in the hand's firmware (MIT mode): the script sets kp/kd/effort limit once and a 1 kHz thread streams position, velocity-feedforward, and effort targets as one native float32[60], reading joint_states as raw bytes decoded with struct. The loop disables the hand on stale feedback, a missing or faulted joint, or excessive tracking error; reported joint velocity is not used for safety because it can be nonzero while the joint is stationary.

Without --confirm-motion it is a dry run that connects and runs the loop without changing gains, enabling, or sending:

python examples/macos/pd_controller.py SERIAL

This enables and moves the hand. Move joint 16 by 5 degrees and back, then hold for 3 seconds with the vendor example gains and a 0.5 A limit:

python examples/macos/pd_controller.py SERIAL --confirm-motion SERIAL \
  --joint 16 --delta-deg 5 --kp 3 --kd 0.05 --effort-limit 0.5

Opposition replay timing

The vendor opposition example slept 1 ms after constructing and sending each command. The macOS copy now uses a 1 kHz monotonic deadline schedule and prints its measured cadence after cleanup. Run the usual command:

python examples/python/wuji_hand_2/6_opposition/opposition.py

The scheduler preserves every recorded position and the original zero velocity/effort commands. After a full-period overrun it shifts the schedule instead of sending a catch-up burst. macOS scheduling is still best effort; inspect the measured interval tails.

For a short 5,000-frame capture with detailed timestamps, run python examples/macos/profile_opposition.py --frames 5000. It uses the fixed loop and writes build/opposition-timing/live-sender.json. --mode original explicitly selects the saved vendor timing baseline for comparisons. The diagnostic-only option --spin-us 100 uses additional CPU for the final wait; the normal script does not busy-wait.

For a connected communication-health capture that does not move the hand, run:

python scripts/benchmark_hand2_connected.py --duration-s 10

The read-only default records host delivery cadence, device timestamp cadence, telemetry sequence gaps/duplicates/reordering, native receiver lag, joint and SDK communication counters, and baseline/final internal finger-bus diagnostics. Use --telemetry both to correlate state and diagnostic projections from each device frame. It preserves the current device stream rate. To explicitly request and record a 1 kHz telemetry rate for the run, add --request-telemetry-hz 1000; the script restores the device default during cleanup.

The same tool can instrument the opposition command path, but this mode enables and moves the hand. It requires the exact discovered serial twice:

python scripts/benchmark_hand2_connected.py \
  --mode opposition \
  --sn SERIAL \
  --confirm-motion SERIAL \
  --request-telemetry-hz 1000

With --frames omitted, active mode replays the complete recording like the ordinary opposition example; add --frames 5000 for a shorter run. Active mode requires the explicit 1 kHz telemetry request for both watchdog streams and verifies their warmup cadence before enabling. It then requires fresh 20-joint state and diagnostics, checks the documented sparse-NID mapping, joint ranges, replay step size, faults, and distance to the first target. During motion it checks every unseen diagnostic frame and stops on a sequence gap, stale feedback, a missing/disabled joint, a new motor communication timeout, a low response rate, excessive tracking error, a limit, or a fault. Cleanup is only reported successful after a new diagnostic frame confirms that no motor remains enabled; an unconfirmed normal disable escalates to the device emergency stop.

For the small all-joint control benchmark, use sine mode. This enables and moves every motor. The default trajectory is a 5-degree peak (10-degree peak-to-peak), 0.5 Hz sine wave centered on the hand's freshly measured pose:

python scripts/benchmark_hand2_connected.py \
  --mode sine \
  --sn SERIAL \
  --confirm-motion SERIAL \
  --request-telemetry-hz 1000

The run first requires 10 clean seconds from both 1 kHz telemetry streams. It then snapshots all 20 joint positions, verifies at least 10 degrees of room to each documented limit, and generates 10,001 targets over exactly 10,000 one-ms intervals. A two-second raised-cosine ramp at each end makes the command begin and finish at the measured pose. All joints receive the same offset in phase. For this first-debug test the script temporarily applies kp=3, kd=0.05, and a 0.5 A effort limit, verifies all 20 readbacks, and restores the original values only after fresh telemetry confirms the motors are disabled.

Sine mode uses absolute 1 ms deadlines with bounded catch-up: safety checks and target construction consume the time before each deadline, and no two sends may start less than 0.8 ms apart. It aborts before a send that would be at least 1 ms late. It also stops for a send call lasting at least 1 ms, a 20 ms feedback stall, any sequence or communication-counter anomaly, a joint response rate below 80%, more than 2 degrees of tracking error, more than 7 degrees of measured excursion from center, or speed above 0.55 rad/s. Keep the hand secured, the entire pinch zone clear, cables slack, and an independent power disconnect within reach. Python cyclic garbage collection is disabled during this short active capture and restored in cleanup; reference counting remains active. This choice and the before/after GC state are recorded because cyclic-GC pauses can batch otherwise lossless 1 kHz telemetry on macOS. Use --disable-cyclic-gc with read-only mode to measure the same tuned host condition without enabling motors.

Each run writes session.json, summary.json, commands.csv, telemetry.csv, and comm_diag.jsonl under build/connected-timing/<UTC timestamp>/. Publisher-return timing and healthy feedback establish host/SDK behavior, not firmware receipt or application of each command; the current device API exposes no command acknowledgement. State and diagnostic rows with the same sequence and device timestamp are correlated views of one device frame, not independent confirmation of two links.

For a separate passive observer, run:

.venv-verify/bin/python examples/macos/monitor_joint_commands.py

It subscribes to the command topic without enabling or commanding the hand. Its timestamps include observer and network delay and are not firmware arrival timestamps.

Run a hardware-free comparison of the same replay workload through the actual native Zenoh publisher with a separate localhost receiver:

.venv-verify/bin/python scripts/benchmark_opposition_timing.py --samples 5000

The command adapter caches the fixed generated field annotations rather than resolving them again for every joint command. Validation and float32 conversion remain in place. Measurements and limits are described in the timing investigation.

SDK storage defaults to .state/ when run from this checkout. Outside the checkout, set WUJI_MACOS_STATE_DIR to an explicit storage directory:

export WUJI_MACOS_STATE_DIR="$PWD/.state"

The bridge redirects only the reused SDK image's home/log-path lookup. It does not change the process HOME, global Python installation, or shell configuration. Imports alone do not initialize the SDK manager or open hardware.

How this build works

The public SDK repository distributes examples and binary releases, without its Rust implementation/build manifests or a PyPI source distribution. The Mac CLI already contains many compiled SDK components. This project converts a copy of a checksum-pinned CLI executable into a loadable Mach-O library, then compiles small C/ARM64 adapters for recovered Rust calling conventions. It is binary reuse, not a source build of the vendor's PyO3 extension.

Every installed native library is checksum-verified before loading. Rust containers are handled and freed in the native bridge; Python sees a checked C ABI. The original executable is unchanged. Updating the pinned CLI requires reviewing the ABI again. The CLI does not retain every Python SDK call path, so broad API parity cannot be assumed from the presence of its Rust core.

Layout

  • artifacts.lock.json: upstream artifact versions, URLs, and SHA-256 hashes.
  • scripts/: fetching, Mach-O conversion, binding generation, build, verification, and native probes.
  • native/: manager, device, codec, and Rust formatting adapters.
  • src/wuji_sdk/: public Python interface, value types, codecs, and native dispatch.
  • src/wuji_macos/: native-library integrity/provenance and offline header probe.
  • examples/: unchanged upstream scripts/data and their provenance.
  • tests/: native tests, payload fixtures, and explicitly isolated Python contract tests.
  • research/: source availability, ABI evidence, and API coverage.
  • .artifacts/, .cache/, .venv*/, .state/, build/, dist/: ignored local output.

See native reuse, ABI investigation, and source availability.

Upstream: Mac CLI release, SDK repository.

Metadata

Release files for wuji-macos 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for wuji-macos 0.1.0
File Interpreter ABI Platform Actions
wuji_macos-0.1.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for wuji_macos-0.1.0-py3-none-macosx_11_0_arm64.whl

Release files / wuji_macos-0.1.0-py3-none-macosx_11_0_arm64.whl

Download URL wuji_macos-0.1.0-py3-none-macosx_11_0_arm64.whl
Size 31.4 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
450cf5baf866cd57438ef58f18f17ee6b82ab64344136eed2acfd7572fd3c399
BLAKE2b-256 checksum
How to use checksums
24a1c09ddc57f9b37a7aef959daafdf1e646ab1a7752ffab6b0f945dd5674ec7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Security for wuji-macos 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

1 file in this release has been uploaded with provenance metadata.

Attestation for wuji_macos-0.1.0-py3-none-macosx_11_0_arm64.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

1 release file Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page