15ff5acca7
Merge pull request 'feat: Visual odometry from RealSense stereo ORB (Issue #586 )' ( #593 ) from sl-perception/issue-586-visual-odom into main
2026-03-14 13:32:56 -04:00
f2743198e5
Merge pull request 'feat: WebUI map view (Issue #587 )' ( #591 ) from sl-webui/issue-587-map-view into main
2026-03-14 13:32:50 -04:00
6512c805be
Merge pull request 'feat: Motor current monitoring (Issue #584 )' ( #594 ) from sl-controls/issue-584-motor-current into main
2026-03-14 13:32:30 -04:00
1da1d50171
Merge pull request 'feat: Phone video bridge (Issue #585 )' ( #592 ) from sl-android/issue-585-video-bridge into main
2026-03-14 13:32:24 -04:00
6a8b6a679e
Merge pull request 'feat: Integrate UWB tag display + ESP-NOW + e-stop (salty/uwb-tag-display-wireless)' ( #590 ) from sl-uwb/issue-merge-uwb-tag-display into main
2026-03-14 13:32:19 -04:00
ddf8332cd7
Merge pull request 'feat: Battery holder bracket (Issue #588 )' ( #589 ) from sl-mechanical/issue-588-battery-holder into main
2026-03-14 13:32:16 -04:00
e9429e6177
Merge pull request 'feat: ROS2 launch orchestrator for full SaltyBot bringup (Issue #577 )' ( #582 ) from sl-jetson/issue-577-bringup-launch into main
2026-03-14 13:32:10 -04:00
2b06161cb4
feat: Motor current monitoring and overload protection (Issue #584 )
...
Adds ADC-based motor current sensing with configurable overload threshold,
soft PWM limiting, hard cutoff on sustained overload, and auto-recovery.
Changes:
- include/motor_current.h: MotorCurrentState enum (NORMAL/SOFT_LIMIT/COOLDOWN),
thresholds (5A hard, 4A soft, 2s overload, 10s cooldown), full API
- src/motor_current.c: reads battery_adc_get_current_ma() each tick (reuses
existing ADC3 IN13/PC3 DMA sampling); linear PWM scale in soft-limit zone
(scale256 fixed-point); fault counter + one-tick fault_pending flag for
main-loop fault log integration; telemetry at MOTOR_CURR_TLM_HZ (5 Hz)
- include/pid_flash.h: add pid_sched_entry_t (16 bytes), pid_sched_flash_t
(128 bytes at 0x0807FF40), PID_SCHED_MAX_BANDS=6, pid_flash_load_schedule(),
pid_flash_save_all() — fixes missing types needed by jlink.h (Issue #550 )
- src/pid_flash.c: implement flash_write_words() helper, pid_flash_load_schedule(),
pid_flash_save_all() — single sector-7 erase covers both schedule and PID records
- include/jlink.h: add JLINK_TLM_MOTOR_CURRENT (0x86), jlink_tlm_motor_current_t
(8 bytes: current_ma, limit_pct, state, fault_count), jlink_send_motor_current_tlm()
- src/jlink.c: implement jlink_send_motor_current_tlm() (14-byte frame)
Motor overload state machine:
MC_NORMAL : current_ma < 4000 mA — full PWM authority
MC_SOFT_LIMIT : 4000-5000 mA — linear reduction (0% at 4A → 100% at 5A)
MC_COOLDOWN : >5A sustained 2s → zero output for 10s then NORMAL
Main-loop integration:
motor_current_tick(now_ms);
if (motor_current_fault_pending()) fault_log_append(FAULT_MOTOR_OVERCURRENT);
cmd = motor_current_apply_limit(balance_pid_output());
motor_current_send_tlm(now_ms);
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 12:25:29 -04:00
c1b82608d5
feat: Visual odometry from RealSense stereo ORB (Issue #586 )
...
Adds stereo ORB-based visual odometry to saltybot_visual_odom package.
New modules:
- orb_stereo_matcher.py: ORB feature detection (cv2.ORB_create) with BFMatcher
NORM_HAMMING + Lowe ratio test for temporal matching (infra1 prev→curr).
Stereo scale method matches infra1↔infra2 under epipolar row constraint
(|Δrow|≤2px), computes depth = baseline_m * fx / disparity.
- stereo_orb_node.py: StereoOrbNode subscribes to infra1+infra2+depth
(ApproximateTimeSynchronizer 3-topic), detects/matches ORB temporally,
estimates SE(3) via Essential matrix (5-point RANSAC) using StereoVO,
recovers metric scale from D435i aligned depth (primary) or stereo
baseline disparity (fallback). Publishes nav_msgs/Odometry on
/saltybot/visual_odom and broadcasts TF2 odom→camera_link. Baseline
auto-updated from infra2 camera_info Tx (overrides parameter).
- config/stereo_orb_params.yaml, launch/stereo_orb.launch.py
- setup.py: adds stereo_orb entrypoint, installs launch+config dirs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 12:21:58 -04:00
sl-android
08bc23f6df
feat: Phone video streaming bridge (Issue #585 )
...
Phone side — phone/video_bridge.py:
- MJPEG streaming server for Android/Termux phone camera
- Dual camera backends: OpenCV VideoCapture (V4L2) with automatic
fallback to termux-camera-photo for unmodified Android
- WebSocket server (ws://0.0.0.0:8765) — binary JPEG frames + JSON
info/error control messages; supports multiple concurrent clients
- HTTP server (http://0.0.0.0:8766 ):
/stream — multipart/x-mixed-replace MJPEG
/snapshot — single JPEG
/health — JSON stats (frame count, dropped, resolution, fps)
- Thread-safe single-slot FrameBuffer; CaptureThread rate-limited with
wall-clock accounting for capture latency
- Flags: --ws-port, --http-port, --width, --height, --fps, --quality,
--device, --camera-id, --no-http, --debug
Jetson side — saltybot_phone/phone_camera_node.py:
- ROS2 node: receives JPEG frames, publishes:
/saltybot/phone/camera sensor_msgs/Image (bgr8)
/saltybot/phone/camera/compressed sensor_msgs/CompressedImage
/saltybot/phone/camera/info std_msgs/String (stream metadata)
- WebSocket client (primary); HTTP MJPEG polling fallback on WS failure
- Auto-reconnect loop (default 3 s) for both transports
- Latency warning when frame age > latency_warn_ms (default 200 ms)
- 10 s diagnostics log: received/published counts + last frame age
- Registered as phone_camera_node console script in setup.py
- Added to phone_bringup.py launch with phone_host / phone_cam_enabled args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 12:20:28 -04:00
5dac6337e6
feat: WebUI map view (Issue #587 )
...
Standalone 3-file 2D map panel (ui/map_panel.{html,js,css}).
No build step. Open directly or serve ui/ directory.
Canvas layers (drawn every animation frame):
- Grid lines (1m spacing) + world-origin axis cross + 1m scale bar
- RPLIDAR scan dots (/scan, green, cbor-compressed, 100ms throttle)
- Safety zone rings: danger 0.30m (red dashed) + warn 1.00m (amber dashed)
- 100-position breadcrumb trail with fading cyan polyline + dots every 5 pts
- UWB anchor markers (amber diamond + label, user-configured)
- Robot marker: circle + forward arrow, red when e-stopped
Interactions:
- Mouse wheel zoom (zooms around cursor)
- Click+drag pan
- Pinch-to-zoom (touch, two-finger)
- Auto-center toggle (robot stays centered when on)
- Zoom +/- buttons, Reset view button
- Clear trail button
- Mouse hover shows world coords (m) in bottom-left HUD
ROS topics:
SUB /saltybot/pose/fused geometry_msgs/PoseStamped 50ms throttle
SUB /scan sensor_msgs/LaserScan 100ms + cbor
SUB /saltybot/safety_zone/status std_msgs/String (JSON) 200ms throttle
Sidebar:
- Robot position (x, y m) + heading (°)
- Safety zone: forward zone (CLEAR/WARN/DANGER), closest obstacle (m), e-stop
- UWB anchor manager: add/remove anchors with x/y/label, persisted localStorage
- Topic reference
E-stop banner: pulsing red overlay when /saltybot/safety_zone/status estop_active=true
Mobile-responsive: sidebar hidden on <700px, canvas fills viewport.
WS URL persisted in localStorage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 12:20:04 -04:00
sl-uwb
4b8d1b2ff7
feat: Integrate UWB tag display + ESP-NOW + e-stop (salty/uwb-tag-display-wireless)
...
Integrates Tee's additions to the DS-TWR tag firmware (esp32/uwb_tag/).
Base is our DS-TWR initiator from Issue #545 ; extensions added:
OLED display (SSD1306 128×64, I2C SDA=4 SCL=5):
- Big distance readout (nearest anchor, auto m/mm)
- Per-anchor range rows with link-age indicator
- Signal strength bars (RSSI)
- Uptime + sequence counter
- Full-screen E-STOP warning when button held
ESP-NOW wireless (peer-to-peer, no AP required):
- 20-byte broadcast packet: magic, tag_id, msg_type, anchor_id,
range_mm, rssi_dbm, timestamp_ms, battery_pct, flags, seq_num
- MSG_RANGE (0x10) on every successful TWR
- MSG_ESTOP (0x20) at 10 Hz while button held; 3× clear on release
- MSG_HEARTBEAT (0x30) at 1 Hz
Emergency stop (GPIO 0 / BOOT button, active LOW):
- Blocks ranging while active
- 10 Hz ESP-NOW e-stop TX, serial +ESTOP:ACTIVE / +ESTOP:CLEAR
- 3× clear packets on release
Build: adds Adafruit SSD1306 + GFX libraries to platformio.ini.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 12:19:31 -04:00
5556c06153
feat: Battery holder bracket (Issue #588 )
2026-03-14 12:18:37 -04:00
5a1290a8f9
Merge pull request 'feat: UWB anchor mount bracket (Issue #564 )' ( #569 ) from sl-mechanical/issue-564-uwb-anchor-mount into main
2026-03-14 12:15:43 -04:00
7b75cdad1a
feat: UWB anchor mount bracket (Issue #564 )
2026-03-14 12:15:12 -04:00
b09017c949
Merge pull request 'feat: UWB-IMU EKF fusion for robust indoor localization (Issue #573 )' ( #581 ) from sl-uwb/issue-573-uwb-imu-fusion into main
2026-03-14 12:14:05 -04:00
1726558a7a
Merge pull request 'feat: RPLIDAR safety zone detector (Issue #575 )' ( #580 ) from sl-perception/issue-575-safety-zone into main
2026-03-14 12:14:01 -04:00
5a3f4d1df6
Merge pull request 'feat: WebUI event log panel (Issue #576 )' ( #579 ) from sl-webui/issue-576-event-log into main
2026-03-14 12:13:56 -04:00
b2f01b42f3
Merge pull request 'feat: Termux sensor dashboard (Issue #574 )' ( #578 ) from sl-android/issue-574-sensor-dashboard into main
2026-03-14 12:13:51 -04:00
a7eb2ba3e5
Merge pull request 'feat: PID gain scheduling for speed-dependent balance (Issue #550 )' ( #560 ) from sl-controls/issue-550-pid-scheduling into main
2026-03-14 12:13:44 -04:00
4035b4cfc3
feat: ROS2 launch orchestrator for full SaltyBot bringup (Issue #577 )
...
Adds saltybot_bringup.launch.py with ordered startup groups (drivers→
perception→navigation→UI), timer-based health gates, configurable
profiles (minimal/full/debug), and estop on Ctrl-C shutdown.
Also adds launch_profiles.py dataclass module and 53-test coverage for
profile hierarchy, timing gates, safety bounds, and to_dict serialization.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:57:57 -04:00
sl-uwb
7708c63698
feat: UWB-IMU EKF fusion for robust indoor localization (Issue #573 )
...
EKF fusing UWB position (10Hz) with IMU accel+gyro (200Hz) for
SaltyBot indoor localization with UWB dropout resilience.
Package: saltybot_uwb_imu_fusion
- ekf_math.py: 6-state EKF [x,y,θ,vx,vy,ω], IMU predict + UWB update
- IMU as process input: body-frame accel rotated to world via heading
- Jacobian F for nonlinear rotation effect
- Process noise Q from continuous white-noise model
- UWB 2D position update, heading update from quaternion
- Accel bias estimation (low-pass)
- Velocity damping during UWB dropout (>2s threshold)
- ekf_node.py: ROS2 node subscribing to /imu/data (200Hz) + /saltybot/uwb/pose
or /uwb/bearing (10Hz)
- Publishes /saltybot/pose/fused (PoseStamped)
- Publishes /saltybot/pose/fused_cov (PoseWithCovarianceStamped)
- Broadcasts base_link → map TF2 at IMU rate
- Suppresses output after max_dead_reckoning_s without UWB
- 14/14 unit tests passing (predict, update, dropout, PD covariance)
- Launch: ros2 launch saltybot_uwb_imu_fusion uwb_imu_fusion.launch.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:55:43 -04:00
131d85a0d3
feat: RPLIDAR safety zone detector (Issue #575 )
...
Add saltybot_safety_zone — ROS2 Python node that processes the RPLIDAR
A1M8 /scan into three concentric 360° safety zones, latches an e-stop
when DANGER is detected in the forward arc, and overrides /cmd_vel to
zero while the latch is active.
Zone thresholds (default):
DANGER < 0.30 m — latching e-stop in forward arc
WARN < 1.00 m — advisory (published in sector data)
CLEAR otherwise
Sector grid:
36 sectors of 10° each (sector 0 = robot forward, CCW positive).
Per-sector: angle_deg, zone, min_range_m, in_forward_arc flag.
E-stop behaviour:
- Latches after estop_debounce_frames (2) consecutive DANGER scans
in the forward arc (configurable ±30°, or all-arcs mode).
- While latched: zero Twist published to /cmd_vel every scan + every
incoming /cmd_vel_input message is blocked.
- Clear only via service (obstacle must be gone):
/saltybot/safety_zone/clear_estop (std_srvs/Trigger)
Published topics:
/saltybot/safety_zone String/JSON every scan
— per-sector {sector, angle_deg, zone, min_range_m, forward}
— estop_active, estop_reason, danger_sectors[], warn_sectors[]
/saltybot/safety_zone/status String/JSON 10 Hz
— forward_zone, closest_obstacle_m, danger/warn counts
/cmd_vel Twist zero when e-stopped
Subscribed topics:
/scan LaserScan — RPLIDAR A1M8
/cmd_vel_input Twist — upstream velocity (pass-through / block)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:54:52 -04:00
44691742c8
feat: WebUI event log panel (Issue #576 )
...
Standalone 3-file filterable real-time event log (no build step).
Files:
ui/event_log_panel.html — layout, toolbar, empty state
ui/event_log_panel.js — rosbridge subscriptions, ring buffer, render
ui/event_log_panel.css — dark-theme, responsive grid layout
Features:
- 1000-entry ring buffer (oldest dropped when full, FIFO)
- Subscribes /rosout (rcl_interfaces/msg/Log) + /saltybot/events (std_msgs/String JSON)
- Severity filter buttons: DEBUG / INFO / WARN / ERROR / FATAL / EVENT (toggle on/off)
- Node name filter: select dropdown populated from seen nodes
- Live text search with <mark> highlight, Ctrl+F shortcut, Esc to clear
- Auto-scroll to latest entry; pauses on mouse hover (messages still buffered)
- Manual pause/resume button; detects user scroll-up and stops auto-scroll
- CSV export of current filtered view with timestamp (filename includes ISO date)
- Clear all entries button
- Color-coded by severity: left border stripe + text color per level
- Entry columns: timestamp (ms precision) | severity | node | message
- [system] entries for connect/disconnect events
- WS URL persisted in localStorage
- Responsive: node column hidden on narrow screens
ROS topics:
SUB /rosout rcl_interfaces/msg/Log (all nodes)
SUB /saltybot/events std_msgs/String (JSON: {level,node,msg})
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:54:13 -04:00
sl-android
814624045a
feat: Termux sensor dashboard (Issue #574 )
...
Add phone/sensor_dashboard.py — publishes phone sensors to SaltyBot MQTT:
- IMU → saltybot/phone/imu @ 5 Hz (accelerometer + gyroscope via
termux-sensor -s <name> -n 1)
- GPS → saltybot/phone/gps @ 1 Hz (lat/lon/alt/accuracy/speed/bearing
via termux-location; GPS→network fallback on cold start)
- Battery → saltybot/phone/battery @ 1 Hz (pct/charging/temp/health/plugged
via termux-battery-status)
- paho-mqtt with loop_start() + on_connect/on_disconnect callbacks for
automatic reconnect (exponential back-off, max 60 s)
- Each sensor runs in its own daemon thread (SensorPoller); rate enforced
by wall-clock sleep accounting for read latency
- 30 s status log: per-poller publish/error counts + MQTT state
- Flags: --broker, --port, --imu-hz, --gps-hz, --bat-hz, --qos,
--no-imu, --no-gps, --no-battery, --debug
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:53:04 -04:00
929c9ecd74
feat: UWB anchor mount bracket (Issue #564 )
2026-03-14 11:51:50 -04:00
8592361095
feat: PID gain scheduling for speed-dependent balance (Issue #550 )
...
Implements a speed-dependent PID gain scheduler that interpolates Kp/Ki/Kd
across a configurable table of velocity breakpoints, replacing the fixed
single-gain PID used previously.
Changes:
- include/pid_flash.h: add pid_sched_entry_t (16-byte entry), pid_sched_flash_t
(128-byte record at 0x0807FF40), pid_flash_load_schedule(), pid_flash_save_all()
(atomic single-sector erase for both schedule and single-PID records)
- src/pid_flash.c: implement load_schedule and save_all; single erase covers
both records at 0x0807FF40 (schedule) and 0x0807FFC0 (single PID)
- include/pid_schedule.h: API header -- init, get_gains, apply, set/get table,
flash_save, active_band_idx, get_default_table
- src/pid_schedule.c: linear interpolation between sorted speed-band entries;
integrator reset on band transition; default 3-band table (0/0.3/0.8 m/s)
- include/jlink.h: add SCHED_GET (0x0C), SCHED_SET (0x0D), SCHED_SAVE (0x0E)
commands; TLM_SCHED (0x85); jlink_tlm_sched_t; JLinkSchedSetBuf;
sched_get_req, sched_save_req fields in JLinkState; include pid_flash.h
- src/jlink.c: dispatch SCHED_GET/SET/SAVE; implement jlink_send_sched_telemetry,
jlink_get_sched_set; add JLinkSchedSetBuf static buffer
- test/test_pid_schedule.c: 48 unit tests -- all passing (gcc host build)
Flash layout (sector 7):
0x0807FF40 pid_sched_flash_t (128 bytes) -- schedule
0x0807FFC0 pid_flash_t ( 64 bytes) -- single PID (existing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:51:11 -04:00
35440b7463
Merge pull request 'feat: ROS2 sensor health monitor (Issue #566 )' ( #572 ) from sl-jetson/issue-566-health-monitor into main
2026-03-14 11:49:55 -04:00
d36b79371d
Merge pull request 'feat: ESP32 UWB Pro anchor firmware — DS-TWR responder (Issue #544 )' ( #570 ) from sl-uwb/issue-544-anchor-firmware into main
2026-03-14 11:49:51 -04:00
3b0b9d0f16
Merge pull request 'feat: UWB tag firmware (Issue #545 )' ( #568 ) from sl-perception/issue-546-uwb-ros2 into main
2026-03-14 11:49:43 -04:00
4116232b27
Merge pull request 'feat: WebUI diagnostics dashboard (Issue #562 )' ( #567 ) from sl-webui/issue-562-diagnostics into main
2026-03-14 11:49:39 -04:00
c7dcce18c2
feat: UWB anchor mount bracket wall/ceiling design (Issue #564 )
2026-03-14 11:47:07 -04:00
8e03a209be
feat: ROS2 sensor health monitor (Issue #566 )
...
Add sensor_health_node to saltybot_health_monitor package. Monitors 8
sensor topics for staleness, publishing DiagnosticArray on
/saltybot/diagnostics and MQTT JSON on saltybot/health.
Sensors monitored (configurable thresholds):
/camera/color/image_raw, /camera/depth/image_rect_raw,
/camera/color/camera_info, /scan, /imu/data,
/saltybot/uwb/range, /saltybot/battery, /saltybot/motor_daemon/status
Each sensor: OK/WARN/ERROR based on topic age vs warn_s/error_s thresholds.
Critical sensors (camera, lidar, imu, motor_daemon) escalate overall status.
Files added:
sensor_health_node.py — SensorWatcher + SensorHealthNode
config/sensor_health_params.yaml — per-sensor thresholds
launch/sensor_health.launch.py
test/test_sensor_health.py — 35 tests, all passing
setup.py/package.xml updated: sensor_msgs, diagnostic_msgs deps + new entry point.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:47:01 -04:00
sl-uwb
a4879b6b3f
feat: ESP32 UWB Pro anchor firmware — DS-TWR responder (Issue #544 )
...
Anchor firmware for Makerfabs ESP32 UWB Pro (DW3000 chip). Two anchors
mount on SaltyBot (port/starboard), USB-connected to Jetson Orin.
- DS-TWR responder: Poll→Resp→Final with ±10cm accuracy
- Streams +RANGE:<id>,<mm>,<rssi_dbm> on Serial 115200
- AT command interface: AT+RANGE?, AT+RANGE_ADDR=, AT+ID?
- ANCHOR_ID 0/1 set at build time (env:anchor0 / env:anchor1)
- PlatformIO config for Makerfabs MaUWB_DW3000 library
- udev rules for /dev/uwb-anchor0 /dev/uwb-anchor1 USB symlinks
- Pin map: SCK=18 MISO=19 MOSI=23 CS=21 RST=27 IRQ=34
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:45:29 -04:00
2180b61440
feat: ROS2 UWB position node (Issue #546 )
...
Add saltybot_uwb_position — ROS2 Python package that reads JSON range
measurements from an ESP32 DW3000 UWB tag over USB serial, trilaterates
the robot's absolute position from 3+ fixed infrastructure anchors, and
publishes position + TF2 to the rest of the stack.
Serial protocol (one JSON line per frame):
Full frame: {"ts":…, "ranges": [{"id":0,"d_mm":1500,"rssi":-65}, …]}
Per-anchor: {"id":0, "d_mm":1500, "rssi":-65.0}
Accepts both "d_mm" and "range_mm" field names.
Trilateration (trilateration.py, numpy, no ROS deps):
Linear least-squares: linearise sphere equations around anchor 0,
solve (N-1)x2 (2D) or (N-1)x3 (3D) system via np.linalg.lstsq.
2D mode (default): robot_z fixed, needs >=3 anchors.
3D mode (solve_z=true): full 3D, needs >=4 anchors.
Outlier rejection:
After initial solve, compute per-anchor residual |r_meas - r_pred|.
Reject anchors with residual > outlier_threshold_m (0.4 m default).
Re-solve with inliers if >= min_anchors remain.
Track consecutive outlier strikes; flag in /status after N strikes.
Kalman filter (KalmanFilter3D, constant-velocity, 6-state, numpy):
Predict-only coasting when anchors drop below minimum.
Q=0.05, R=0.10 (tunable).
Topics:
/saltybot/uwb/pose PoseStamped 10 Hz Kalman-filtered position
/saltybot/uwb/range/<id> UwbRange on arrival, raw per-anchor ranges
/saltybot/uwb/status String/JSON 10 Hz state+residuals+flags
TF2: uwb_link -> map (identity rotation)
Anchor config: flat float arrays in YAML.
Default layout: 4-anchor 5x5m room at 2m height.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:43:22 -04:00
c2d9adad25
feat: WebUI diagnostics dashboard (Issue #562 )
...
Standalone 3-file diagnostics dashboard (ui/diagnostics_panel.{html,js,css}).
No build step — serve the ui/ directory directly. roslib.js via CDN.
Panels:
- Battery: voltage (V), SOC (%), current (A) with large readouts + gauge bars
+ 2-minute sparkline history canvas, 4S LiPo thresholds
- Temperatures: CPU/GPU (Jetson tegrastats) + Board/STM32 + Motor L/R
color-coded temp boxes with mini progress bars (green<60 amber<75 red>75°C)
- Motor current: per-wheel current gauge bars + CMD value + balance_state label
Thresholds: warn 8A / crit 12A
- Resources: RAM / GPU memory / Disk — gauge bars with used/total display
Thresholds: warn 80% / crit 95%
- WiFi / Network: RSSI signal bars (5-level) + dBm readout + latency (ms)
MQTT broker status via mqtt_connected KeyValue
- ROS2 node health: full DiagnosticArray node list with OK/WARN/ERROR/STALE
badges, per-node message preview, MutationObserver count badge
Features:
- Auto 2 Hz refresh via rosbridge subscriptions (throttle_rate: 500ms)
- Pulsing refresh indicator dot on each update
- System status bar: HEALTHY/DEGRADED/FAULT/STALE badge + battery/thermal/net
- Alert thresholds: red/amber/green for every metric
- Responsive CSS grid: 3-col → 2-col → 1-col via media queries
- WS URL persisted in localStorage
ROS topics:
SUB /diagnostics diagnostic_msgs/DiagnosticArray
SUB /saltybot/balance_state std_msgs/String (JSON)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:41:43 -04:00
76668d8346
Merge pull request 'feat: RPLIDAR A1 mount bracket (Issue #561 )' ( #563 ) from sl-mechanical/issue-561-rplidar-mount into main
2026-03-14 11:41:10 -04:00
d8e5490a0e
feat: RPLIDAR A1 mount bracket (Issue #561 )
2026-03-14 11:40:17 -04:00
6409360428
Merge pull request 'feat: Pan/tilt gimbal servo driver for ST3215 bus servos (Issue #547 )' ( #559 ) from sl-firmware/issue-547-gimbal-servo into main
2026-03-14 11:40:02 -04:00
6c5ecc9e00
Merge pull request 'feat: ROS2 gimbal control node (Issue #548 )' ( #558 ) from sl-jetson/issue-548-gimbal-ros2 into main
2026-03-14 11:39:58 -04:00
df6b79d676
Merge pull request 'feat: WebUI gimbal control panel (Issue #551 )' ( #557 ) from sl-webui/issue-551-gimbal-webui into main
2026-03-14 11:36:49 -04:00
0dbd64a6f4
Merge pull request 'feat: Camera gimbal mount for RealSense D435i (Issue #552 )' ( #556 ) from sl-mechanical/issue-552-gimbal-mount into main
2026-03-14 11:36:31 -04:00
8e21201dd4
Merge pull request 'feat: Person-following head tracking (Issue #549 )' ( #555 ) from sl-perception/issue-549-head-tracking into main
2026-03-14 11:36:29 -04:00
80e3b23aec
Merge pull request 'feat: Phone voice command interface (Issue #553 )' ( #554 ) from sl-android/issue-553-voice-command into main
2026-03-14 11:36:27 -04:00
36643dd652
feat: Pan/tilt gimbal servo driver for ST3215 bus servos (Issue #547 )
...
- servo_bus.c/h: half-duplex USART3 driver for Feetech ST3215 servos at
1 Mbps; blocking TX/RX with CRC checksum; read/write position, torque
enable, speed; deg<->raw conversion (center=2048, 4096 counts/360°)
- gimbal.c/h: gimbal_t controller; 50 Hz feedback poll alternating pan/tilt
at 25 Hz each; clamps to ±GIMBAL_PAN/TILT_LIMIT_DEG soft limits
- jlink.c: dispatch JLINK_CMD_GIMBAL_POS (0x0B, 6-byte payload int16+int16+
uint16); jlink_send_gimbal_state() for JLINK_TLM_GIMBAL_STATE (0x84)
- main.c: servo_bus_init() + gimbal_init() on boot; gimbal_tick() in main
loop; gimbal_updated flag handler; GIMBAL_STATE telemetry at 50 Hz
- config.h: SERVO_BUS_UART/PORT/PIN/BAUD, GIMBAL_PAN/TILT_ID, GIMBAL_TLM_HZ,
GIMBAL_PAN/TILT_LIMIT_DEG
- jlink.h: CMD_GIMBAL_POS, TLM_GIMBAL_STATE, jlink_tlm_gimbal_state_t (10 B),
gimbal_updated/pan_x10/tilt_x10/speed volatile fields in JLinkState
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:38:06 -04:00
da6a17cdcb
feat: ROS2 gimbal control node (Issue #548 )
...
saltybot_gimbal ROS2 Python package for pan/tilt camera head control
via JLINK binary protocol over serial to STM32 (Issue #547 C side).
- gimbal_node.py: subscribes /saltybot/gimbal/cmd (Vector3: pan, tilt,
speed), publishes /saltybot/gimbal/state (JSON), /saltybot/gimbal/cmd_echo
- Services: /saltybot/gimbal/home (Trigger), /saltybot/gimbal/look_at
(Trigger + /saltybot/gimbal/look_at_target PointStamped)
- jlink_gimbal.py: JLINK codec matching jlink.h — CMD_GIMBAL_POS=0x0B,
TLM_GIMBAL_STATE=0x84, CRC16-CCITT, deg*10 encoding, speed register
- MotionAxis: trapezoidal velocity profile (configurable accel + speed)
- Configurable limits: pan ±150°, tilt ±45° (gimbal_params.yaml)
- Serial reconnect with configurable retry delay
- 48 unit tests — all passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:34:06 -04:00
cc3a65f4a4
feat: WebUI gimbal control panel (Issue #551 )
...
Adds a full gimbal control panel with live camera preview:
Standalone page (ui/gimbal_panel.html + .js + .css):
- Self-contained HTML page, no build step, served directly
- roslib.js via CDN, connects to rosbridge WebSocket
- 2-D canvas pan/tilt pad: click-drag + touch pointer capture
- Live camera stream (front/rear/left/right selector, base64 CompressedImage)
- FPS badge + angle overlay on video feed
- Preset positions: CENTER / LEFT / RIGHT / UP / DOWN
- Home button (0° / 0°)
- Person-tracking toggle → /gimbal/tracking_enabled
- Current angle display from /gimbal/state feedback
- WS URL persisted in localStorage
React component (GimbalPanel.jsx) + App.jsx integration:
- Same features in dashboard — TELEOP group → Gimbal tab
- Shares rosbridge connection from parent
- Mobile-responsive: stacks vertically on mobile, side-by-side on lg+
ROS topics:
PUB /gimbal/cmd geometry_msgs/Vector3
SUB /gimbal/state geometry_msgs/Vector3
PUB /gimbal/tracking_enabled std_msgs/Bool
SUB /camera/*/image_raw/compressed sensor_msgs/CompressedImage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:29:29 -04:00
c68b751590
feat: Person-following head tracking (Issue #549 )
...
Add saltybot_head_tracking — ROS2 Python node for automatic person-
following using dual-axis PID control targeting the pan/tilt camera head.
Pipeline:
1. Subscribe to /saltybot/objects (DetectedObjectArray from YOLOv8n)
2. Filter for class_id==0 (person); select best target by score:
score = 0.6 * 1/(1+dist_m) + 0.4 * confidence
(falls back to confidence-only when distance_m==0 / unknown)
3. Compute pixel error of bbox centre from image centre
4. Apply dead-zone (10 px default) to suppress micro-jitter
5. Convert pixel error to angle error via camera FOV
6. Independent PID controllers for pan and tilt axes
7. Accumulate PID output into absolute angle setpoint
8. Publish geometry_msgs/Point to /saltybot/gimbal/cmd:
x = pan_angle_deg, y = tilt_angle_deg, z = confidence
State machine:
IDLE -> waiting for first detection
TRACKING -> active PID
LOST -> hold last angle for hold_duration_s (3 s)
CENTERING -> return to (0, 0) at 20 deg/s -> IDLE
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:28:17 -04:00
0fd6ea92b0
feat: Camera gimbal mount bracket for RealSense D435i (Issue #552 )
2026-03-14 10:28:03 -04:00
sl-android
c249b2d74e
feat: Phone voice command interface (Issue #553 )
...
Add phone/voice_commander.py — Termux-based voice command listener for SaltyBot:
- Continuous wake word detection ('Hey Salty') via Whisper STT on short audio clips
- Command recording after wake word, transcribed with local Whisper (tiny/base/small)
- Parses go forward/back/left/right, stop, follow me, go home, look at me
- Publishes JSON to /saltybot/voice/cmd via ROS2 (rclpy) or rosbridge WebSocket
- TTS confirmation via termux-tts-speak; 'Yes?' prompt on wake word
- Fuzzy token-overlap fallback for wake word matching
- Flags: --host, --port, --model, --threshold, --record-sec, --no-tts, --debug
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:26:38 -04:00