Local Bridge
SO-101 hardware is never controlled directly by the browser. The frontend talks to a local bridge bound to 127.0.0.1:8765 by default.
For the GitHub Pages flow, see GitHub Pages to localhost guide. For the authoritative native-Windows matrix and end-to-end procedure, see:
Native-Windows setup
Use native 64-bit Windows with CPython 3.12.10 x64 and Git for Windows. Do not reuse a parent-project environment. The setup script resolves from its own path and owns only SolU\.venv-so101.
Set-Location E:\Projects\RoboBuddy_AI\SolU
Set-ExecutionPolicy -Scope Process Bypass
.\tools\setup-so101-windows.ps1
For a suspect or partial local environment:
.\tools\setup-so101-windows.ps1 -ForceRepair
The setup installs the exact dependency boundary from bridge\pyproject.toml, verifies editable bridge provenance, imports the SO-101 adapter without serial access, runs the phone protocol probe, and constructs the selected NumPy kinematics backend. It does not enumerate/open serial ports, start listeners, or send motion.
Find and configure/calibrate the actual SO-101 only under the existing hardware procedure:
.\.venv-so101\Scripts\lerobot-find-port.exe
.\.venv-so101\Scripts\lerobot-setup-motors.exe --robot.type=so101_follower --robot.port=COM24
.\.venv-so101\Scripts\lerobot-calibrate.exe --robot.type=so101_follower --robot.port=COM24 --robot.id=classroom_so101_01
These commands can change hardware state. Do not run them as part of unattended software verification.
Default-disabled bridge
Start or restart the token-protected loopback bridge with all hardware Leader features disabled:
.\tools\start-so101-bridge.ps1 -Restart
The helper prints a local bridge token. Enter it only in the main-page Diagnostics panel. Do not store it in source, screenshots, or support exports.
Stop only the exactly matched SolU bridge process:
.\tools\start-so101-bridge.ps1 -Stop
The launcher refuses non-loopback bridge API binding, an external Python/bridge path, unrelated listener ownership, feature changes without -Restart, or hardware Leader enablement with -NoToken. run-app.bat remains a safe development launcher and does not enable physical control.
A read-only connection-path check is available only when a real intended robot is present and the operator has authorized opening its COM port:
.\tools\start-so101-bridge.ps1 -ConnectCheck -PortName COM24 -RobotInstanceId classroom_so101_01
Android private-LAN HTTPS
Select an assigned/up RFC1918 address, install mkcert 1.4.4 explicitly, and create runtime-only material:
$PrivateIPv4 = '192.168.1.25' # replace with the intended assigned address
.\tools\setup-phone-leader-tls.ps1 `
-PrivateIPv4 $PrivateIPv4 `
-Port 8443 `
-AddPrivateFirewallRule
The generated certificate/key files remain under ignored .runtime-private\phone-leader-tls. Transfer only android-rootCA.pem to Android and install it as a CA certificate. Never transfer phone-leader-key.pem or mkcert's rootCA-key.pem. Before QR pairing, Android Chrome must open https://<private-ip>:8443/ without a certificate warning and expose a secure context/WebXR capability.
The checked-in Phone Leader configuration is intentionally provisional, disabled, and unqualified. Therefore this enablement shape must fail closed until reviewed physical evidence supplies non-provisional limits and qualification:
The intended qualified launcher begins .\tools\start-so101-bridge.ps1 -Restart -EnablePhoneLeader and must still reject startup unless /health confirms phoneLeaderQualified: true.
$TlsRoot = Join-Path $PWD '.runtime-private\phone-leader-tls'
.\tools\start-so101-bridge.ps1 `
-Restart `
-EnablePhoneLeader `
-PhoneHttpsHost $PrivateIPv4 `
-PhoneHttpsPort 8443 `
-PhoneHttpsCertificate (Join-Path $TlsRoot 'phone-leader-cert.pem') `
-PhoneHttpsPrivateKey (Join-Path $TlsRoot 'phone-leader-key.pem')
Do not edit the gates merely to make the command start. Follow Windows verification procedure and the existing Phone Leader qualification documents.
Rollback to the default-disabled mode with .\tools\start-so101-bridge.ps1 -Restart -DisablePhoneLeader.
Cleanup:
.\tools\remove-phone-leader-tls.ps1 -Port 8443
Authentication notes
Generic startup from the selected environment is possible with python -m robobuddy_bridge, but the PowerShell helper is the authoritative Windows launcher because it verifies provenance, listener ownership, mode postconditions, and failure cleanup.
For local development only, --no-token/-NoToken may be used while all hardware Leader features are disabled. Never reuse cloud/API secrets as a bridge token.
The browser's No-token local checkbox only stops the page from sending an Authorization token; it cannot override a bridge process that is still requiring one. Restart the bridge with --no-token/-NoToken, or uncheck the box and enter the bridge's printed local token.
Endpoints:
GET /healthGET /portsGET /robotsGET /robots/{robot_id}/statePOST /robots/{robot_id}/connectPOST /robots/{robot_id}/disconnectPOST /robots/{robot_id}/armPOST /robots/{robot_id}/disarmPOST /robots/{robot_id}/homePOST /robots/{robot_id}/stopPOST /robots/{robot_id}/executePOST /robots/{robot_id}/manual-targetPOST /robots/{robot_id}/manual-cancel
The bridge accepts only validated RoboBuddy command JSON. It does not accept arbitrary Python from the frontend and does not launch interactive calibration from HTTP requests.
Statuses include READY, DISCONNECTED, CONNECTED, NEEDS_CALIBRATION, EXECUTING, STOPPED, ERROR, and LEROBOT_NOT_INSTALLED.
NEEDS_CALIBRATION includes the exact lerobot-calibrate command shape to run in PowerShell. The bridge releases the serial port in this state, and browser motion remains blocked until LeRobot reports calibration.
Every connection starts disarmed. POST /arm succeeds only while connected with matching calibration and verified Feetech position-limit registers. Home, program execution, and manual targets are rejected while disarmed. STOP, disconnect, calibration loss, and terminal bridge errors clear pending targets and require an explicit new arm request. A successful re-arm is state-only: it clears stale failed-operation state but performs no serial read, hold, target replay, or motor write.
POST /manual-target accepts a session id, monotonically increasing sequence, joint, target value, and final flag. The bridge keeps only the latest target, schedules no more than one serial operation at a time, and caps scheduling at 30 Hz. A non-final target expires after 250 ms without an update; a higher sequence in the same active drag can resume after that watchdog hold. A final target remains eligible to converge. POST /manual-cancel identifies and retires the exact session, cancels it if active, and holds the latest measured pose. A late request from that retired session is ignored, while a late cancellation cannot stop a newer session.
Clipped manual convergence is feedback-paced. After an intermediate relative goal is accepted, the bridge does not resend an equivalent goal until a fresh observation shows enough movement to advance it. Same-joint browser updates preserve this observation schedule, so a slow or contact-blocked joint does not turn a 30 Hz input stream into redundant 30 Hz all-servo transactions.
GET /robots/{robot_id}/state returns cached control state, including authoritative armed, requested targetJoints, measured joints, translated jointLimits, limitStatus, manualControl, and Home compatibility. Browser polling therefore does not cause a new serial observation. The bridge samples measured positions at a controlled 5 Hz while moving and once after cancellation or final settlement.
SO-101 serial operations are single-flight and retry only transport-level ConnectionError failures. Each logical action or observation receives at most three attempts, separated by 20 ms and 50 ms backoffs. STOP, disarm, disconnect, and operation invalidation cancel any remaining ordinary-motion attempts; the explicit measured-position safety hold remains allowed. Other exception classes are not retried. One or two failed manual observation transactions do not disarm, while three consecutive failures report feedback_unavailable and require explicit re-arming.
The connect handshake is separate from motion retries. LeRobot is the only owner of the selected COM port. RoboBuddy retries only an exact missing-motor-ID handshake, using at most three fresh robot instances and closing each failed bus without torque-disable or configuration traffic. It does not retry wrong-model, port, semantic calibration, configuration, or disconnected-device errors as whole connections. A transport-level ConnectionError while reading calibration registers still receives the normal bounded serial retry. Connected status requires both verified limits and one successful fresh feedback read.
Measured-position holds are sent only after a fresh feedback read succeeds. If feedback is unavailable, cancellation still stops and disarms the operation without writing stale cached coordinates.
Manual targets and complete program batches are validated again at the adapter boundary. Out-of-range or non-finite values, unknown joints, and empty actions are rejected before operation ownership, targets, or serial traffic change. Validation rejection leaves arming intact; exhausted communication retries and motor/feedback faults still disarm.
An error saying no SO-101 motors responded after three connection attempts indicates persistent whole-bus packet loss. Check supply voltage and current under load, the USB adapter/cable, and serial-bus wiring and connectors. Keep retry limits and automatic disarming intact rather than masking a hardware fault.
The live joint limits are derived from Feetech Min_Position_Limit and Max_Position_Limit register values using the connected LeRobot calibration. They are runtime overrides only and are cleared when the port, bridge session, robot instance, or active robot changes. These bounds are applied to Manual Control, Home, saved poses, smooth/multi-joint commands, Blockly, and Python. They are servo position limits, not collision detection.
Explicit token value:
$env:ROBOBUDDY_BRIDGE_TOKEN="choose-a-local-token"
python -m robobuddy_bridge
Do not reuse cloud/API secrets.
Known limitation: the standard-library bridge returns HTTP 426 for the telemetry WebSocket placeholder. Manual streaming and telemetry use backpressured HTTP requests plus cached state polling; no WebSocket or fixed-cadence guarantee is required.