597 Commits

Author SHA1 Message Date
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
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
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
59d164944d Merge origin/sl-controls/issue-533-battery-adc — resolve jlink conflicts
Keep both Issue #531 (PID_RESULT telemetry) and Issue #533 (BATTERY
telemetry) additions in include/jlink.h and src/jlink.c.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:10:26 -05:00
6e09d13dfc Merge pull request 'feat: Audio pipeline — wake word + STT + TTS on Jabra SPEAK 810 (Issue #503)' (#543) from sl-jetson/issue-503-audio-pipeline into main 2026-03-07 10:07:16 -05:00
salty
02217443ea chore: merge CAD files and design docs from seb/saltylab seed repo
Consolidating seb/saltylab into saltylab-firmware before deleting the seed repo.
- 16 OpenSCAD CAD models → cad/
- Design docs (SALTYLAB.md, PLATFORM.md, AGENTS.md, board-viz.html) → docs/
2026-03-07 10:04:24 -05:00
14164089dc feat: Audio pipeline end-to-end (Issue #503)
- Add VoskSTT class to audio_utils.py: offline Vosk STT backend as
  low-latency CPU alternative to Whisper for Jetson deployments
- Update audio_pipeline_node.py: stt_backend param ("whisper"/"vosk"),
  Vosk loading with Whisper fallback, CPU auto-detection for Whisper,
  dual-backend _process_utterance dispatch, STT/<backend> log prefix
- Update audio_pipeline_params.yaml: add stt_backend and vosk_model_path
- Add test/test_audio_pipeline.py: 40 unit tests covering EnergyVAD,
  PCM conversion, AudioBuffer, UtteranceSegmenter, VoskSTT, JabraAudioDevice,
  AudioMetrics, AudioState
- Integrate into full_stack.launch.py: audio_pipeline at t=5s with
  enable_audio_pipeline and audio_stt_backend args

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:03:31 -05:00
6d316514da Merge remote-tracking branch 'origin/sl-firmware/issue-531-pid-autotune' 2026-03-07 10:03:24 -05:00
2d97033539 Merge remote-tracking branch 'origin/sl-perception/issue-532-depth-costmap' 2026-03-07 10:03:24 -05:00
71ec357c93 Merge remote-tracking branch 'origin/sl-webui/issue-534-teleop-webui' 2026-03-07 10:03:24 -05:00
cc0ffd1999 feat: Battery voltage ADC driver with DMA sampling (Issue #533)
STM32F7 ADC driver for battery voltage/current monitoring using
DMA-based continuous sampling, IIR low-pass filter, voltage divider
calibration, and USART telemetry to Jetson. Integrates with power
management for low-battery sleep (Issue #467).

Implementation:
- include/battery_adc.h: New driver header with calibration struct and
  public API (init, tick, get_voltage_mv, get_current_ma, calibrate,
  publish, check_pm, is_low, is_critical)
- src/battery_adc.c: ADC3 continuous-scan DMA (DMA2_Stream0/Ch2), 4x
  hardware oversampling of both Vbat (PC1/IN11) and Ibat (PC3/IN13),
  IIR LPF (alpha=1/8, cutoff ~4 Hz at 100 Hz tick rate), calibration
  with ±500 mV offset clamp, 3S/4S auto-detection, 1 Hz USART publish
- include/jlink.h + src/jlink.c: Add JLINK_TLM_BATTERY (0x82) telemetry
  type and jlink_tlm_battery_t (10-byte packed struct), implement
  jlink_send_battery_telemetry() using CRC16-XModem framing
- include/power_mgmt.h + src/power_mgmt.c: Add
  power_mgmt_notify_battery() — triggers STOP-mode sleep when Vbat
  sustains critical level (Issue #467)
- test/test_battery_adc.c: 27 unit tests (27/27 passing): voltage
  conversion, calibration offset/scale, IIR LPF convergence, SoC
  estimation (3S/4S), low/critical flags, PM notification timing,
  calibration reset, publish rate-limiting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:01:02 -05:00
19a30a1c4f feat: PID auto-tune for balance mode (Issue #531)
Implement Ziegler-Nichols relay feedback auto-tuning with flash persistence:

Firmware (STM32F722):
- pid_flash.c/h: erase+write Kp/Ki/Kd to flash sector 7 (0x0807FFC0),
  magic-validated; load on boot to restore saved tune
- jlink.h: add JLINK_CMD_PID_SAVE (0x0A) and JLINK_TLM_PID_RESULT (0x83)
  with jlink_tlm_pid_result_t struct and pid_save_req flag in JLinkState
- jlink.c: dispatch JLINK_CMD_PID_SAVE -> pid_save_req; add
  jlink_send_pid_result() to confirm flash write outcome over USART1
- main.c: load saved PID from flash after balance_init(); handle
  pid_save_req in main loop (disarmed-only, erase stalls CPU ~1s)

Jetson ROS2 (saltybot_pid_autotune):
- pid_autotune_node.py: add Ki to Ziegler-Nichols formula (ZN PID:
  Kp=0.6Ku, Ki=1.2Ku/Tu, Kd=0.075KuTu); add JLink serial client that
  sends JLINK_CMD_PID_SET + JLINK_CMD_PID_SAVE after tuning completes
- autotune_config.yaml: add jlink_serial_port and jlink_baud_rate params

Trigger: ros2 service call /saltybot/autotune_pid std_srvs/srv/Trigger

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 09:56:19 -05:00
f71fdae747 feat: Depth-to-costmap plugin for RealSense D435i (Issue #532)
Add saltybot_depth_costmap — a Nav2 costmap2d plugin that converts
D435i depth images directly into obstacle markings on both local and
global costmaps.

Pipeline:
  1. Subscribe to /camera/depth/image_rect_raw (16UC1 mm) + camera_info
  2. Back-project depth pixels to 3D using pinhole camera intrinsics
  3. Transform points to costmap global_frame via TF2
  4. Apply configurable height filter (min_height..max_height above ground)
  5. Mark obstacle cells as LETHAL_OBSTACLE
  6. Inflate neighbours within inflation_radius as INSCRIBED_INFLATED_OBSTACLE

Parameters:
  min_height: 0.05 m       — floor clearance (ignores ground returns)
  max_height: 0.80 m       — ceiling cutoff (ignores lights/ceiling)
  obstacle_range: 3.5 m    — max marking distance from camera
  clearing_range: 4.0 m    — max distance processed at all
  inflation_radius: 0.10 m — in-layer inflation (works before inflation_layer)
  downsample_factor: 4     — process 1 of N rows+cols (~19k pts @ 640×480)

Integration (#478):
  - Added depth_costmap_layer to local_costmap plugins list
  - Added depth_costmap_layer to global_costmap plugins list
  - Plugin registered via pluginlib (plugin.xml)

Files:
  jetson/ros2_ws/src/saltybot_depth_costmap/
    CMakeLists.txt, package.xml, plugin.xml
    include/saltybot_depth_costmap/depth_costmap_layer.hpp
    src/depth_costmap_layer.cpp
  jetson/ros2_ws/src/saltybot_bringup/config/nav2_params.yaml (updated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 09:52:18 -05:00
916ad36ad5 feat(webui): teleop web interface with live camera stream (Issue #534)
Adds TeleopWebUI component — a dedicated browser-based remote control
panel combining live video and joystick teleoperation in one view:

- Live camera stream (front/rear/left/right) via rosbridge CompressedImage
- Virtual joystick (canvas-based, touch + mouse, 10% deadzone)
- WASD / arrow-key keyboard fallback, Space for quick stop
- Speed presets: SLOW (20%), NORMAL (50%), FAST (100%)
- Latching E-stop button with pulsing visual indicator
- Real-time linear/angular velocity display
- Mobile-responsive: stacks vertically on small screens, side-by-side on lg+
- Added TELEOP tab group → Drive tab in App.jsx

Topics: /camera/<name>/image_raw/compressed (subscribe)
        /cmd_vel geometry_msgs/Twist (publish)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 09:51:14 -05:00
fc43135144 feat: Spring-loaded phone mount bracket for T-slot rail (Issue #535)
Parametric OpenSCAD design for 2020 T-slot rail phone mount bracket.
Adjustable width 60-85mm, spring-loaded cam quick-release lever,
vibration-dampening flexure rib grip pads. PETG 3D-printable, no supports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 09:49:10 -05:00
e67783f313 Merge remote-tracking branch 'origin/sl-android/issue-521-esc-debug-cleanup' 2026-03-06 23:34:45 -05:00
68b6410b24 Merge remote-tracking branch 'origin/sl-jetson/issue-523-motor-daemon' 2026-03-06 23:34:45 -05:00
5b7ee63d1e Merge remote-tracking branch 'origin/sl-controls/issue-522-usart6-truncation' 2026-03-06 23:34:45 -05:00
4f33e4e88d Merge pull request 'fix: USB CDC TX investigation (Issue #524)' (#525) from sl-mechanical/issue-524-usb-cdc-tx into main 2026-03-06 23:34:17 -05:00
54bc2509c1 Merge pull request 'fix: IMU calibration (Issue #520)' (#530) from sl-firmware/issue-520-imu-calibration into main 2026-03-06 23:34:16 -05:00
b0a5041261 fix: MPU6000 IMU calibration SPI/DCache issue (Issue #520)
Three bugs prevented mpu6000_is_calibrated() from returning true,
blocking arming and balance mode:

1. WHO_AM_I single-attempt: one SPI glitch returning 0x00 caused
   icm42688_init() to return -128, skipping mpu6000_calibrate()
   entirely. Fix: retry WHO_AM_I up to 3 times with 10ms gaps.

2. icm42688_read() rx[15] uninitialized: if HAL_SPI_TransmitReceive()
   failed, garbage stack data was accumulated as gyro bias. Fix: zero-
   init rx[15] so failed transfers produce zero data.

3. mpu6000_calibrate() raw uninitialized: UB if icm42688_read() is
   a no-op (imu_type mismatch). Fix: zero-init raw each iteration.

Also add SCB_InvalidateDCache_by_Addr() on SPI rx buffers in rreg()
and icm42688_read() for DCache coherency. Currently a no-op (DCache
is not enabled), but required if SCB_EnableDCache() is added — stack
buffers in SRAM2 are in the cacheable memory region on STM32F7.

Fix misleading DCache comment in icm42688.c (claimed DCache was
disabled by main.c; actually SCB_EnableDCache() is never called).

Build: 59904 bytes Flash (+512), 17100 bytes RAM — SUCCESS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:14:49 -05:00
7141e12320 feat: Integration test suite expanded (Issue #504) - resolve conflicts 2026-03-06 23:10:42 -05:00
sl-android
d5246fe3a8 chore: Guard ESC debug output behind compile flag (Issue #521)
- esc_hoverboard.c: huart2 static in production; non-static only under
  #ifdef DEBUG_MOTOR_TEST (needed by R command in jetson_uart.c)
- esc_hoverboard.c: UART5 diagnostic in hoverboard_backend_init() and
  per-packet printf in hoverboard_backend_send() guarded by same flag
- esc_hoverboard.c: #include <stdio.h> also guarded (not needed in production)
- jetson_uart.c: R (baud sweep) and X (GPIO test) commands guarded by
  #ifdef DEBUG_MOTOR_TEST — not compiled into production firmware

Production build: no debug output, static huart2, no R/X commands.
Debug build: define DEBUG_MOTOR_TEST to re-enable all diagnostics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:07:36 -05:00
7ad8c82da6 feat: Orin motor control daemon (Issue #523)
Add saltybot_motor_daemon ROS2 package — Python daemon that subscribes
to /cmd_vel and drives the FC via W<speed>,<steer>\n over /dev/ttyTHS1
at 921600 baud.

- motor_daemon_node.py: 50 Hz fixed-rate TX, 200ms safety watchdog,
  Twist→ESC conversion (±1000 range), FC ack parsing (W:<s>,<st>),
  periodic ? status query, /diagnostics publisher, auto-reconnect
- config/motor_daemon_params.yaml: all tunable params with comments
- launch/motor_daemon.launch.py: parameterised launch file
- test/test_motor_daemon.py: 25 unit tests (all passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:04:07 -05:00
b128e164e7 fix: USART6 TX mutex to prevent truncated output (Issue #522)
USART1 IDLE interrupt (DMA circular RX) was calling HAL_UART_IRQHandler
mid-frame during polling HAL_UART_Transmit, resetting gState and causing
leading nulls / truncated frames on the Jetson telemetry link at 921600 baud.

Fix: introduce jlink_tx_locked() which disables USART1_IRQn around every
blocking HAL_UART_Transmit call, preventing IRQHandler from corrupting
gState while the TX loop is running.  A s_tx_busy flag drops any
re-entrant caller (ESC debug, future USART6/VESC paths).

Both jlink_send_telemetry (50 Hz) and jlink_send_power_telemetry (1 Hz)
now use jlink_tx_locked().  Also correct the stale config.h comment that
misidentified the Jetson link as USART6 (it moved to USART1 in Issue #120).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:02:57 -05:00
e28f1549cb feat: Orin motor control daemon (Issue #523)
Add saltybot_motor_daemon ROS2 package — Python daemon that subscribes
to /cmd_vel and drives the FC via W<speed>,<steer>\n over /dev/ttyTHS1
at 921600 baud.

- motor_daemon_node.py: 50 Hz fixed-rate TX, 200ms safety watchdog,
  Twist→ESC conversion (±1000 range), FC ack parsing (W:<s>,<st>),
  periodic ? status query, /diagnostics publisher, auto-reconnect
- config/motor_daemon_params.yaml: all tunable params with comments
- launch/motor_daemon.launch.py: parameterised launch file
- test/test_motor_daemon.py: 25 unit tests (all passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:02:57 -05:00
dd4eabf218 fix: Investigate USB CDC TX failure (Issue #524)
Root causes confirmed from code audit:
1. DCache coherency: USB OTG FS reads physical SRAM while CPU writes through
   DCache. Fix: MPU Region 0 marks 512B aligned USB buffer struct non-cacheable
   (TEX=1, C=0, B=0) before HAL_PCD_Init(). DCache stays enabled globally.
2. IWDG ordering: safety_init() (IWDG start) deferred after all peripheral inits
   to avoid watchdog reset during mpu6000_calibrate() (~510ms blocking).

DMA conflicts, GPIO conflicts, clock tree, and interrupt priorities all ruled out
with evidence. Full findings documented in USB_CDC_BUG.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:01:00 -05:00
Salty Bead
3bb4b71cea feat: motor bench test working — UART5 ESC + USART6 Jetson + W command
- Fix UART5 ESC: PC12=TX, PD2=RX @ 115200 baud (was USART2/38400)
- Add jetson_uart.c: USART6 command interface (A/D/E/Z/H/C/W/R/X/? commands)
- Add W command: persistent direct motor test (sets globals, main loop sends at 50Hz)
- Fix power_mgmt: keep UART5 clock active, PC7 EXTI wake source
- Fix main loop: direct_test_speed/steer override disarmed zero-send
- Add boot banner on USART6

Tested: Orin -> FC USART6 -> FC UART5 -> EFeru ESC -> both motors spin
2026-03-06 22:35:24 -05:00
f14ce5c3ba Merge remote-tracking branch 'origin/sl-perception/issue-469-terrain-classification' 2026-03-06 17:37:27 -05:00
2e2ed2d0a7 Merge remote-tracking branch 'origin/sl-controls/issue-506-launch-profiles' 2026-03-06 17:37:27 -05:00
5b9e9dd412 Merge pull request 'feat: Headscale VPN auto-connect (Issue #502)' (#517) from sl-jetson/issue-502-headscale-vpn into main 2026-03-06 17:37:07 -05:00
706a67c0b7 Merge pull request 'feat: Charging dock hardware design (Issue #505)' (#516) from sl-webui/issue-504-integration-tests into main 2026-03-06 17:37:06 -05:00
8d58d5e34c feat: Terrain classification for speed adaptation (Issue #469)
Implement multi-sensor terrain classification using RealSense D435i depth and RPLIDAR A1M8:

- saltybot_terrain_classification: New ROS2 package for terrain classification
- TerrainClassifier: Rule-based classifier matching depth variance + reflectance to terrain type
  (smooth/carpet/grass/gravel) with hysteresis + confidence scoring
- DepthExtractor: Extracts roughness from depth discontinuities and surface gradients
- LidarExtractor: Extracts reflectance from RPLIDAR scan intensities
- terrain_classification_node: 10Hz node fusing both sensors, publishes:
  - /saltybot/terrain_type (JSON with type, confidence, speed_scale)
  - /saltybot/terrain_type_string (human-readable type)
  - /saltybot/terrain_speed_scale (0.0-1.0 speed multiplier for smooth/carpet/grass/gravel)

Speed scales: smooth=1.0, carpet=0.9, grass=0.75, gravel=0.6

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 16:43:21 -05:00
d3eca7bebc feat: Integration test suite (Issue #504)
Add comprehensive integration testing for complete ROS2 system stack:

Integration Tests (test_integration_full_stack.py):
  - Verifies all ROS2 nodes launch successfully
  - Checks critical topics are published (sensors, nav, control)
  - Validates system component health and stability
  - Tests launch file validity and configuration
  - Covers indoor/outdoor/follow modes

Launch Testing (test_launch_full_stack.py):
  - Validates launch file syntax and configuration
  - Verifies all required packages are installed
  - Checks launch sequence timing
  - Validates conditional logic for optional components

Test Coverage:
  ✓ SLAM/RTAB-Map (indoor mode)
  ✓ Nav2 navigation stack
  ✓ Perception (YOLOv8n person detection)
  ✓ Control (cmd_vel bridge, STM32 bridge)
  ✓ Audio pipeline and monitoring
  ✓ Sensors (LIDAR, RealSense, UWB, CSI cameras)
  ✓ Battery and temperature monitoring
  ✓ Autonomous docking behavior
  ✓ TF2 tree and odometry

Usage:
  pytest test/test_integration_full_stack.py -v
  pytest test/test_launch_full_stack.py -v

Documentation:
  See test/README_INTEGRATION_TESTS.md for detailed information.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 16:42:38 -05:00
8d67d06857 feat: Integration test suite (Issue #504)
Add comprehensive integration testing for complete ROS2 system stack:

Integration Tests (test_integration_full_stack.py):
  - Verifies all ROS2 nodes launch successfully
  - Checks critical topics are published (sensors, nav, control)
  - Validates system component health and stability
  - Tests launch file validity and configuration
  - Covers indoor/outdoor/follow modes

Launch Testing (test_launch_full_stack.py):
  - Validates launch file syntax and configuration
  - Verifies all required packages are installed
  - Checks launch sequence timing
  - Validates conditional logic for optional components

Test Coverage:
  ✓ SLAM/RTAB-Map (indoor mode)
  ✓ Nav2 navigation stack
  ✓ Perception (YOLOv8n person detection)
  ✓ Control (cmd_vel bridge, STM32 bridge)
  ✓ Audio pipeline and monitoring
  ✓ Sensors (LIDAR, RealSense, UWB, CSI cameras)
  ✓ Battery and temperature monitoring
  ✓ Autonomous docking behavior
  ✓ TF2 tree and odometry

Usage:
  pytest test/test_integration_full_stack.py -v
  pytest test/test_launch_full_stack.py -v

Documentation:
  See test/README_INTEGRATION_TESTS.md for detailed information.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 16:42:31 -05:00
e5329391bc feat: Add parameter profile YAML files for Nav2 (Issue #506)
- profile_indoor.yaml: Conservative settings (0.4 m/s, 0.35m inflation)
- profile_outdoor.yaml: Moderate settings (0.8 m/s, 0.3m inflation)
- profile_demo.yaml: Agile settings (0.6 m/s, 0.32m inflation)

Each profile customizes velocity limits, costmap inflation, and obstacle detection.
2026-03-06 16:42:31 -05:00
5d17b6c501 feat: Issue #506 — Update nav2.launch.py for profile support
Add profile argument to nav2.launch.py to accept launch profile parameter
and log profile selection for debugging/monitoring.

Changes:
- Add profile_arg declaration with choices (indoor/outdoor/demo)
- Add profile substitution and log output
- Update docstring with profile documentation

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 16:42:31 -05:00
b5acb32ee6 feat: Issue #506 — Update full_stack.launch.py for profile support
Add profile argument and documentation to full_stack.launch.py for
Issue #506 launch parameter profiles. Updated to support:
- profile:=indoor (conservative)
- profile:=outdoor (moderate)
- profile:=demo (agile with tricks/social features)

Changes:
- Add profile_arg declaration
- Add profile substitution handle
- Update docstring with profile examples
- Ready for profile-based Nav2 parameter overrides

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 16:42:31 -05:00
bbfcd2a9d1 feat: Issue #506 — Launch parameter profiles (indoor/outdoor/demo)
Implement profile-based parameter overrides for Nav2, costmap, and behavior
server configurations. Profiles predefine parameter sets for different
deployment scenarios.

New files:
- config/profiles/indoor.yaml: Conservative (0.2 m/s, tight geofence, no GPS)
- config/profiles/outdoor.yaml: Moderate (0.5 m/s, wide geofence, GPS-enabled)
- config/profiles/demo.yaml: Agile (0.3 m/s, tricks/social features enabled)
- saltybot_bringup/profile_loader.py: YAML loader and parameter merger utility

Supports: ros2 launch saltybot_bringup full_stack.launch.py profile:=<profile>

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 16:42:31 -05:00
5add2cab51 Merge remote-tracking branch 'origin/sl-mechanical/issue-505-charging-dock'
# Conflicts:
#	phone/INSTALL_MOTOR_TEST.md
#	phone/MOTOR_TEST_JOYSTICK.md
#	phone/motor_test_joystick.py
2026-03-06 14:59:59 -05:00
892d0a2089 Merge remote-tracking branch 'origin/sl-android/issue-513-phone-joystick'
# Conflicts:
#	phone/MOTOR_TEST_JOYSTICK.md
#	phone/motor_test_joystick.py
2026-03-06 14:00:21 -05:00
678fd221f5 Merge pull request 'feat: Remove ELRS arm requirement (Issue #512)' (#514) from sl-firmware/issue-512-autonomous-arming into main 2026-03-06 12:52:05 -05:00
sl-android
f49e84b8bb feat: Phone-based motor test joystick app (Issue #513)
Implements terminal-based curses UI for interactive bench testing of SaltyBot motors via Termux.

Features:
- Interactive keyboard-based joystick (W/A/S/D or arrow keys)
- Conservative velocity defaults: 0.1 m/s linear, 0.3 rad/s angular
- Real-time velocity feedback with bar graphs
- Spacebar e-stop (instantly zeros velocity)
- 500ms timeout safety (sends zero velocity if idle)
- Dual backend: ROS2 (/cmd_vel) or WebSocket
- Graceful fallback if ROS2 unavailable

Safety Features:
- Conservative defaults (0.1/0.3 m/s)
- E-stop button (spacebar)
- 500ms timeout (sends zero if idle)
- Input clamping and exponential decay
- Status/warning displays

Files:
- motor_test_joystick.py: Main application
- MOTOR_TEST_JOYSTICK.md: User documentation
- INSTALL_MOTOR_TEST.md: Installation guide

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 11:47:15 -05:00
48e9af60a9 feat: Remove ELRS arm requirement for autonomous operation (Issue #512)
Enable Jetson autonomous arming while keeping RC as optional override.

Key Changes:
=============

1. RC Kill Switch (CH5 OFF) → Emergency Stop (not disarm)
   - Motors cutoff immediately on kill switch
   - Robot remains armed (allows re-arm without re-initializing)
   - Maintains kill switch safety for emergency situations

2. RC Disarm Only on Explicit CH5 Falling Edge (while RC alive)
   - RC disconnect doesn't disarm Jetson-controlled missions
   - RC failsafe timer (500ms) still handles signal loss

3. Jetson Autonomous Arming (via CDC 'A' command)
   - Works independently of RC state
   - Requires: calibrated IMU, robot level (±10°), no estop active
   - Uses same 500ms arm-hold safety as RC

4. All Safety Preserved
   - Arming hold timer: 500ms
   - Tilt limit: ±10° level
   - IMU calibration required
   - Remote E-stop override
   - RC failsafe: 500ms signal loss = disarm
   - Jetson timeout: 500ms heartbeat = zero motors

Command Protocol (CDC):
   A = arm (Jetson)
   D = disarm (Jetson)
   E/Z = estop / clear estop
   H = heartbeat (keep-alive)
   C<spd>,<str> = drive command

Behavior Matrix:
   RC disconnected      → Jetson-armed stays armed, normal operation
   RC connected + armed → Both Jetson and RC can arm, blended control
   RC kill switch (CH5) → Emergency stop + can re-arm via Jetson 'A'
   RC signal lost       → Disarm after 500ms (failsafe)

See AUTONOMOUS_ARMING.md for complete protocol and testing checklist.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 11:46:03 -05:00
e4116dffc0 feat: Remove ELRS arm requirement for autonomous operation (Issue #512)
Enable Jetson autonomous arming while keeping RC as optional override.

Changes:
- RC kill switch (CH5 OFF) now triggers emergency stop instead of disarm
  → Allows Jetson-armed robots to remain armed when RC disconnects
  → Maintains kill switch safety for emergency situations

- RC disarm only triggers on explicit CH5 falling edge (RC still alive)
  → RC disconnect doesn't disarm Jetson-controlled missions
  → RC failsafe timer (500ms) handles signal loss separately

- Jetson arming via CDC 'A' command works independently of RC state
  → Robots can operate fully autonomous without RC transmitter
  → Heartbeat timeout (500ms) prevents runaway if Jetson crashes

Safety maintained:
- Arming hold timer: 500ms (prevents accidental arm)
- Tilt limit: ±10° level required
- IMU calibration: Required before any arm attempt
- Remote E-stop: Blocks all arming
- RC failsafe: 500ms signal loss = disarm
- Jetson timeout: 500ms heartbeat = zero motors

Command protocol (unchanged):
- Jetson: A=arm, D=disarm, E=estop, Z=clear estop
- RC: CH5 switch (optional override)
- Heartbeat: H command every ≤500ms
- Drive: C<speed>,<steer> every ≤200ms

See AUTONOMOUS_ARMING.md for complete protocol and testing checklist.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 11:45:12 -05:00
b0c2b5564d feat: Add Issue #505 CAD - 24V Charging Dock OpenSCAD Models
CAD implementation files for Issue #505 (24V charging dock upgrade):

- charging_dock_505.scad: Main dock assembly
  * Base plate: 340×320×12 mm (enlarged for 240W PSU)
  * Back wall: 250×85×10 mm (pogo pin housing, LED bezel recess)
  * V-guide rails: 100mm deep, self-centering funnel (print 2×)
  * ArUco marker frame: ID 42 (DICT_4X4_250), 15cm mast
  * PSU bracket: Sized for Mean Well IRM-240-24 (210×108×56 mm)
  * LED bezel: 4× status indicators (SEARCHING/ALIGNED/CHARGING/FULL)

- charging_dock_receiver_505.scad: Robot-side receiver variants
  * Lab receiver: Stem collar mount (SaltyLab)
  * Rover receiver: Deck flange mount (SaltyRover)
  * Tank receiver: Skid plate mount + extended nose (SaltyTank)
  * Common contact geometry: 20mm CL-to-CL brass pads, V-nose guide
  * Wire bore: 3mm (supports 12 AWG charging wires)

Key changes from Issue #159 (5V):
- PSU dimensions: 63×45×28 mm → 210×108×56 mm
- Base/bracket enlarged accordingly
- ArUco ID: 0 → 42
- Contact geometry unchanged (compatible with Issue #159 receivers)
- Pogo pins, V-guides, LED circuit identical

Files ready for:
- STL export via OpenSCAD render commands
- 3D printing (PETG recommended)
- Assembly integration with docking node (#489)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-06 11:44:28 -05:00
1f4929b68c Merge remote-tracking branch 'origin/sl-jetson/issue-477-urdf'
# Conflicts:
#	jetson/config/RECOVERY_BEHAVIORS.md
2026-03-06 11:43:31 -05:00
d97fa5fab0 Merge remote-tracking branch 'origin/sl-webui/issue-482-behavior-tree'
# Conflicts:
#	jetson/ros2_ws/src/saltybot_bringup/behavior_trees/autonomous_coordinator.xml
#	jetson/ros2_ws/src/saltybot_bringup/launch/autonomous_mode.launch.py
2026-03-06 11:43:26 -05:00
a3d3ea1471 Merge remote-tracking branch 'origin/sl-perception/issue-478-costmaps'
# Conflicts:
#	jetson/ros2_ws/src/saltybot_bringup/config/nav2_params.yaml
2026-03-06 11:43:11 -05:00