<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: liershui2500</title>
    <description>The latest articles on DEV Community by liershui2500 (@liershui2500).</description>
    <link>https://dev.to/liershui2500</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4109401%2Feca166ca-076c-43db-851c-6a38d3d3a23d.jpg</url>
      <title>DEV Community: liershui2500</title>
      <link>https://dev.to/liershui2500</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/liershui2500"/>
    <language>en</language>
    <item>
      <title>Building a Web Development Workbench for Unitree G1 with C++17, SDK2 and WebSockets</title>
      <dc:creator>liershui2500</dc:creator>
      <pubDate>Fri, 04 Sep 2026 09:10:03 +0000</pubDate>
      <link>https://dev.to/liershui2500/building-a-web-development-workbench-for-unitree-g1-with-c17-sdk2-and-websockets-3og1</link>
      <guid>https://dev.to/liershui2500/building-a-web-development-workbench-for-unitree-g1-with-c17-sdk2-and-websockets-3og1</guid>
      <description>&lt;p&gt;I’ve been building &lt;strong&gt;UniRoboGui&lt;/strong&gt;, an open-source browser-based development and debugging workbench for the Unitree G1 EDU.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/ershui2500/UniRoboGui" rel="noopener noreferrer"&gt;https://github.com/ershui2500/UniRoboGui&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The main idea is simple: keep the robot-facing stack in C++17 on the G1 PC2, communicate directly through Unitree SDK2 DDS, and expose a browser interface through HTTP and WebSocket.&lt;/p&gt;

&lt;p&gt;That gives me one place to inspect telemetry, 29DoF joints, URDF, point clouds, SLAM, navigation, RealSense, joint-debugging state and voice/LLM workflows without creating a separate desktop tool for each subsystem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzx16v0vi1t5cwayk472s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzx16v0vi1t5cwayk472s.jpg" alt="UniRoboGui Unitree G1 web development workbench" width="799" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;A robotics SDK can expose every API you need and still leave a lot of integration work to the application developer.&lt;/p&gt;

&lt;p&gt;While working with the G1, I repeatedly needed small tools for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checking fresh LowState data&lt;/li&gt;
&lt;li&gt;inspecting BMS, IMU, FSM and odometry&lt;/li&gt;
&lt;li&gt;visualizing all 29 joints&lt;/li&gt;
&lt;li&gt;checking Livox Mid-360 PointCloud2 data&lt;/li&gt;
&lt;li&gt;watching SLAM mapping progress&lt;/li&gt;
&lt;li&gt;setting localization/navigation targets&lt;/li&gt;
&lt;li&gt;checking RealSense RGB and depth&lt;/li&gt;
&lt;li&gt;debugging joint control conditions&lt;/li&gt;
&lt;li&gt;recording upper-body motions&lt;/li&gt;
&lt;li&gt;testing ASR/TTS&lt;/li&gt;
&lt;li&gt;connecting an LLM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing a one-off tool for each problem works until those “one-off” tools become part of your daily workflow.&lt;/p&gt;

&lt;p&gt;So I started consolidating them.&lt;/p&gt;

&lt;h2&gt;
  
  
  High-level architecture
&lt;/h2&gt;

&lt;p&gt;The architecture intentionally stays small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  |
  | HTTP / WebSocket
  v
C++17 web server
Boost.Asio + Boost.Beast
  |
  | Unitree SDK2 DDS
  v
Unitree G1 EDU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eth0  -&amp;gt; SDK2 DDS
wlan0 -&amp;gt; browser / Internet
8080  -&amp;gt; UniRoboGui web service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did not add a ROS bridge just to support the browser UI. The robot-facing communication path remains SDK2 DDS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend services
&lt;/h2&gt;

