Skip to content

Instantly share code, notes, and snippets.

@aphexcx
Created January 24, 2026 08:22
Show Gist options
  • Select an option

  • Save aphexcx/e4521fb10b00e32dca5b57b607ca3319 to your computer and use it in GitHub Desktop.

Select an option

Save aphexcx/e4521fb10b00e32dca5b57b607ca3319 to your computer and use it in GitHub Desktop.
M20 Relay Architecture Diagrams
Display the source blob
Display the rendered blob
Raw
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 500" fill="none">
<defs>
<linearGradient id="browserGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3B82F6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1D4ED8;stop-opacity:1" />
</linearGradient>
<linearGradient id="relayGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#10B981;stop-opacity:1" />
<stop offset="100%" style="stop-color:#059669;stop-opacity:1" />
</linearGradient>
<linearGradient id="ros2Grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#F59E0B;stop-opacity:1" />
<stop offset="100%" style="stop-color:#D97706;stop-opacity:1" />
</linearGradient>
<linearGradient id="hwGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#EF4444;stop-opacity:1" />
<stop offset="100%" style="stop-color:#DC2626;stop-opacity:1" />
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="2" dy="4" stdDeviation="4" flood-opacity="0.15"/>
</filter>
</defs>
<!-- Title -->
<text x="450" y="35" text-anchor="middle" font-family="system-ui, sans-serif" font-size="24" font-weight="bold" fill="#1F2937">M20 Relay Architecture</text>
<text x="450" y="55" text-anchor="middle" font-family="system-ui, sans-serif" font-size="14" fill="#6B7280">Browser-to-Dog Communication</text>
<!-- Browser Box -->
<g filter="url(#shadow)">
<rect x="40" y="120" width="180" height="140" rx="12" fill="url(#browserGrad)"/>
<text x="130" y="155" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" fill="white">Browser</text>
<text x="130" y="175" text-anchor="middle" font-family="system-ui, sans-serif" font-size="12" fill="rgba(255,255,255,0.8)">(Houdini App)</text>
<line x1="60" y1="190" x2="200" y2="190" stroke="rgba(255,255,255,0.3)" stroke-width="1"/>
<text x="130" y="210" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">WebSocket Client</text>
<text x="130" y="228" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">WebRTC Video/Audio</text>
<text x="130" y="246" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">Gamepad/Keyboard</text>
</g>
<!-- Tailscale Cloud -->
<g filter="url(#shadow)">
<ellipse cx="360" cy="190" rx="100" ry="50" fill="#E5E7EB" stroke="#9CA3AF" stroke-width="2"/>
<text x="360" y="185" text-anchor="middle" font-family="system-ui, sans-serif" font-size="14" font-weight="bold" fill="#4B5563">Tailscale VPN</text>
<text x="360" y="205" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="#6B7280">Secure Mesh Network</text>
</g>
<!-- M20 Relay Box -->
<g filter="url(#shadow)">
<rect x="520" y="100" width="200" height="180" rx="12" fill="url(#relayGrad)"/>
<text x="620" y="130" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" fill="white">M20 Relay</text>
<text x="620" y="148" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.8)">(On Robot Computer)</text>
<line x1="540" y1="160" x2="700" y2="160" stroke="rgba(255,255,255,0.3)" stroke-width="1"/>
<text x="620" y="180" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">FastAPI + WebSocket</text>
<text x="620" y="198" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">ROS2 Bridge (rclpy)</text>
<text x="620" y="216" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">go2rtc (RTSP→WebRTC)</text>
<text x="620" y="234" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">GStreamer Audio</text>
<text x="620" y="260" text-anchor="middle" font-family="system-ui, sans-serif" font-size="10" fill="rgba(255,255,255,0.7)">Port 8000 (WS) | 1984 (WebRTC)</text>
</g>
<!-- ROS2 Box -->
<g filter="url(#shadow)">
<rect x="760" y="120" width="120" height="140" rx="12" fill="url(#ros2Grad)"/>
<text x="820" y="155" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" fill="white">ROS2</text>
<text x="820" y="173" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.8)">Foxy</text>
<line x1="775" y1="185" x2="865" y2="185" stroke="rgba(255,255,255,0.3)" stroke-width="1"/>
<text x="820" y="205" text-anchor="middle" font-family="system-ui, sans-serif" font-size="10" fill="rgba(255,255,255,0.9)">/IMU_DATA</text>
<text x="820" y="220" text-anchor="middle" font-family="system-ui, sans-serif" font-size="10" fill="rgba(255,255,255,0.9)">/JOINTS_DATA</text>
<text x="820" y="235" text-anchor="middle" font-family="system-ui, sans-serif" font-size="10" fill="rgba(255,255,255,0.9)">/BATTERY_DATA</text>
<text x="820" y="250" text-anchor="middle" font-family="system-ui, sans-serif" font-size="10" fill="rgba(255,255,255,0.9)">/JOINTS_CMD</text>
</g>
<!-- Robot Hardware Box -->
<g filter="url(#shadow)">
<rect x="620" y="340" width="200" height="130" rx="12" fill="url(#hwGrad)"/>
<text x="720" y="375" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" fill="white">Robot Hardware</text>
<line x1="640" y1="390" x2="800" y2="390" stroke="rgba(255,255,255,0.3)" stroke-width="1"/>
<text x="720" y="410" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">16 Joint Motors</text>
<text x="720" y="428" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">IMU Sensor</text>
<text x="720" y="446" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">RTSP Cameras (Front/Rear)</text>
<text x="720" y="464" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="rgba(255,255,255,0.9)">Jabra Speak 510 USB</text>
</g>
<!-- Arrows -->
<!-- Browser to Tailscale -->
<path d="M220 175 L255 175" stroke="#3B82F6" stroke-width="3" marker-end="url(#arrowBlue)"/>
<path d="M255 205 L220 205" stroke="#3B82F6" stroke-width="3" marker-end="url(#arrowBlue)"/>
<!-- Tailscale to Relay -->
<path d="M460 175 L515 175" stroke="#10B981" stroke-width="3" marker-end="url(#arrowGreen)"/>
<path d="M515 205 L460 205" stroke="#10B981" stroke-width="3" marker-end="url(#arrowGreen)"/>
<!-- Relay to ROS2 -->
<path d="M720 190 L755 190" stroke="#F59E0B" stroke-width="3" marker-end="url(#arrowOrange)"/>
<!-- ROS2 to Hardware -->
<path d="M820 260 L820 300 L720 300 L720 335" stroke="#EF4444" stroke-width="3" marker-end="url(#arrowRed)"/>
<!-- Arrow markers -->
<defs>
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#3B82F6"/>
</marker>
<marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#10B981"/>
</marker>
<marker id="arrowOrange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#F59E0B"/>
</marker>
<marker id="arrowRed" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#EF4444"/>
</marker>
</defs>
<!-- Data flow labels -->
<rect x="225" y="145" width="95" height="20" rx="4" fill="#F3F4F6"/>
<text x="272" y="159" text-anchor="middle" font-family="system-ui, sans-serif" font-size="9" fill="#4B5563">Commands</text>
<rect x="225" y="210" width="95" height="20" rx="4" fill="#F3F4F6"/>
<text x="272" y="224" text-anchor="middle" font-family="system-ui, sans-serif" font-size="9" fill="#4B5563">Telemetry/Video</text>
<rect x="460" y="145" width="55" height="20" rx="4" fill="#F3F4F6"/>
<text x="487" y="159" text-anchor="middle" font-family="system-ui, sans-serif" font-size="9" fill="#4B5563">WS/RTC</text>
<!-- Legend -->
<rect x="40" y="420" width="400" height="60" rx="8" fill="#F9FAFB" stroke="#E5E7EB"/>
<text x="60" y="445" font-family="system-ui, sans-serif" font-size="11" font-weight="bold" fill="#374151">Data Flow:</text>
<circle cx="150" cy="441" r="6" fill="#3B82F6"/>
<text x="162" y="445" font-family="system-ui, sans-serif" font-size="10" fill="#4B5563">WebSocket + WebRTC</text>
<circle cx="280" cy="441" r="6" fill="#F59E0B"/>
<text x="292" y="445" font-family="system-ui, sans-serif" font-size="10" fill="#4B5563">ROS2 Topics</text>
<circle cx="380" cy="441" r="6" fill="#EF4444"/>
<text x="392" y="445" font-family="system-ui, sans-serif" font-size="10" fill="#4B5563">Hardware I/O</text>
<text x="60" y="468" font-family="system-ui, sans-serif" font-size="10" fill="#6B7280">Latency: Commands ~20ms | Video ~300ms | Audio ~50ms</text>
</svg>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment