Adds slcan setup script and saltybot_can_bridge ROS2 package implementing
full CAN bus integration between the Orin and the Mamba motor controller /
VESC motor controllers via a CANable 2.0 USB dongle (slcan interface).
- jetson/scripts/setup_can.sh: slcand-based bring-up/tear-down for slcan0
at 500 kbps with error handling (already up, device missing, retry)
- saltybot_can_bridge/mamba_protocol.py: CAN message ID constants and
encode/decode helpers for velocity, mode, e-stop, IMU, battery, VESC state
- saltybot_can_bridge/can_bridge_node.py: ROS2 node subscribing to /cmd_vel
and /estop, publishing /can/imu, /can/battery, /can/vesc/{left,right}/state
and /can/connection_status; background reader thread, watchdog zero-vel,
auto-reconnect every 5 s on CAN error
- config/can_bridge_params.yaml: default params (slcan0, VESC IDs 56/68,
Mamba ID 1, 0.5 s command timeout)
- test/test_can_bridge.py: 30 unit tests covering encode/decode round-trips
and edge cases — all pass without ROS2 or CAN hardware
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
127 lines
4.5 KiB
Bash
Executable File
127 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# setup_can.sh — Bring up CANable 2.0 (slcan/ttyACM0) as slcan0 at 500 kbps
|
||
# Issue: https://gitea.vayrette.com/seb/saltylab-firmware/issues/674
|
||
#
|
||
# This script uses slcand to expose the CANable 2.0 (USB CDC / slcan firmware)
|
||
# as a SocketCAN interface. It is NOT used for the gs_usb (native SocketCAN)
|
||
# firmware variant — use jetson/scripts/can_setup.sh for that.
|
||
#
|
||
# Usage:
|
||
# sudo ./setup_can.sh # bring up slcan0
|
||
# sudo ./setup_can.sh down # bring down slcan0 and kill slcand
|
||
# sudo ./setup_can.sh verify # candump one-shot check (Ctrl-C to stop)
|
||
#
|
||
# Environment overrides:
|
||
# CAN_DEVICE — serial device (default: /dev/ttyACM0)
|
||
# CAN_IFACE — SocketCAN name (default: slcan0)
|
||
# CAN_BITRATE — bit rate (default: 500000)
|
||
#
|
||
# Mamba CAN ID: 1 (0x001)
|
||
# VESC left: 56 (0x038)
|
||
# VESC right: 68 (0x044)
|
||
|
||
set -euo pipefail
|
||
|
||
DEVICE="${CAN_DEVICE:-/dev/ttyACM0}"
|
||
IFACE="${CAN_IFACE:-slcan0}"
|
||
BITRATE="${CAN_BITRATE:-500000}"
|
||
|
||
log() { echo "[setup_can] $*"; }
|
||
warn() { echo "[setup_can] WARNING: $*" >&2; }
|
||
die() { echo "[setup_can] ERROR: $*" >&2; exit 1; }
|
||
|
||
# Map numeric bitrate to slcand -s flag (0-8 map to standard CAN speeds)
|
||
bitrate_flag() {
|
||
case "$1" in
|
||
10000) echo "0" ;;
|
||
20000) echo "1" ;;
|
||
50000) echo "2" ;;
|
||
100000) echo "3" ;;
|
||
125000) echo "4" ;;
|
||
250000) echo "5" ;;
|
||
500000) echo "6" ;;
|
||
800000) echo "7" ;;
|
||
1000000) echo "8" ;;
|
||
*) die "Unsupported bitrate: $1 (choose 10k–1M)" ;;
|
||
esac
|
||
}
|
||
|
||
# ── up ─────────────────────────────────────────────────────────────────────
|
||
cmd_up() {
|
||
# Verify serial device is present
|
||
if [[ ! -c "$DEVICE" ]]; then
|
||
die "$DEVICE not found — is CANable 2.0 plugged in?"
|
||
fi
|
||
|
||
# If interface already exists, leave it alone
|
||
if ip link show "$IFACE" &>/dev/null; then
|
||
log "$IFACE is already up — nothing to do."
|
||
ip -details link show "$IFACE"
|
||
return 0
|
||
fi
|
||
|
||
local sflag
|
||
sflag=$(bitrate_flag "$BITRATE")
|
||
|
||
log "Starting slcand on $DEVICE → $IFACE at ${BITRATE} bps (flag -s${sflag}) …"
|
||
# -o open device, -c close on exit, -f forced, -s<N> speed, -S<baud> serial baud
|
||
slcand -o -c -f -s"${sflag}" -S 3000000 "$DEVICE" "$IFACE" \
|
||
|| die "slcand failed to start"
|
||
|
||
# Give slcand a moment to create the netdev
|
||
local retries=0
|
||
while ! ip link show "$IFACE" &>/dev/null; do
|
||
retries=$((retries + 1))
|
||
if [[ $retries -ge 10 ]]; then
|
||
die "Timed out waiting for $IFACE to appear after slcand start"
|
||
fi
|
||
sleep 0.2
|
||
done
|
||
|
||
log "Bringing up $IFACE …"
|
||
ip link set "$IFACE" up \
|
||
|| die "ip link set $IFACE up failed"
|
||
|
||
log "$IFACE is up."
|
||
ip -details link show "$IFACE"
|
||
}
|
||
|
||
# ── down ───────────────────────────────────────────────────────────────────
|
||
cmd_down() {
|
||
log "Bringing down $IFACE …"
|
||
if ip link show "$IFACE" &>/dev/null; then
|
||
ip link set "$IFACE" down || warn "Could not set $IFACE down"
|
||
else
|
||
warn "$IFACE not found — already down?"
|
||
fi
|
||
|
||
# Kill any running slcand instances bound to our device
|
||
if pgrep -f "slcand.*${DEVICE}" &>/dev/null; then
|
||
log "Stopping slcand for $DEVICE …"
|
||
pkill -f "slcand.*${DEVICE}" || warn "pkill returned non-zero"
|
||
fi
|
||
log "Done."
|
||
}
|
||
|
||
# ── verify ─────────────────────────────────────────────────────────────────
|
||
cmd_verify() {
|
||
if ! ip link show "$IFACE" &>/dev/null; then
|
||
die "$IFACE is not up — run '$0 up' first"
|
||
fi
|
||
log "Listening on $IFACE — expecting frames from Mamba (0x001), VESC left (0x038), VESC right (0x044)"
|
||
log "Press Ctrl-C to stop."
|
||
exec candump "$IFACE"
|
||
}
|
||
|
||
# ── main ───────────────────────────────────────────────────────────────────
|
||
CMD="${1:-up}"
|
||
case "$CMD" in
|
||
up) cmd_up ;;
|
||
down) cmd_down ;;
|
||
verify) cmd_verify ;;
|
||
*)
|
||
echo "Usage: $0 [up|down|verify]"
|
||
exit 1
|
||
;;
|
||
esac
|