&lt;p&gt;The executable composes a few focused services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UnitreeDataSource
SnapshotStore
ControlService
PerceptionService
CameraService
VoiceService
HttpServer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation lives mostly in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/unitree_data_source.cpp
src/snapshot_store.cpp
src/json_serializer.cpp
src/http_server.cpp
src/control_service.cpp
src/perception_service.cpp
src/camera_service.cpp
src/voice_service.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not a complicated framework. The separation mainly keeps DDS callbacks, robot state, HTTP handlers, perception and control from turning into one large file.&lt;/p&gt;

&lt;h2&gt;
  
  
  SDK2 telemetry
&lt;/h2&gt;

&lt;p&gt;The backend subscribes directly to SDK2 DDS topics including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rt/lf/lowstate
rt/lf/bmsstate
rt/lf/secondary_imu
rt/lf/mainboardstate
rt/odommodestate
rt/sportmodestate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Callbacks update a shared snapshot instead of exposing SDK2 message types directly to the frontend.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DDS subscribers
      |
      v
SnapshotStore
      |
      +------&amp;gt; HTTP snapshot/status endpoints
      |
      +------&amp;gt; WebSocket telemetry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the browser a stable application-level representation of the current robot state.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebSocket telemetry at 10 Hz
&lt;/h2&gt;

&lt;p&gt;General robot telemetry is pushed through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ws/telemetry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default rate is 10 Hz.&lt;/p&gt;

&lt;p&gt;The WebSocket session is implemented with Boost.Beast. After each snapshot is written, a timer schedules the next write.&lt;/p&gt;

&lt;p&gt;The main telemetry stream is used for lightweight state such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DDS status&lt;/li&gt;
&lt;li&gt;FSM&lt;/li&gt;
&lt;li&gt;battery state&lt;/li&gt;
&lt;li&gt;IMUs&lt;/li&gt;
&lt;li&gt;odometry&lt;/li&gt;
&lt;li&gt;joint state&lt;/li&gt;
&lt;li&gt;control status&lt;/li&gt;
&lt;li&gt;voice status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Large perception data is fetched separately instead of being forced into every telemetry message.&lt;/p&gt;

&lt;h2&gt;
  
  
  29DoF URDF visualization
&lt;/h2&gt;

&lt;p&gt;The frontend uses Three.js and URDF Loader to render the G1.&lt;/p&gt;

&lt;p&gt;Live joint data follows this path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LowState
   |
   v
Snapshot JSON
   |
   v
WebSocket
   |
   v
29 joint mapping
   |
   v
Three.js URDF model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjg89f1grslgf8n8xh68.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjg89f1grslgf8n8xh68.jpg" alt="Unitree G1 live status and 29DoF URDF" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I keep both the 3D view and a numeric joint table.&lt;/p&gt;

&lt;p&gt;The 3D model is great for spotting posture/mapping problems. The table is still necessary for exact values like temperature, torque and velocity.&lt;/p&gt;

&lt;h2&gt;
  
  
  PointCloud2 processing for the browser
&lt;/h2&gt;

&lt;p&gt;Sending a full raw point cloud to a browser is not always a great idea.&lt;/p&gt;

&lt;p&gt;The perception service can decode PointCloud2 and apply web-oriented filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PointCloud2
    |
    v
field decoding
    |
    v
range crop
    |
    v
height crop
    |
    v
voxel filtering
    |
    v
optional isolated-voxel removal
    |
    v
maximum point count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The web representation is intentionally small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;PointSample&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose is not to reproduce every feature of RViz in a browser. It is to make the perception chain easy to inspect from the same development interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accumulated SLAM map
&lt;/h2&gt;

&lt;p&gt;During mapping, the backend also maintains an accumulated global map.&lt;/p&gt;

&lt;p&gt;Instead of only rendering the latest LiDAR frame, the UI can show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current point cloud
+
accumulated map
+
robot pose
+
trajectory
+
navigation target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A sequence number lets the frontend know when the global map changed and should be fetched again.&lt;/p&gt;

&lt;p&gt;For obstacles, I currently prefer a semi-transparent 2.5D voxel representation over aggressive contour smoothing.&lt;/p&gt;

