From 6420e07487cdfc6f3ff8060d04c680af24d3b727 Mon Sep 17 00:00:00 2001 From: sl-firmware Date: Sat, 28 Feb 2026 23:16:17 -0500 Subject: [PATCH] feat: rosbridge WebSocket server for web UI (port 9090) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds rosbridge_suite to the Jetson stack so the browser dashboard can subscribe to ROS2 topics via roslibjs over ws://jetson:9090. docker-compose.yml New service: saltybot-rosbridge - Runs saltybot_bringup/launch/rosbridge.launch.py - network_mode: host → port 9090 directly reachable on Jetson LAN - Depends on saltybot-ros2, stm32-bridge, csi-cameras saltybot_bringup/launch/rosbridge.launch.py - rosbridge_websocket node (port 9090, params from rosbridge_params.yaml) - 4× image_transport/republish nodes: compress CSI camera streams /camera//image_raw → /camera//image_raw/compressed (JPEG 75%) saltybot_bringup/config/rosbridge_params.yaml Whitelisted topics: /map /scan /tf /tf_static /saltybot/imu /saltybot/balance_state /cmd_vel /person/* /camera/*/image_raw/compressed max_message_size: 10 MB (OccupancyGrid headroom) saltybot_bringup/SENSORS.md Added rosbridge connection section with roslibjs snippet, topic reference table, bandwidth estimates, and throttle_rate tips. saltybot_bringup/package.xml Added exec_depend: rosbridge_server, image_transport, image_transport_plugins (all already installed in Docker image). Co-Authored-By: Claude Sonnet 4.6 --- .DS_Store | Bin 0 -> 8196 bytes .pio/build/f722/src/crsf.o | Bin 0 -> 4616 bytes .pio/build/f722/src/i2c1.o | Bin 0 -> 1204 bytes .pio/build/f722/src/jetson_cmd.o | Bin 0 -> 2344 bytes .pio/build/f722/src/mag.o | Bin 0 -> 3104 bytes .pio/build/f722/src/mode_manager.o | Bin 0 -> 3204 bytes .pio/build/f722/src/motor_driver.o | Bin 0 -> 1708 bytes .pio/build/f722/src/mpu6000.o | Bin 0 -> 4044 bytes .pio/build/f722/src/safety.o | Bin 0 -> 3044 bytes jetson/docker-compose.yml | 37 +++++++ .../ros2_ws/src/saltybot_bringup/SENSORS.md | 54 ++++++++++ .../config/rosbridge_params.yaml | 66 ++++++++++++ .../launch/rosbridge.launch.py | 97 ++++++++++++++++++ .../ros2_ws/src/saltybot_bringup/package.xml | 3 + 14 files changed, 257 insertions(+) create mode 100644 .DS_Store create mode 100644 .pio/build/f722/src/crsf.o create mode 100644 .pio/build/f722/src/i2c1.o create mode 100644 .pio/build/f722/src/jetson_cmd.o create mode 100644 .pio/build/f722/src/mag.o create mode 100644 .pio/build/f722/src/mode_manager.o create mode 100644 .pio/build/f722/src/motor_driver.o create mode 100644 .pio/build/f722/src/mpu6000.o create mode 100644 .pio/build/f722/src/safety.o create mode 100644 jetson/ros2_ws/src/saltybot_bringup/config/rosbridge_params.yaml create mode 100644 jetson/ros2_ws/src/saltybot_bringup/launch/rosbridge.launch.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e3c8dbe6689c64c1b6172235c12f7770d5da84d8 GIT binary patch literal 8196 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 GiuWJA97!wy literal 0 HcmV?d00001 diff --git a/.pio/build/f722/src/mag.o b/.pio/build/f722/src/mag.o new file mode 100644 index 0000000000000000000000000000000000000000..654ce88b2a78e76de9a30aadb73141143327699e GIT binary patch literal 3104 zcma)7U2GIp6h8CQUAhz~rTka9%YrV_9hPlLqd}z>mXcB+1ThiGX16=gHQgU)2LwY> zVxmEPlK9|*HN*!^h!2%eRHc#=EkeBb%G z=gyp&nLczht7#f3(I`nRMD*hd>FF4hDoN|9m!{_^5xIH%L8eP@XirDSHeb^cbY0JO zjDGj4wtxB0zn5nk8!0+_wqgFI>Q2vfjx}>0c{sz5{z}Dfn~RQRw)b6&B*A zKr+5ii<9I;T$7sOy3{dw)y3*XE$(G|%&K^2z^@5WYuxEekQlhiJ9H=4EU5Ti8K=d) zJnw3VUzSMRU!vw22gcUT^z-ZKk26XBtEYelrB! z;(cZsM%v8IG<4dq==Jk;^cln(G8eIk*V^Ju8Q&4$;#scLdxWnPHzg9EiR@a*nAz?l zLUPi7OUM0gKZ|#tzKQDCS7dK49e=Q#{(Xs`$)aUHM|69HNDR~Ak&&lF^6;_e#i?p_ zsxHn|9Wm^b#i4S+E*9;gIN?+$?0UWG)WuLLlioX&dSx(G&c9rBc8MV?W9=18rk_j? zilk#t+4;J?OPm=#IljmCOQeQU1t(vbm=unEu~aWrD4f zvrZ5dd_3vnnvXB~_)XTRbKU3p8FStZAAjs)ezts$FIaCO{7Jn$!5X8=->Vp!JLvPW z5Abq-@Nz%sE9`%Q=yldRiQZ=Y7}0yIA1C_I=RabN7kid9&i6TM^u@Ew{mw&kA7s@p zm)(2;+I2iVsVsajU9yUJC}b7tb+YgowwSckXoV;_b`M9j4`91r&Qv?)1Sf(ww)~m( zZ8^A@>2lewpg;T(ti0-Yx?;-qn%g>eEb0#TzGH9?x#D-d$jxLnW%710q=OKI-w_d|v)ns(Nq1 z4%WK@un10xs`mznV7)7t`)lC19)A~9y&JH(U-jHx!h=`O9(2?G^k3q*4<*XxcbskY z47gtK-@raz_pq>M4NA(-h#ODq$! literal 0 HcmV?d00001 diff --git a/.pio/build/f722/src/mode_manager.o b/.pio/build/f722/src/mode_manager.o new file mode 100644 index 0000000000000000000000000000000000000000..d60956596b548fdfd0a1680895269266179982c3 GIT binary patch literal 3204 zcma)8TTB#J82)ExSr$;iEmFiv#s%$SWxA{ouuUr$U9f1;S`r)6470m~EbcPv%wQOm^85{DYnM#|Ig0ZotBHRuc?Y3k6+;YpU(KTYb!NVJAS#gl2&*8 zjA2VM@qv@;b55bJ>jdUPGTx|Msh5gBh5{oG@;jpGg6~U1)C(1zXyiPqj8D)&tZg>W5>Z1cc7r2X}Jd`-_C62hq5%)knBEHO{Jb8^29=r~`*Te_& zC~scN;maKOG85mIN98U9X7W(sz*ji%6%1-h9@abX>mBh*vwgu?3EM1#GQ~knJWm~Tj`F-1zsWAX__ z6Qj^*KvdI*CEbWePeNl1bSWY0hD4^=8fs~SlTsw1jKw5F)=nr!QF|#q7B@hW%tI!{ zL&JG}I60mu7|~=ZQNRt$u~<>;ge|6zM~b@`PsLPEk>`HQXshiAj;TtQW`Rs1ZM{VA>#zU->qGgCzX`$4E8+L`XIQj9Ruqay!69 zk~;t{S^h69Kb=cgEtqTU!Ug$?>@B%{RbC449O1AZ?858^DWH5O0G&6&)BbxJ9KIRm z_-aVvctF(ONg8qlrJ?2I2-EOFG(f}@%OFN{9Yk`7wk?r!6y@ApbrJ95HDcCstr00& zsVB59EYp7h3JdlGl(lp;A@N`PS=3aTuISj;VpL5gl`%{PJ};td7+O3sZYVmY?`aDz zrFr%|R)gF>QlT|_9dL)%yNmV| zD-8D*I_$Xj(5Cx|6}P1fqNEp~)3U2^4-cVEZyJq-a5R3(p`EW24g2^QAKi_1+yokQ zkFmmVAECqEuYvvI@i6-(KkN58>UO?wpTI}w($4ppjgMX|#`mj(uND(V{j$RHcMBbM zz8lC#d8WM33&;3YQMa#eClatZVkoa~z;V+&7(e0G5x^>}&2|eHwEPDVH^o3GiGw~7 zE8J5l7WxixyP08O$RsT5lWc0VgpO)d1^lRAnitKL?NL-=34c>ssIgU;O0TUQcN6pS KDhpzass8|h{!mr` literal 0 HcmV?d00001 diff --git a/.pio/build/f722/src/motor_driver.o b/.pio/build/f722/src/motor_driver.o new file mode 100644 index 0000000000000000000000000000000000000000..14dde9a3c2b515d8bc4bc34a67b8fe52fe7965c7 GIT binary patch literal 1708 zcma)6K~ED=5T5O}Kt)s#R4^f1AhAvCy3~@2@c=Efnou62+#RNR)!5A;%%x)iSSBy^b-kX{4>&(2_d0m>EN(+Jj zTm%Tifdw$_w~;{HKEiMujsXmc%PX~7MtRjc6-c+GL&d>6&liVYLZ~gRyxsfpt3K2B z`X`KOk@obBuie7>uHUy&*9K=&t4Zh9 zH$p@c(L7VDg`&jtg_2n^s#0uZB&NjYU_6F3sk)jkfe=PDkTRI1FGjNoXz6Ip27O)r zZlOmwEuIm2g+8I*Kj3aET?obP^24`1$Ls836AB=L&kdt3dTc+OX!1B8j=Mw%c<=z6 zM6k#Sh`U9ERu8|`4P*8;58md%gLeD_XAn*`4600k-~oi12uD12JDjt5fSCZgzz?Gh zyKLJ5$l}R`?mmZ$dPM-ZZn15QQZX!pq_uwX* z4=a|M$J1iYS>zfHmIX3qRYA_1CdlNF-IsC7lyQs?!5Rf0f;eUV4QLcgDdj(!Bo~ZI zMXzFA_+n($vRElUZ|Nr8?SJJg66t%<_aSlqV;r4!KJg*ciN`12%Oi}0=7nbEU%d0k z@#b|;FVB0`!@Ghu%^ny2!Y0r`(>&Gy7O?=D$D_FDRS}Qk=W)67ZlLX+dY<>Ohqs9~ z?-8o|eR+mD#pU!y>c0L0b;_%d6NPw7w&rYv(RzamW6`s*C4tV1+`yAKBy{px s$u6T~8CM7D^q)dC(EX_=yf#$f3;yUSDGlNAI?;CXo}lrblknuc-<4$bZU6uP literal 0 HcmV?d00001 diff --git a/.pio/build/f722/src/mpu6000.o b/.pio/build/f722/src/mpu6000.o new file mode 100644 index 0000000000000000000000000000000000000000..4257e087b4734f37d7a561f61a021b0908575606 GIT binary patch literal 4044 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 M>R9J8Co#wHUu;@&2><{9 literal 0 HcmV?d00001 diff --git a/jetson/docker-compose.yml b/jetson/docker-compose.yml index eec51e4..8b5c531 100644 --- a/jetson/docker-compose.yml +++ b/jetson/docker-compose.yml @@ -166,6 +166,20 @@ services: # ── Surround vision — 360° bird's-eye view + Nav2 camera obstacle layer ───── saltybot-surround: + + # ── rosbridge WebSocket server ──────────────────────────────────────────── + # Serves ROS2 topics to the web dashboard (roslibjs) on ws://jetson:9090 + # + # Topics exposed (whitelist in ros2_ws/src/saltybot_bringup/config/rosbridge_params.yaml): + # /map /scan /tf /tf_static /saltybot/imu /saltybot/balance_state + # /cmd_vel /person/* /camera/*/image_raw/compressed + # + # Also runs image_transport/republish nodes to compress 4× CSI camera streams. + # Raw 640×480 YUYV → JPEG-compressed sensor_msgs/CompressedImage. + # + # Install (already in image): apt install ros-humble-rosbridge-server + # ros-humble-image-transport-plugins + rosbridge: image: saltybot/ros2-humble:jetson-orin build: context: . @@ -191,6 +205,29 @@ services: start_cameras:=false camera_height:=0.30 publish_rate:=5.0 + + container_name: saltybot-rosbridge + restart: unless-stopped + runtime: nvidia + network_mode: host # port 9090 is directly reachable on host + depends_on: + - saltybot-ros2 # needs /map, /tf published + - stm32-bridge # needs /saltybot/imu, /saltybot/balance_state + - csi-cameras # needs /camera/*/image_raw for compression + environment: + - ROS_DOMAIN_ID=42 + - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp + volumes: + - ./ros2_ws/src:/ros2_ws/src:rw + - ./config:/config:ro + # Port 9090 is accessible on all host interfaces via network_mode: host. + # To restrict to a specific interface, set host: "192.168.x.x" in + # rosbridge_params.yaml instead of using Docker port mappings. + command: > + bash -c " + source /opt/ros/humble/setup.bash && + source /ros2_ws/install/local_setup.bash 2>/dev/null || true && + ros2 launch saltybot_bringup rosbridge.launch.py " # ── Nav2 autonomous navigation stack ──────────────────────────────────────── diff --git a/jetson/ros2_ws/src/saltybot_bringup/SENSORS.md b/jetson/ros2_ws/src/saltybot_bringup/SENSORS.md index d62ddf7..2e2638b 100644 --- a/jetson/ros2_ws/src/saltybot_bringup/SENSORS.md +++ b/jetson/ros2_ws/src/saltybot_bringup/SENSORS.md @@ -4,6 +4,60 @@ ROS2 Humble | ROS_DOMAIN_ID=42 | DDS: CycloneDDS --- +## rosbridge WebSocket (web dashboard) + +The web dashboard connects to ROS2 topics via rosbridge WebSocket on **port 9090**. + +```bash +# Start rosbridge (and compressed camera republishers): +ros2 launch saltybot_bringup rosbridge.launch.py + +# Or via docker-compose: +docker compose up -d rosbridge +``` + +**Browser connection (roslibjs):** +```javascript +var ros = new ROSLIB.Ros({ url: 'ws://jetson.local:9090' }); +ros.on('connection', () => console.log('connected')); +ros.on('error', (e) => console.error('error', e)); +ros.on('close', () => console.log('disconnected')); +``` + +**Whitelisted topics** (configured in `config/rosbridge_params.yaml`): + +| Topic | Type | Notes | +|-------|------|-------| +| `/map` | `nav_msgs/OccupancyGrid` | Use `throttle_rate: 5000` (0.2 Hz) | +| `/scan` | `sensor_msgs/LaserScan` | 5.5 Hz, 1440 pts | +| `/tf` | `tf2_msgs/TFMessage` | Use `throttle_rate: 100` (10 Hz) | +| `/tf_static` | `tf2_msgs/TFMessage` | One-shot on connect | +| `/saltybot/imu` | `sensor_msgs/Imu` | 50 Hz, pitch/roll/yaw | +| `/saltybot/balance_state` | `std_msgs/String` | JSON balance controller state | +| `/cmd_vel` | `geometry_msgs/Twist` | Subscribe to monitor Nav2 commands | +| `/person/target` | (custom) | Person tracking target | +| `/person/detections` | (custom) | Person detection results | +| `/camera/front/image_raw/compressed` | `sensor_msgs/CompressedImage` | JPEG 75%, 640×480 | +| `/camera/left/image_raw/compressed` | `sensor_msgs/CompressedImage` | JPEG 75%, 640×480 | +| `/camera/rear/image_raw/compressed` | `sensor_msgs/CompressedImage` | JPEG 75%, 640×480 | +| `/camera/right/image_raw/compressed` | `sensor_msgs/CompressedImage` | JPEG 75%, 640×480 | + +**Bandwidth tips:** +- Raw 640×480 YUYV = ~900 KB/frame. rosbridge republishes JPEG-compressed ≈ 15–25 KB/frame. +- Always use `throttle_rate` for cameras in roslibjs (e.g. `throttle_rate: 200` = max 5 fps). +- `/map` is large (~100 KB); use `throttle_rate: 5000` or subscribe only on demand. + +**Verify connection:** +```bash +# Check WebSocket is listening +ss -tlnp | grep 9090 + +# List topics visible to rosbridge +ros2 topic list | grep -E 'map|scan|imu|camera|person|cmd_vel' +``` + +--- + ## Build & Run ```bash diff --git a/jetson/ros2_ws/src/saltybot_bringup/config/rosbridge_params.yaml b/jetson/ros2_ws/src/saltybot_bringup/config/rosbridge_params.yaml new file mode 100644 index 0000000..47bbf89 --- /dev/null +++ b/jetson/ros2_ws/src/saltybot_bringup/config/rosbridge_params.yaml @@ -0,0 +1,66 @@ +# rosbridge_params.yaml — rosbridge_websocket server configuration +# +# WebSocket endpoint: ws://:9090 +# Browser client lib: roslibjs (cdn.jsdelivr.net/npm/roslib) +# +# Topic whitelist limits bandwidth and prevents unintended exposure. +# Glob patterns follow Python fnmatch: * matches everything (incl. /). +# +# Bandwidth budget (worst case, all subscriptions active): +# /map ~100 KB/update × 0.2 Hz = ~20 KB/s +# /scan ~5 KB/scan × 5.5 Hz = ~28 KB/s +# /saltybot/imu ~0.2 KB/msg × 50 Hz = ~10 KB/s +# /tf ~1 KB/msg × 10 Hz = ~10 KB/s +# 4× cameras ~20 KB/frame × 5 Hz = ~400 KB/s (JPEG quality 75) +# ───────────────────────────────────────────────────── +# Total ~470 KB/s ≈ 3.8 Mbps +# +# Use throttle_rate in roslibjs subscribe() calls to cap per-topic rates: +# viewer.subscribe({ topic: '/map', throttle_rate: 5000 }) // 0.2 Hz + +rosbridge_websocket: + ros__parameters: + # ── Network ────────────────────────────────────────────────────────────── + port: 9090 + host: "0.0.0.0" # bind all interfaces (Jetson LAN + USB-C) + address: "" + + # ── Authentication ──────────────────────────────────────────────────────── + authenticate: false # no auth on local network; enable if exposed + + # ── Message size ────────────────────────────────────────────────────────── + # OccupancyGrid /map can exceed 1 MB for large environments. + max_message_size: 10000000 # 10 MB + + # ── Topic/service/param whitelists ──────────────────────────────────────── + # JSON-encoded glob list. "*" in a glob matches any chars including "/". + # Only topics matching at least one pattern are accessible to clients. + topics_glob: >- + ["/map", + "/person/target", + "/person/detections", + "/camera/*/image_raw/compressed", + "/scan", + "/cmd_vel", + "/saltybot/imu", + "/saltybot/balance_state", + "/tf", + "/tf_static"] + + services_glob: "[]" # no service calls via WebSocket + params_glob: "[]" # no parameter access via WebSocket + + # ── Connection management ───────────────────────────────────────────────── + # Time (s) before dropping a client that stops responding. + unregister_timeout: 10.0 + + # Fragment timeout: max seconds to wait for all fragments of a large message. + fragment_timeout: 600 + + # Delay between consecutive outgoing messages (ms). 0 = unlimited. + # Set > 0 (e.g. 10) if browser JS event loop is overwhelmed. + delay_between_messages: 0 + + # ── Logging ─────────────────────────────────────────────────────────────── + # Set to true to log every publish/subscribe call (verbose, dev only). + bson_only_mode: false diff --git a/jetson/ros2_ws/src/saltybot_bringup/launch/rosbridge.launch.py b/jetson/ros2_ws/src/saltybot_bringup/launch/rosbridge.launch.py new file mode 100644 index 0000000..31bbd8c --- /dev/null +++ b/jetson/ros2_ws/src/saltybot_bringup/launch/rosbridge.launch.py @@ -0,0 +1,97 @@ +""" +rosbridge.launch.py — rosbridge WebSocket server + compressed image transport + +Starts two things: + 1. rosbridge_websocket — WebSocket server on port 9090 (roslibjs-compatible) + 2. image_transport/republish × 4 — compresses each CSI camera's raw stream + /camera//image_raw → /camera//image_raw/compressed + +Prerequisites (already installed in saltybot/ros2-humble:jetson-orin): + apt install ros-humble-rosbridge-server + apt install ros-humble-image-transport-plugins # supplies compressed plugin + +Browser connection: + var ros = new ROSLIB.Ros({ url: 'ws://:9090' }); + +Topic whitelist is configured in config/rosbridge_params.yaml. + +Bandwidth tips: + - Subscribe to /camera//image_raw/compressed, not raw images. + - Use throttle_rate in ROSLIB.Topic to cap camera update rate: + new ROSLIB.Topic({ ... messageType: 'sensor_msgs/CompressedImage', + throttle_rate: 200 }) // max 5 fps per camera + - Subscribe to /map with throttle_rate: 5000 (0.2 Hz is enough for display). + +Usage: + ros2 launch saltybot_bringup rosbridge.launch.py + +Verify: + ros2 topic echo /rosbridge_websocket/status + # In browser console: + # var ros = new ROSLIB.Ros({ url: 'ws://jetson.local:9090' }); + # ros.on('connection', () => console.log('connected')); +""" + +import os +from launch import LaunchDescription +from launch_ros.actions import Node +from ament_index_python.packages import get_package_share_directory + + +# Camera names matching saltybot_cameras/launch/csi_cameras.launch.py +# Topics: /camera//image_raw (published by v4l2_camera_node) +_CAMERAS = ['front', 'left', 'rear', 'right'] + +# JPEG quality for compressed output (0–100). +# 75 = good quality/size trade-off at 640×480: ~15–25 KB/frame. +# Lower to 50 for tighter bandwidth budgets; raise to 90 for inspection use. +_JPEG_QUALITY = 75 + + +def generate_launch_description(): + pkg_share = get_package_share_directory('saltybot_bringup') + params_file = os.path.join(pkg_share, 'config', 'rosbridge_params.yaml') + + # ── rosbridge WebSocket server ──────────────────────────────────────────── + rosbridge = Node( + package='rosbridge_server', + executable='rosbridge_websocket', + name='rosbridge_websocket', + parameters=[params_file], + output='screen', + ) + + # ── Compressed image republishers ───────────────────────────────────────── + # image_transport/republish subscribes to raw sensor_msgs/Image and + # re-publishes as sensor_msgs/CompressedImage using the compressed plugin. + # + # Node arguments: ['raw', 'compressed'] + # 'raw' — input transport type + # 'compressed' — output transport type + # + # Topic remappings: + # in → /camera//image_raw + # out/compressed → /camera//image_raw/compressed + # + # Parameter jpeg_quality controls JPEG encoder quality for the + # compressed publisher. The full parameter path in ROS2 is: + # //compressed/jpeg_quality + republishers = [ + Node( + package='image_transport', + executable='republish', + name=f'compress_{name}', + arguments=['raw', 'compressed'], + remappings=[ + ('in', f'/camera/{name}/image_raw'), + ('out/compressed', f'/camera/{name}/image_raw/compressed'), + ], + parameters=[{ + 'compressed.jpeg_quality': _JPEG_QUALITY, + }], + output='screen', + ) + for name in _CAMERAS + ] + + return LaunchDescription([rosbridge] + republishers) diff --git a/jetson/ros2_ws/src/saltybot_bringup/package.xml b/jetson/ros2_ws/src/saltybot_bringup/package.xml index 5083f62..71ac935 100644 --- a/jetson/ros2_ws/src/saltybot_bringup/package.xml +++ b/jetson/ros2_ws/src/saltybot_bringup/package.xml @@ -15,6 +15,9 @@ nav2_bringup robot_state_publisher tf2_ros + rosbridge_server + image_transport + image_transport_plugins ament_python -- 2.47.2