chore: remove Mamba F722S / STM32 / BlackPill legacy hw refs #716
@ -1,4 +1,4 @@
|
|||||||
"""stm32_protocol.py — Binary frame codec for Jetson↔STM32 communication.
|
"""esp32_protocol.py — Binary frame codec for Jetson↔STM32 communication.
|
||||||
|
|
||||||
Issue #119: defines the binary serial protocol between the Jetson Nano and the
|
Issue #119: defines the binary serial protocol between the Jetson Nano and the
|
||||||
STM32F722 flight controller over USB CDC @ 921600 baud.
|
STM32F722 flight controller over USB CDC @ 921600 baud.
|
||||||
@ -55,7 +55,7 @@ from sensor_msgs.msg import Imu
|
|||||||
from std_msgs.msg import String
|
from std_msgs.msg import String
|
||||||
from std_srvs.srv import SetBool, Trigger
|
from std_srvs.srv import SetBool, Trigger
|
||||||
|
|
||||||
from .stm32_protocol import (
|
from .esp32_protocol import (
|
||||||
FrameParser,
|
FrameParser,
|
||||||
ImuFrame, BatteryFrame, MotorRpmFrame, ArmStateFrame, ErrorFrame,
|
ImuFrame, BatteryFrame, MotorRpmFrame, ArmStateFrame, ErrorFrame,
|
||||||
encode_heartbeat, encode_speed_steer, encode_arm, encode_set_mode,
|
encode_heartbeat, encode_speed_steer, encode_arm, encode_set_mode,
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
mamba_protocol.py — CAN message encoding/decoding for the Mamba motor controller
|
balance_protocol.py — CAN message encoding/decoding for the Mamba motor controller
|
||||||
and VESC telemetry.
|
and VESC telemetry.
|
||||||
|
|
||||||
CAN message layout
|
CAN message layout
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
can_bridge_node.py — ROS2 node bridging the SaltyBot Orin to the Mamba motor
|
can_bridge_node.py — ROS2 node bridging the SaltyBot Orin to the ESP32-S3 BALANCE
|
||||||
controller and VESC motor controllers over CAN bus.
|
controller and VESC motor controllers over CAN bus.
|
||||||
|
|
||||||
The node opens the SocketCAN interface (slcan0 by default), spawns a background
|
The node opens the SocketCAN interface (slcan0 by default), spawns a background
|
||||||
@ -34,7 +34,7 @@ from rcl_interfaces.msg import SetParametersResult
|
|||||||
from sensor_msgs.msg import BatteryState, Imu
|
from sensor_msgs.msg import BatteryState, Imu
|
||||||
from std_msgs.msg import Bool, Float32MultiArray, String
|
from std_msgs.msg import Bool, Float32MultiArray, String
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 — Orin↔FC (Mamba) protocol
|
include/orin_can.h — Orin↔FC (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))
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user