&lt;p&gt;That choice came from a debugging concern: a visualization should not hide a short wall or small obstacle just because removing it makes the map look cleaner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigation as a state machine
&lt;/h2&gt;

&lt;p&gt;A navigation UI is not just a “send target” button.&lt;/p&gt;

&lt;p&gt;The workflow needs to represent states such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;idle
mapping
localizing
navigating
paused
cancelled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current interface supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;selecting an initial pose&lt;/li&gt;
&lt;li&gt;selecting a goal and heading&lt;/li&gt;
&lt;li&gt;single-goal navigation&lt;/li&gt;
&lt;li&gt;multi-goal navigation&lt;/li&gt;
&lt;li&gt;pause&lt;/li&gt;
&lt;li&gt;resume&lt;/li&gt;
&lt;li&gt;cancel&lt;/li&gt;
&lt;li&gt;saved local navigation tasks&lt;/li&gt;
&lt;li&gt;exiting the current map&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend owns the robot-side transitions; the frontend reflects the current state and available actions.&lt;/p&gt;

&lt;p&gt;Real navigation is treated as an explicitly enabled physical capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  RealSense D435i without assuming device numbers
&lt;/h2&gt;

&lt;p&gt;The camera service supports both librealsense2 and V4L2.&lt;/p&gt;

&lt;p&gt;One lesson from physical robot deployment is that this is fragile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RGB   = /dev/video0
Depth = /dev/video2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;USB device numbers can change after re-enumeration.&lt;/p&gt;

&lt;p&gt;So the service can scan the current V4L2 devices and use capabilities/pixel formats to distinguish RGB from Z16 depth input.&lt;/p&gt;

&lt;p&gt;Manual device paths still exist as overrides.&lt;/p&gt;

&lt;p&gt;The service also detects stale frames instead of indefinitely serving the last successful frame as though the camera were still online.&lt;/p&gt;

&lt;h2&gt;
  
  
  Joint debugging: backend checks matter more than UI controls
&lt;/h2&gt;

&lt;p&gt;The joint-debug page supports upper-body and full-body workflows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4f1uya8e3key1qaqdj35.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4f1uya8e3key1qaqdj35.jpg" alt="Unitree G1 joint debugging and motion teaching" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before a real command is accepted, the backend can verify conditions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fresh LowState&lt;/li&gt;
&lt;li&gt;allowed FSM state&lt;/li&gt;
&lt;li&gt;control/DDS readiness&lt;/li&gt;
&lt;li&gt;URDF joint limits&lt;/li&gt;
&lt;li&gt;browser control lease / heartbeat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A disabled button is useful UX, but it is not a safety boundary.&lt;/p&gt;

&lt;p&gt;The backend still rejects invalid operations independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  20 Hz kinesthetic motion recording
&lt;/h2&gt;

&lt;p&gt;There is also an upper-body hand-guided teaching workflow.&lt;/p&gt;

&lt;p&gt;At a high level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start recording
      |
      v
manually guide joints
      |
      v
sample LowState at 20 Hz
      |
      v
save trajectory
      |
      v
play it back later
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A saved action can either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;release control after playback, or&lt;/li&gt;
&lt;li&gt;hold the final pose after playback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local actions can also be mapped to reserved G1 controller button combinations.&lt;/p&gt;

&lt;p&gt;This makes simple interactive/demo motions much faster to create than manually authoring every trajectory point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice and LLM integration
&lt;/h2&gt;

&lt;p&gt;The voice service currently handles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ASR
Unitree native TTS
local Kokoro TTS
built-in G1 conversation path
customer OpenAI-compatible LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The customer LLM mode supports configuration such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API URL
model
role prompt
fixed Q&amp;amp;A entries
wake phrase
TTS backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API URL is normalized to a Chat Completions endpoint, and the API key is not sent back as normal plaintext telemetry.&lt;/p&gt;

&lt;p&gt;The main reason I used an OpenAI-compatible interface is portability. I do not want the rest of the robot application to depend on a single model provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Kokoro TTS
&lt;/h2&gt;

