refactor: clean up all remaining stm32_protocol/mamba_protocol string refs

Update test files, conftest, protocol_defs, docstrings, and inline comments
to use the new module names: esp32_protocol and balance_protocol.

- saltybot_can_e2e_test tests + conftest: mamba_protocol → balance_protocol
- saltybot_bridge tests: stm32_protocol → esp32_protocol
- esp32_protocol.py + balance_protocol.py self-referencing comments fixed
- jlink_gimbal.py comment updated
- test_balance_protocol_invalid_mode_raises (was test_mamba_...)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sl-webui 2026-04-04 08:49:12 -04:00
parent 34162784ab
commit 40bbf31ba5
17 changed files with 42 additions and 42 deletions

View File

@ -39,7 +39,7 @@ import serial
from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
from std_msgs.msg import String from std_msgs.msg import String
from .stm32_protocol import ( from .esp32_protocol import (
BAUD_RATE, BAUD_RATE,
FrameParser, FrameParser,
RcChannels, RcChannels,

View File

@ -1,4 +1,4 @@
"""stm32_protocol.py — Inter-board UART frame codec (ESP32 BALANCE ↔ ESP32 IO). """esp32_protocol.py — Inter-board UART frame codec (ESP32 BALANCE ↔ ESP32 IO).
File name retained for import compatibility. This module implements the binary File name retained for import compatibility. This module implements the binary
serial protocol that runs between the two ESP32-S3 embedded boards. serial protocol that runs between the two ESP32-S3 embedded boards.
@ -16,7 +16,7 @@ CRC8 covers: LEN + TYPE + PAYLOAD (polynomial 0x07, init 0x00).
Max payload: 64 bytes. No ETX byte. Max payload: 64 bytes. No ETX byte.
Note: the Orin communicates with ESP32 BALANCE via CAN (CANable2/slcan0), Note: the Orin communicates with ESP32 BALANCE via CAN (CANable2/slcan0),
NOT via this serial protocol. See mamba_protocol.py for the CAN frame codec. NOT via this serial protocol. See balance_protocol.py for the CAN frame codec.
Message types IO BALANCE: Message types IO BALANCE:
0x01 RC_CHANNELS raw RC channel values (CRSF or ELRS) 0x01 RC_CHANNELS raw RC channel values (CRSF or ELRS)

View File

@ -1,8 +1,8 @@
""" """
remote_estop_node.py -- Remote e-stop bridge: MQTT -> STM32 USB CDC remote_estop_node.py -- Remote e-stop bridge: MQTT -> ESP32-S3 IO USB CDC
{"kill": true} -> writes 'E\n' to STM32 (ESTOP_REMOTE, immediate motor cutoff) {"kill": true} -> writes 'E\n' to ESP32-S3 IO (ESTOP_REMOTE, immediate motor cutoff)
{"kill": false} -> writes 'Z\n' to STM32 (clear latch, robot can re-arm) {"kill": false} -> writes 'Z\n' to ESP32-S3 IO (clear latch, robot can re-arm)
Cellular watchdog: if MQTT link drops for > cellular_timeout_s while in Cellular watchdog: if MQTT link drops for > cellular_timeout_s while in
AUTO mode, automatically sends 'F\n' (ESTOP_CELLULAR_TIMEOUT). AUTO mode, automatically sends 'F\n' (ESTOP_CELLULAR_TIMEOUT).
@ -26,7 +26,7 @@ class RemoteEstopNode(Node):
def __init__(self): def __init__(self):
super().__init__('remote_estop_node') super().__init__('remote_estop_node')
self.declare_parameter('serial_port', '/dev/stm32-bridge') self.declare_parameter('serial_port', '/dev/esp32-io')
self.declare_parameter('baud_rate', 921600) self.declare_parameter('baud_rate', 921600)
self.declare_parameter('mqtt_host', 'mqtt.example.com') self.declare_parameter('mqtt_host', 'mqtt.example.com')
self.declare_parameter('mqtt_port', 1883) self.declare_parameter('mqtt_port', 1883)

View File

@ -1,5 +1,5 @@
""" """
Unit tests for JetsonSTM32 command serialization logic. Unit tests for JetsonESP32-S3 IO command serialization logic.
Tests Twistspeed/steer conversion and frame formatting. Tests Twistspeed/steer conversion and frame formatting.
Run with: pytest jetson/ros2_ws/src/saltybot_bridge/test/test_cmd.py Run with: pytest jetson/ros2_ws/src/saltybot_bridge/test/test_cmd.py
""" """

View File

@ -139,10 +139,10 @@ class TestModeGate:
MODE_ASSISTED = 1 MODE_ASSISTED = 1
MODE_AUTONOMOUS = 2 MODE_AUTONOMOUS = 2
def _apply_mode_gate(self, stm32_mode, current_speed, current_steer, def _apply_mode_gate(self, balance_mode, current_speed, current_steer,
target_speed, target_steer, step=10): target_speed, target_steer, step=10):
"""Mirror of _control_cb mode gate logic.""" """Mirror of _control_cb mode gate logic."""
if stm32_mode != self.MODE_AUTONOMOUS: if balance_mode != self.MODE_AUTONOMOUS:
# Reset ramp state, send zero # Reset ramp state, send zero
return 0, 0, 0, 0 # (current_speed, current_steer, sent_speed, sent_steer) return 0, 0, 0, 0 # (current_speed, current_steer, sent_speed, sent_steer)
new_s = _ramp_toward(current_speed, target_speed, step) new_s = _ramp_toward(current_speed, target_speed, step)

View File

@ -1,5 +1,5 @@
""" """
Unit tests for STM32 telemetry parsing logic. Unit tests for ESP32-S3 IO telemetry parsing logic.
Run with: pytest jetson/ros2_ws/src/saltybot_bridge/test/test_parse.py Run with: pytest jetson/ros2_ws/src/saltybot_bridge/test/test_parse.py
""" """

View File

@ -1,4 +1,4 @@
"""test_stm32_cmd_node.py — Unit tests for Stm32CmdNode with mock serial port. """test_stm32_cmd_node.py — Unit tests for Esp32IoCmdNode with mock serial port.
Tests: Tests:
- Serial open/close lifecycle - Serial open/close lifecycle
@ -12,7 +12,7 @@ Tests:
- Zero-speed sent on node shutdown - Zero-speed sent on node shutdown
- CRC errors counted correctly - CRC errors counted correctly
Run with: pytest test/test_stm32_cmd_node.py -v Run with: pytest test/test_stm32_cmd_node.py -v # (file name kept for history)
No ROS2 runtime required uses mock Node infrastructure. No ROS2 runtime required uses mock Node infrastructure.
""" """
@ -29,7 +29,7 @@ import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from saltybot_bridge.stm32_protocol import ( from saltybot_bridge.esp32_protocol import (
STX, ETX, CmdType, TelType, STX, ETX, CmdType, TelType,
encode_speed_steer, encode_heartbeat, encode_arm, encode_pid_update, encode_speed_steer, encode_heartbeat, encode_arm, encode_pid_update,
_build_frame, _crc16_ccitt, _build_frame, _crc16_ccitt,
@ -219,10 +219,10 @@ class TestMockSerialTX:
class TestMockSerialRX: class TestMockSerialRX:
"""Test RX parsing path using MockSerial with pre-loaded telemetry data.""" """Test RX parsing path using MockSerial with pre-loaded telemetry data."""
from saltybot_bridge.stm32_protocol import FrameParser from saltybot_bridge.esp32_protocol import FrameParser
def test_rx_imu_frame(self): def test_rx_imu_frame(self):
from saltybot_bridge.stm32_protocol import FrameParser, ImuFrame from saltybot_bridge.esp32_protocol import FrameParser, ImuFrame
raw = _imu_frame_bytes(pitch=500, roll=-200, yaw=100, ax=0, ay=0, az=981) raw = _imu_frame_bytes(pitch=500, roll=-200, yaw=100, ax=0, ay=0, az=981)
ms = MockSerial(rx_data=raw) ms = MockSerial(rx_data=raw)
parser = FrameParser() parser = FrameParser()
@ -241,7 +241,7 @@ class TestMockSerialRX:
assert f.accel_z == pytest.approx(9.81) assert f.accel_z == pytest.approx(9.81)
def test_rx_battery_frame(self): def test_rx_battery_frame(self):
from saltybot_bridge.stm32_protocol import FrameParser, BatteryFrame from saltybot_bridge.esp32_protocol import FrameParser, BatteryFrame
raw = _battery_frame_bytes(v_mv=10500, i_ma=1200, soc=45) raw = _battery_frame_bytes(v_mv=10500, i_ma=1200, soc=45)
ms = MockSerial(rx_data=raw) ms = MockSerial(rx_data=raw)
parser = FrameParser() parser = FrameParser()
@ -257,7 +257,7 @@ class TestMockSerialRX:
assert f.soc_pct == 45 assert f.soc_pct == 45
def test_rx_multiple_frames_in_one_read(self): def test_rx_multiple_frames_in_one_read(self):
from saltybot_bridge.stm32_protocol import FrameParser from saltybot_bridge.esp32_protocol import FrameParser
raw = (_imu_frame_bytes() + _arm_state_frame_bytes() + _battery_frame_bytes()) raw = (_imu_frame_bytes() + _arm_state_frame_bytes() + _battery_frame_bytes())
ms = MockSerial(rx_data=raw) ms = MockSerial(rx_data=raw)
parser = FrameParser() parser = FrameParser()
@ -271,7 +271,7 @@ class TestMockSerialRX:
assert parser.frames_error == 0 assert parser.frames_error == 0
def test_rx_bad_crc_counted_as_error(self): def test_rx_bad_crc_counted_as_error(self):
from saltybot_bridge.stm32_protocol import FrameParser from saltybot_bridge.esp32_protocol import FrameParser
raw = bytearray(_arm_state_frame_bytes(state=1)) raw = bytearray(_arm_state_frame_bytes(state=1))
raw[-3] ^= 0xFF # corrupt CRC raw[-3] ^= 0xFF # corrupt CRC
ms = MockSerial(rx_data=bytes(raw)) ms = MockSerial(rx_data=bytes(raw))
@ -282,7 +282,7 @@ class TestMockSerialRX:
assert parser.frames_error == 1 assert parser.frames_error == 1
def test_rx_resync_after_corrupt_byte(self): def test_rx_resync_after_corrupt_byte(self):
from saltybot_bridge.stm32_protocol import FrameParser, ArmStateFrame from saltybot_bridge.esp32_protocol import FrameParser, ArmStateFrame
garbage = b"\xDE\xAD\x00\x00" garbage = b"\xDE\xAD\x00\x00"
valid = _arm_state_frame_bytes(state=1) valid = _arm_state_frame_bytes(state=1)
ms = MockSerial(rx_data=garbage + valid) ms = MockSerial(rx_data=garbage + valid)

View File

@ -1,4 +1,4 @@
"""test_stm32_protocol.py — Unit tests for binary STM32 frame codec. """test_esp32_protocol.py — Unit tests for binary STM32 frame codec.
Tests: Tests:
- CRC16-CCITT correctness - CRC16-CCITT correctness
@ -12,7 +12,7 @@ Tests:
- Speed/steer clamping in encode_speed_steer - Speed/steer clamping in encode_speed_steer
- Round-trip encode decode for all known telemetry types - Round-trip encode decode for all known telemetry types
Run with: pytest test/test_stm32_protocol.py -v Run with: pytest test/test_esp32_protocol.py -v
""" """
from __future__ import annotations from __future__ import annotations
@ -25,7 +25,7 @@ import os
# ── Path setup (no ROS2 install needed) ────────────────────────────────────── # ── Path setup (no ROS2 install needed) ──────────────────────────────────────
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from saltybot_bridge.stm32_protocol import ( from saltybot_bridge.esp32_protocol import (
STX, ETX, STX, ETX,
CmdType, TelType, CmdType, TelType,
ImuFrame, BatteryFrame, MotorRpmFrame, ArmStateFrame, ErrorFrame, ImuFrame, BatteryFrame, MotorRpmFrame, ArmStateFrame, ErrorFrame,

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Unit tests for saltybot_can_bridge.mamba_protocol. Unit tests for saltybot_can_bridge.balance_protocol.
No ROS2 or CAN hardware required tests exercise encode/decode round-trips No ROS2 or CAN hardware required tests exercise encode/decode round-trips
and boundary conditions entirely in Python. and boundary conditions entirely in Python.
@ -11,7 +11,7 @@ Run with: pytest test/test_can_bridge.py -v
import struct import struct
import unittest import unittest
from saltybot_can_bridge.mamba_protocol import ( from saltybot_can_bridge.balance_protocol import (
MAMBA_CMD_ESTOP, MAMBA_CMD_ESTOP,
MAMBA_CMD_MODE, MAMBA_CMD_MODE,
MAMBA_CMD_VELOCITY, MAMBA_CMD_VELOCITY,

View File

@ -6,7 +6,7 @@ Orin↔Mamba↔VESC integration test suite.
All IDs and payload formats are derived from: All IDs and payload formats are derived from:
include/orin_can.h OrinFC (Mamba) protocol include/orin_can.h OrinFC (Mamba) protocol
include/vesc_can.h VESC CAN protocol include/vesc_can.h VESC CAN protocol
saltybot_can_bridge/mamba_protocol.py existing bridge constants saltybot_can_bridge/balance_protocol.py existing bridge constants
CAN IDs used in tests CAN IDs used in tests
--------------------- ---------------------
@ -22,7 +22,7 @@ FC (Mamba) → Orin telemetry (standard 11-bit, matching orin_can.h):
FC_IMU 0x402 8 bytes FC_IMU 0x402 8 bytes
FC_BARO 0x403 8 bytes FC_BARO 0x403 8 bytes
Mamba VESC internal commands (matching mamba_protocol.py): Mamba VESC internal commands (matching balance_protocol.py):
MAMBA_CMD_VELOCITY 0x100 8 bytes left_mps (f32) | right_mps (f32) big-endian MAMBA_CMD_VELOCITY 0x100 8 bytes left_mps (f32) | right_mps (f32) big-endian
MAMBA_CMD_MODE 0x101 1 byte mode (0=idle,1=drive,2=estop) MAMBA_CMD_MODE 0x101 1 byte mode (0=idle,1=drive,2=estop)
MAMBA_CMD_ESTOP 0x102 1 byte 0x01=stop MAMBA_CMD_ESTOP 0x102 1 byte 0x01=stop
@ -54,7 +54,7 @@ FC_IMU: int = 0x402
FC_BARO: int = 0x403 FC_BARO: int = 0x403
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Mamba → VESC internal command IDs (from mamba_protocol.py) # Mamba → VESC internal command IDs (from balance_protocol.py)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
MAMBA_CMD_VELOCITY: int = 0x100 MAMBA_CMD_VELOCITY: int = 0x100
@ -136,14 +136,14 @@ def build_estop_cmd(action: int = 1) -> bytes:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Frame builders — Mamba velocity commands (mamba_protocol.py encoding) # Frame builders — Mamba velocity commands (balance_protocol.py encoding)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def build_velocity_cmd(left_mps: float, right_mps: float) -> bytes: def build_velocity_cmd(left_mps: float, right_mps: float) -> bytes:
""" """
Build a MAMBA_CMD_VELOCITY payload (8 bytes, 2 × float32 big-endian). Build a MAMBA_CMD_VELOCITY payload (8 bytes, 2 × float32 big-endian).
Matches encode_velocity_cmd() in mamba_protocol.py. Matches encode_velocity_cmd() in balance_protocol.py.
""" """
return struct.pack(">ff", float(left_mps), float(right_mps)) return struct.pack(">ff", float(left_mps), float(right_mps))

View File

@ -14,7 +14,7 @@ _pkg_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _pkg_root not in sys.path: if _pkg_root not in sys.path:
sys.path.insert(0, _pkg_root) sys.path.insert(0, _pkg_root)
# Also add the saltybot_can_bridge package so we can import mamba_protocol. # Also add the saltybot_can_bridge package so we can import balance_protocol.
_bridge_pkg = os.path.join( _bridge_pkg = os.path.join(
os.path.dirname(_pkg_root), "saltybot_can_bridge" os.path.dirname(_pkg_root), "saltybot_can_bridge"
) )
@ -60,7 +60,7 @@ def loopback_can_bus():
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def bridge_components(): def bridge_components():
""" """
Return the mamba_protocol encode/decode callables and a fresh mock bus. Return the balance_protocol encode/decode callables and a fresh mock bus.
Yields a dict with keys: Yields a dict with keys:
bus MockCANBus instance bus MockCANBus instance
@ -69,7 +69,7 @@ def bridge_components():
encode_estop encode_estop_cmd(stop) bytes encode_estop encode_estop_cmd(stop) bytes
decode_vesc decode_vesc_state(data) VescStateTelemetry decode_vesc decode_vesc_state(data) VescStateTelemetry
""" """
from saltybot_can_bridge.mamba_protocol import ( from saltybot_can_bridge.balance_protocol import (
encode_velocity_cmd, encode_velocity_cmd,
encode_mode_cmd, encode_mode_cmd,
encode_estop_cmd, encode_estop_cmd,

View File

@ -28,7 +28,7 @@ from saltybot_can_e2e_test.protocol_defs import (
parse_velocity_cmd, parse_velocity_cmd,
parse_fc_vesc, parse_fc_vesc,
) )
from saltybot_can_bridge.mamba_protocol import ( from saltybot_can_bridge.balance_protocol import (
encode_velocity_cmd, encode_velocity_cmd,
encode_mode_cmd, encode_mode_cmd,
) )

View File

@ -32,7 +32,7 @@ from saltybot_can_e2e_test.protocol_defs import (
parse_velocity_cmd, parse_velocity_cmd,
parse_fc_status, parse_fc_status,
) )
from saltybot_can_bridge.mamba_protocol import ( from saltybot_can_bridge.balance_protocol import (
encode_velocity_cmd, encode_velocity_cmd,
encode_mode_cmd, encode_mode_cmd,
encode_estop_cmd, encode_estop_cmd,

View File

@ -30,7 +30,7 @@ from saltybot_can_e2e_test.protocol_defs import (
parse_fc_vesc, parse_fc_vesc,
parse_vesc_status, parse_vesc_status,
) )
from saltybot_can_bridge.mamba_protocol import ( from saltybot_can_bridge.balance_protocol import (
VESC_TELEM_STATE as BRIDGE_VESC_TELEM_STATE, VESC_TELEM_STATE as BRIDGE_VESC_TELEM_STATE,
decode_vesc_state, decode_vesc_state,
) )

View File

@ -33,7 +33,7 @@ from saltybot_can_e2e_test.protocol_defs import (
build_velocity_cmd, build_velocity_cmd,
parse_velocity_cmd, parse_velocity_cmd,
) )
from saltybot_can_bridge.mamba_protocol import ( from saltybot_can_bridge.balance_protocol import (
encode_velocity_cmd, encode_velocity_cmd,
encode_mode_cmd, encode_mode_cmd,
encode_estop_cmd, encode_estop_cmd,

View File

@ -27,7 +27,7 @@ from saltybot_can_e2e_test.protocol_defs import (
build_velocity_cmd, build_velocity_cmd,
parse_velocity_cmd, parse_velocity_cmd,
) )
from saltybot_can_bridge.mamba_protocol import ( from saltybot_can_bridge.balance_protocol import (
encode_velocity_cmd, encode_velocity_cmd,
encode_mode_cmd, encode_mode_cmd,
encode_estop_cmd, encode_estop_cmd,
@ -189,7 +189,7 @@ class TestModeCommandEncoding:
"""build_mode_cmd in protocol_defs must produce identical bytes.""" """build_mode_cmd in protocol_defs must produce identical bytes."""
for mode in (MODE_IDLE, MODE_DRIVE, MODE_ESTOP): for mode in (MODE_IDLE, MODE_DRIVE, MODE_ESTOP):
assert build_mode_cmd(mode) == encode_mode_cmd(mode), \ assert build_mode_cmd(mode) == encode_mode_cmd(mode), \
f"protocol_defs.build_mode_cmd({mode}) != mamba_protocol.encode_mode_cmd({mode})" f"protocol_defs.build_mode_cmd({mode}) != balance_protocol.encode_mode_cmd({mode})"
class TestInvalidMode: class TestInvalidMode:
@ -218,8 +218,8 @@ class TestInvalidMode:
accepted = sm.set_mode(-1) accepted = sm.set_mode(-1)
assert accepted is False assert accepted is False
def test_mamba_protocol_invalid_mode_raises(self): def test_balance_protocol_invalid_mode_raises(self):
"""mamba_protocol.encode_mode_cmd must raise on invalid mode.""" """balance_protocol.encode_mode_cmd must raise on invalid mode."""
with pytest.raises(ValueError): with pytest.raises(ValueError):
encode_mode_cmd(99) encode_mode_cmd(99)
with pytest.raises(ValueError): with pytest.raises(ValueError):

View File

@ -13,7 +13,7 @@ Telemetry type (STM32 → Jetson):
uint16 pan_speed_raw + uint16 tilt_speed_raw + uint16 pan_speed_raw + uint16 tilt_speed_raw +
uint8 torque_en + uint8 rx_err_pct (10 bytes) uint8 torque_en + uint8 rx_err_pct (10 bytes)
Frame format (shared with stm32_protocol.py): Frame format (shared with esp32_protocol.py):
[STX=0x02][CMD][LEN][PAYLOAD...][CRC16_hi][CRC16_lo][ETX=0x03] [STX=0x02][CMD][LEN][PAYLOAD...][CRC16_hi][CRC16_lo][ETX=0x03]
CRC16-CCITT: poly=0x1021, init=0xFFFF, covers CMD+LEN+PAYLOAD bytes. CRC16-CCITT: poly=0x1021, init=0xFFFF, covers CMD+LEN+PAYLOAD bytes.
""" """