- Python 100%
| .github/workflows | ||
| docs | ||
| examples | ||
| src/a1xy_sdk | ||
| tests | ||
| .gitignore | ||
| PROVENANCE.md | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
A1XY SDK
Independent Python arm lifecycle, CAN FD codec and native XCAN USB transport for Galaxea A1X/A1Y. Extracted from a1xy-teleop, preserving source history and wire behavior. No ROS, web server, MuJoCo, camera or application configuration dependencies.
This implementation is unofficial. Existing hardware evidence covers A1X arms on macOS with MiniCANFD ISO V3.0 / XCAN USB FD adapters. A1Y, Linux USB and Windows USB behavior are unvalidated. The Windows ownership implementation has simulated software tests; it is not hardware support. See platform status and protocol provenance.
Install from source
Use Python 3.11 and uv. The repository is private; no PyPI release is assumed.
cd /path/to/a1xy-sdk
uv sync --extra usb
uv run --locked pytest -q
Or run python -m pip install './a1xy-sdk[usb]' from the parent directory.
The base install needs only NumPy; usb adds PyUSB and bundled libusb.
Receive-only diagnostics
One process owns each adapter. Arrange any existing owner's safe shutdown yourself before opening another connection. Opening configures the CAN adapter and acknowledges traffic; it sends no arm commands and enables no motors.
uv run python examples/read_state.py --list
uv run python examples/read_state.py --channel YOUR_ENUMERATED_CHANNEL --seconds 5
Use your enumerated topology, never a historical installation's USB path. Moving ports can change it.
--can-base-id 0x60 selects an explicitly configured secondary base; the protocol's primary base is
0x50. No two-arm topology, park pose or gripper endpoints are installed as defaults. The arm's
built-in USB port is not a verified control interface. Classic CAN cannot carry the 64-byte command.
The JSON reports measured angles, status words/labels, feedback frequency, position/status ages, frame counts, observed gaps and troubleshooting hints. Final unhealthy feedback/status gives exit code 1. Feedback and status use separate deadlines (100 ms by default and 2 s respectively). Rate uses at most 256 accepted samples from the last 2 s. Gaps count completed intervals exceeding the feedback deadline; they are not estimates of lost packets. Data age exposes ongoing silence.
from a1xy_sdk import Arm
arm = Arm(channel="YOUR_ENUMERATED_CHANNEL") # no USB access yet
try:
arm.connect() # requires fresh feedback within 1 s
state = arm.read_state(timeout=0.05) # copied q, dq, tau, stamp, issues, warnings
print(state.q, state.issues)
print(arm.diagnostics()) # snapshot; read_state consumes queued frames
finally:
arm.close() # receive-only close sends no motor commands
Offline command checks
Command has seven values per field: six arm joints, then gripper. Positions use native motor
radians. Effort retains protocol scaling; physical gripper effort/force units remain unverified.
Arm.validate_command(command) works without opening USB. It checks seven finite values per field
and supplied six-axis position limits, then returns the existing arm/gripper wire payloads. The
encoder retains its clipping behavior. Encoding bounds are not safe operating limits; this check
does not validate gains, trajectories, collision clearance or physical force.
uv run python examples/check_command.py --calibration /path/to/your-calibration.json --command /path/to/your-command.json
The calibration JSON supplies channel, measured park (7 radians), joint_lower and joint_upper
(6 radians each), with optional can_base_id and stale_after_ms. The command JSON supplies q
(7 radians), optionally dq, kp, kd and tau_ff (7 each). No calibration values are bundled.
This example validates and prints bytes only; it never opens an adapter or transmits anything.
Deliberate holding and release
Standalone Arm.enable(kp=..., kd=..., motion_confirmed=True) requires explicit measured park,
paired six-axis limits, seven-value gains and an already connected, commissioned arm. Both feedback
and status must be fresh, the arm must be stationary at its calibrated folded pose, and status must
permit holding. It sends the measured pose, never the calibration target, and keeps the jaws passive.
No motor mode is selected. Existing safeguards for noisy rest velocity and encoder quantization are
preserved from teleop; no new hardware calibration is inferred.
Arm.send(Command(...)) works only after explicit enablement. It validates all original fields before
forcing passive gripper position/gains/velocity/effort. It does not run a background control loop or
plan motion. The caller must supervise freshness, faults, trajectories and E-stop, and continuously
stream appropriate commands. Teleop supplies that controller and retains active gripper guards.
enabled stays true after a partial first write because the arm may already be holding. A write
exception must not be treated as evidence that motors are off. Faults and stale data are reported;
they never cause silent USB closure, automatic mode changes or automatic release. disable() and
close() refuse to release an enabled arm without fresh stationary feedback at measured park.
check_shutdown() requires motors already disabled. Do not put an unsupervised motion sequence in
try/finally and assume closing USB will safely stop an arm. The included examples send no motion.
Reconnect is passive and requires new feedback. A fresh, exact 0x12 is recognized only following a
locally requested release, matching prior behavior; it never permits automatic recovery or resumption.
Mode detection/switching, gravity semantics, physical gripper effort units and recovery after 0x12
remain blocked by current vendor SDK/firmware documentation.
Repository boundaries
a1xy-sdk: hardware lifecycle, measured state/health, command validation, codec and USB transport.a1xy-teleop: application calibration/configuration, active gripper policy, motion supervision, IK, gestures, simulation, cameras and web panel; depends on SDK 0.2.0 through the sibling checkout.lerobot-a1xyanda1xy-vlm-vla: deferred integration/research documents.
No package has been published and no open-source license is granted by this work.