&lt;p&gt;The customer LLM can optionally feed a local Kokoro TTS service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM response
    |
    v
local Kokoro HTTP TTS
    |
    v
16 kHz PCM
    |
    v
Unitree AudioClient
    |
    v
G1 speaker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The robot can therefore use a remote language model while keeping speech synthesis local.&lt;/p&gt;

&lt;p&gt;Unitree’s native TTS path remains available as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP API
&lt;/h2&gt;

&lt;p&gt;The built-in web UI is only one possible client.&lt;/p&gt;

&lt;p&gt;Some of the current routes are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET  /api/health
GET  /api/snapshot
WS   /ws/telemetry

GET  /api/control/status
POST /api/control/command
POST /api/control/velocity

GET  /api/perception/status
GET  /api/perception/frame
GET  /api/perception/global-map
POST /api/perception/command

GET  /api/camera/status
POST /api/camera/command

GET  /api/voice/status
POST /api/voice/tts
POST /api/voice/llm/chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes it possible to build another tablet, Electron or custom application UI on top of the same robot-side service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mock mode
&lt;/h2&gt;

&lt;p&gt;The server supports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--mock&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In mock mode it does not initialize real DDS.&lt;/p&gt;

&lt;p&gt;The mock data source continuously updates simulated robot state, which is enough to exercise large parts of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the web UI&lt;/li&gt;
&lt;li&gt;HTTP endpoints&lt;/li&gt;
&lt;li&gt;WebSocket telemetry&lt;/li&gt;
&lt;li&gt;perception UI&lt;/li&gt;
&lt;li&gt;camera UI&lt;/li&gt;
&lt;li&gt;control-state behavior&lt;/li&gt;
&lt;li&gt;voice UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not meant to replace a simulator.&lt;/p&gt;

&lt;p&gt;It exists so that a CSS change or frontend state-machine regression does not require a real humanoid robot to move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment: online and from another PC
&lt;/h2&gt;

&lt;p&gt;If the G1 can reach GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ershui2500/UniRoboGui.git /home/unitree/UniRoboGui
&lt;span class="nb"&gt;cd&lt;/span&gt; /home/unitree/UniRoboGui
bash scripts/deploy_g1_online.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the robot itself cannot reliably reach GitHub/PyPI, an Internet-connected Linux PC can prepare the resources and deploy over SSH/rsync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ershui2500/UniRoboGui.git
&lt;span class="nb"&gt;cd &lt;/span&gt;UniRoboGui
bash scripts/deploy_g1_from_pc.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Robots often live on much less convenient networks than developer laptops, so I wanted both workflows to be first-class rather than treating offline-ish deployment as an edge case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current target environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Robot:    Unitree G1 EDU
DoF:      29DoF body preferred
PC2:      Ubuntu 20.04 AArch64
SDK:      Unitree SDK2
LiDAR:    Livox Mid-360 / Mid360s
Camera:   Intel RealSense D435i
Browser:  Chromium / Chrome / Edge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firmware and hardware combinations vary, so compatibility should always be checked against the actual robot rather than assumed from an old test environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/ershui2500/UniRoboGui" rel="noopener noreferrer"&gt;https://github.com/ershui2500/UniRoboGui&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Issues:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/ershui2500/UniRoboGui/issues" rel="noopener noreferrer"&gt;https://github.com/ershui2500/UniRoboGui/issues&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you work with Unitree G1 hardware, I’d be especially interested in feedback about different firmware/hardware combinations and the debugging workflows you still find yourself rebuilding.&lt;/p&gt;

&lt;p&gt;UniRoboGui is an independent third-party project, not an official Unitree product.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Safety note:&lt;/strong&gt; walking, navigation, joint control, kinesthetic teaching and motion playback can cause real physical movement. The current web UI also has no authentication layer, so its control port should not be exposed directly to an untrusted network or the public Internet.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>robotics</category>
      <category>slam</category>
      <category>unitree</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
