Add ProtocolSync command and improve MITM logging format
SangamIO changes: - Add ProtocolSync command (0x0C) for GD32 wake sequence at boot - Add CMD_PROTOCOL_SYNC constant and cmd_protocol_sync() builder - Handle protocol sync ACK in reader loop with logging - Update COMMANDS.md with full AuxCtrl init sequence from MITM capture Protocol-MITM changes: - Simplify log format to CSV for easier parsing and analysis - Configure PTY slave for raw mode to prevent escape encoding - Remove verbose packet decoding (now done in post-processing) - Update documentation with new CSV format specification Other: - Add *.log to gitignore - Remove unused mitm_init.sh script - Update deployment scripts for simplified workflow
This commit is contained in:
+3
-1
@@ -53,4 +53,6 @@ Thumbs.db
|
||||
*.dylib
|
||||
|
||||
analysis/
|
||||
.claude/
|
||||
.claude/
|
||||
|
||||
*.log
|
||||
+19
-17
@@ -22,12 +22,11 @@ protocol-mitm/
|
||||
├── src/
|
||||
│ └── main.rs # Serial MITM logger binary
|
||||
├── scripts/ # Robot-side automation scripts
|
||||
│ ├── mitm_boot_setup.sh # Initial MITM configuration
|
||||
│ ├── mitm_cleanup.sh # Remove MITM setup
|
||||
│ ├── mitm_disable.sh # Temporarily disable MITM
|
||||
│ ├── mitm_enable.sh # Re-enable MITM
|
||||
│ ├── mitm_init.sh # Initialize MITM for new session
|
||||
│ └── mitm_stop.sh # Stop MITM cleanly
|
||||
│ ├── mitm_boot_setup.sh # Initial MITM configuration at boot
|
||||
│ ├── mitm_cleanup.sh # Remove MITM setup, restore ports
|
||||
│ ├── mitm_disable.sh # Disable MITM mode, archive logs
|
||||
│ ├── mitm_enable.sh # Enable MITM mode for next boot
|
||||
│ └── mitm_stop.sh # Stop current capture run
|
||||
├── tools/ # Development machine tools
|
||||
│ ├── mitm_deploy_all.sh # Deploy MITM to robot
|
||||
│ ├── mitm_disable.sh # Remote disable
|
||||
@@ -110,19 +109,22 @@ AuxCtrl → /dev/ttyS3 (symlink) → /tmp/ttyS3_tap (PTY slave)
|
||||
|
||||
### Log Format
|
||||
|
||||
```
|
||||
[timestamp] TX <bytes> bytes
|
||||
HEX: FA FB 07 A2 10 0E 00 00 B0 10
|
||||
PKT: CMD=0xA2 LEN=7
|
||||
(LIDAR_PREP - Preparation command)
|
||||
Simple CSV format for easy parsing:
|
||||
|
||||
[timestamp] GPIO_233: 0 -> 1 (Lidar Power ON)
|
||||
|
||||
[timestamp] RX <bytes> bytes
|
||||
HEX: FA FB 63 15 01 00 00 00 04 00 ...
|
||||
PKT: CMD=0x15 LEN=99
|
||||
(STATUS - Sensor/IMU/Battery data)
|
||||
```
|
||||
# MITM Session Run 1 - 2024-11-27T14:30:22+08:00
|
||||
# Format: timestamp_us,direction,data
|
||||
1764579266055103,TX,FA FB 07 A2 10 0E 00 00 B0 10
|
||||
1764579266100000,GPIO_INIT,0
|
||||
1764579266326787,RX,FA FB 63 15 01 00 00 00 04 00 ...
|
||||
1764579266500000,GPIO,0->1
|
||||
# Session ended - TX:15234 pkts/213276 bytes, RX:15189 pkts/1503711 bytes
|
||||
```
|
||||
|
||||
**Columns:**
|
||||
- `timestamp_us`: Microsecond-precision Unix timestamp
|
||||
- `direction`: `TX` (AuxCtrl→GD32), `RX` (GD32→AuxCtrl), `GPIO` (state change), `GPIO_INIT` (initial state)
|
||||
- `data`: Space-separated hex bytes or GPIO state transition
|
||||
|
||||
## Results
|
||||
|
||||
|
||||
@@ -301,42 +301,36 @@ Choose 'y' to reboot and restore normal operation.
|
||||
|
||||
## Log Format
|
||||
|
||||
Each log file contains timestamped bidirectional packet captures:
|
||||
Each log file uses simple CSV format for easy parsing with standard tools:
|
||||
|
||||
```
|
||||
=== MITM Session Started ===
|
||||
Run Number: 1
|
||||
Timestamp: 2023-11-01T14:30:22+00:00
|
||||
Real port: /dev/ttyS3_hardware
|
||||
Virtual port: /tmp/ttyS3_tap
|
||||
===
|
||||
|
||||
[1698850222.946188] TX 14 bytes
|
||||
HEX: FA FB 0B 66 00 00 00 00 00 00 00 00 66 00
|
||||
PKT: CMD=0x66 LEN=11
|
||||
(HEARTBEAT - Keepalive packet)
|
||||
|
||||
[1698850222.946523] RX 102 bytes
|
||||
HEX: FA FB 63 15 01 06 00 0F 04 00 00 01 A6 21 ...
|
||||
PKT: CMD=0x15 LEN=99
|
||||
(STATUS - Sensor/IMU/Battery data)
|
||||
|
||||
# MITM Session Run 1 - 2024-11-27T14:30:22+08:00
|
||||
# Format: timestamp_us,direction,data
|
||||
1764579266055103,GPIO_INIT,0
|
||||
1764579266100000,TX,FA FB 0B 66 00 00 00 00 00 00 00 00 66 00
|
||||
1764579266150000,RX,FA FB 63 15 01 06 00 0F 04 00 00 01 A6 21 ...
|
||||
1764579266500000,GPIO,0->1
|
||||
...
|
||||
|
||||
=== MITM Session Ended ===
|
||||
End Timestamp: 2023-11-01T14:40:35+00:00
|
||||
TX Packets: 15234
|
||||
RX Packets: 15189
|
||||
TX Bytes: 213276
|
||||
RX Bytes: 1503711
|
||||
===
|
||||
# Session ended - TX:15234 pkts/213276 bytes, RX:15189 pkts/1503711 bytes
|
||||
```
|
||||
|
||||
**Packet Annotations:**
|
||||
- **TX**: AuxCtrl → GD32 (commands)
|
||||
- **RX**: GD32 → AuxCtrl (status/responses)
|
||||
- **Timestamps**: Microsecond precision
|
||||
- **CMD annotations**: Common command types decoded
|
||||
**CSV Columns:**
|
||||
| Column | Description |
|
||||
|--------|-------------|
|
||||
| `timestamp_us` | Unix timestamp in microseconds |
|
||||
| `direction` | `TX`, `RX`, `GPIO`, or `GPIO_INIT` |
|
||||
| `data` | Space-separated hex bytes or GPIO state |
|
||||
|
||||
**Direction Values:**
|
||||
- **TX**: AuxCtrl → GD32 (commands sent to motor controller)
|
||||
- **RX**: GD32 → AuxCtrl (status/responses from motor controller)
|
||||
- **GPIO**: GPIO 233 state change (lidar power control), format: `old->new`
|
||||
- **GPIO_INIT**: Initial GPIO 233 state at session start
|
||||
|
||||
**Notes:**
|
||||
- Lines starting with `#` are comments (session header/footer)
|
||||
- Timestamps have microsecond precision for timing analysis
|
||||
- Hex bytes are space-separated uppercase (e.g., `FA FB 03 15`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -436,20 +430,25 @@ ps aux | grep serial_mitm
|
||||
```
|
||||
/mnt/UDISK/
|
||||
├── binary/
|
||||
│ └── serial_mitm # MITM proxy binary (520KB)
|
||||
│ └── serial_mitm # MITM proxy binary (~350KB)
|
||||
├── log/
|
||||
│ ├── mitm_capture_run1_*.log # Capture logs
|
||||
│ ├── mitm_capture_run1_*.log # Capture logs (CSV format)
|
||||
│ ├── mitm_capture_run2_*.log
|
||||
│ └── archive/ # Archived logs
|
||||
│ └── capture_TIMESTAMP/
|
||||
├── mitm_enabled # Flag file (presence = MITM active)
|
||||
├── mitm_run_counter # Current run number (1-5)
|
||||
├── mitm_boot_setup.sh # Boot-time setup script
|
||||
├── mitm_stop.sh # Stop logging script
|
||||
├── mitm_run_counter # Current run number
|
||||
├── mitm_boot_setup.sh # Boot-time setup (called from _root.sh)
|
||||
├── mitm_stop.sh # Stop current capture run
|
||||
├── mitm_cleanup.sh # Restore normal operation
|
||||
├── mitm_enable.sh # Enable MITM mode
|
||||
├── mitm_disable.sh # Disable MITM mode
|
||||
└── _root.sh # User boot script (calls boot_setup)
|
||||
└── _root.sh # User boot script (add hook manually)
|
||||
```
|
||||
|
||||
**Note:** You must manually add the boot hook to `/mnt/UDISK/_root.sh`:
|
||||
```bash
|
||||
[ -f /mnt/UDISK/mitm_boot_setup.sh ] && /mnt/UDISK/mitm_boot_setup.sh
|
||||
```
|
||||
|
||||
### On Dev Machine (`protocol-mitm/`)
|
||||
@@ -459,12 +458,11 @@ protocol-mitm/
|
||||
├── src/
|
||||
│ └── main.rs # MITM proxy source (serial_mitm binary)
|
||||
├── scripts/ # Robot-side scripts (deployed)
|
||||
│ ├── mitm_boot_setup.sh
|
||||
│ ├── mitm_stop.sh
|
||||
│ ├── mitm_cleanup.sh
|
||||
│ ├── mitm_enable.sh
|
||||
│ ├── mitm_disable.sh
|
||||
│ └── mitm_init.sh
|
||||
│ ├── mitm_boot_setup.sh # Boot-time setup (called from _root.sh)
|
||||
│ ├── mitm_stop.sh # Stop current capture run
|
||||
│ ├── mitm_cleanup.sh # Restore normal operation
|
||||
│ ├── mitm_enable.sh # Enable MITM for next boot
|
||||
│ └── mitm_disable.sh # Disable MITM and archive
|
||||
├── tools/ # Dev-side control scripts
|
||||
│ ├── mitm_deploy_all.sh # Build + deploy everything
|
||||
│ ├── mitm_enable.sh # Remote enable
|
||||
@@ -489,45 +487,75 @@ protocol-mitm/
|
||||
```bash
|
||||
cd logs/mitm_captures_*/
|
||||
|
||||
# Find first init packets (CMD=0x08)
|
||||
grep "CMD=0x08" mitm_capture_run1_*.log | head -20
|
||||
# Find init packets (CMD=0x08) - look for "FA FB xx 08"
|
||||
grep ",TX,.*08" mitm_capture_run1_*.log | head -20
|
||||
|
||||
# Count heartbeat frequency
|
||||
grep "CMD=0x66" mitm_capture_run1_*.log | head -100
|
||||
# Find heartbeat packets (CMD=0x66)
|
||||
grep ",TX,.*66" mitm_capture_run1_*.log | head -100
|
||||
|
||||
# Extract all unique command types
|
||||
grep "PKT: CMD=" mitm_capture_run1_*.log | \
|
||||
awk '{print $3}' | sort | uniq -c
|
||||
# Extract all unique TX commands (4th byte is CMD)
|
||||
grep ",TX," mitm_capture_run1_*.log | \
|
||||
awk -F',' '{print $3}' | \
|
||||
awk '{print $4}' | sort | uniq -c | sort -rn
|
||||
```
|
||||
|
||||
### Compare Runs for Consistency
|
||||
|
||||
```bash
|
||||
# Extract command sequences from each run
|
||||
# Extract TX command bytes from each run
|
||||
for log in mitm_capture_run*.log; do
|
||||
echo "=== $log ==="
|
||||
grep "PKT: CMD=" "$log" | awk '{print $3}' | uniq -c
|
||||
grep ",TX," "$log" | awk -F',' '{print $3}' | awk '{print $4}' | sort | uniq -c
|
||||
done
|
||||
```
|
||||
|
||||
### Timing Analysis
|
||||
|
||||
```bash
|
||||
# Calculate heartbeat intervals
|
||||
grep "CMD=0x66" mitm_capture_run1_*.log | \
|
||||
awk -F'[\\[\\]]' '{print $2}' | \
|
||||
# Extract timestamps for TX packets containing 0x66 (heartbeat)
|
||||
grep ",TX,.*66" mitm_capture_run1_*.log | \
|
||||
awk -F',' '{print $1}' | \
|
||||
head -100 > heartbeat_timestamps.txt
|
||||
|
||||
# Use a script to calculate intervals
|
||||
# Calculate intervals (timestamps are in microseconds)
|
||||
python3 -c "
|
||||
import sys
|
||||
times = [float(line.strip()) for line in open('heartbeat_timestamps.txt')]
|
||||
intervals = [times[i+1] - times[i] for i in range(len(times)-1)]
|
||||
times = [int(line.strip()) for line in open('heartbeat_timestamps.txt')]
|
||||
intervals = [(times[i+1] - times[i]) / 1e6 for i in range(len(times)-1)]
|
||||
print(f'Avg interval: {sum(intervals)/len(intervals):.6f}s')
|
||||
print(f'Min: {min(intervals):.6f}s, Max: {max(intervals):.6f}s')
|
||||
"
|
||||
```
|
||||
|
||||
### Parse Packets with Python
|
||||
|
||||
```python
|
||||
import csv
|
||||
|
||||
def parse_mitm_log(filename):
|
||||
"""Parse MITM CSV log file."""
|
||||
packets = []
|
||||
with open(filename) as f:
|
||||
for line in f:
|
||||
if line.startswith('#'):
|
||||
continue # Skip comments
|
||||
parts = line.strip().split(',', 2)
|
||||
if len(parts) == 3:
|
||||
ts_us, direction, data = parts
|
||||
hex_bytes = bytes.fromhex(data.replace(' ', ''))
|
||||
packets.append({
|
||||
'timestamp_us': int(ts_us),
|
||||
'direction': direction,
|
||||
'raw': hex_bytes,
|
||||
'cmd': hex_bytes[3] if len(hex_bytes) > 3 else None
|
||||
})
|
||||
return packets
|
||||
|
||||
# Example: Find all unique commands
|
||||
packets = parse_mitm_log('mitm_capture_run1_*.log')
|
||||
tx_cmds = set(p['cmd'] for p in packets if p['direction'] == 'TX' and p['cmd'])
|
||||
print(f"TX commands: {[hex(c) for c in sorted(tx_cmds)]}")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Small Room First**: Test with a small room (5-10 min cleaning) to validate setup before longer runs
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# MITM Logger Init Script
|
||||
# Starts serial MITM proxy BEFORE Monitor/AuxCtrl
|
||||
|
||||
START=89
|
||||
STOP=10
|
||||
|
||||
ENABLED_FLAG="/mnt/UDISK/mitm_enabled"
|
||||
MITM_BINARY="/mnt/UDISK/binary/serial_mitm"
|
||||
RUN_COUNTER_FILE="/mnt/UDISK/mitm_run_counter"
|
||||
REAL_PORT="/dev/ttyS3"
|
||||
HARDWARE_PORT="/dev/ttyS3_hardware"
|
||||
VIRTUAL_PORT="/tmp/ttyS3_tap"
|
||||
LOG_DIR="/mnt/UDISK/log"
|
||||
|
||||
start() {
|
||||
echo "[MITM Init] Checking if MITM mode is enabled..."
|
||||
|
||||
# Only run if flag file exists
|
||||
if [ ! -f "$ENABLED_FLAG" ]; then
|
||||
echo "[MITM Init] MITM mode disabled. Skipping."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[MITM Init] MITM mode ENABLED. Starting setup..."
|
||||
|
||||
# Check if binary exists
|
||||
if [ ! -f "$MITM_BINARY" ]; then
|
||||
echo "[MITM Init] ERROR: MITM binary not found at $MITM_BINARY"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Create log directory
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
# Read run counter
|
||||
if [ -f "$RUN_COUNTER_FILE" ]; then
|
||||
RUN_NUM=$(cat "$RUN_COUNTER_FILE")
|
||||
else
|
||||
RUN_NUM=1
|
||||
echo 1 > "$RUN_COUNTER_FILE"
|
||||
fi
|
||||
|
||||
echo "[MITM Init] Current run: $RUN_NUM"
|
||||
|
||||
# Check if real serial port exists
|
||||
if [ ! -c "$REAL_PORT" ]; then
|
||||
echo "[MITM Init] ERROR: Serial port $REAL_PORT not found!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if already set up
|
||||
if [ -c "$HARDWARE_PORT" ]; then
|
||||
echo "[MITM Init] MITM already set up (found $HARDWARE_PORT)"
|
||||
else
|
||||
# Move real port to backup location
|
||||
echo "[MITM Init] Moving $REAL_PORT -> $HARDWARE_PORT"
|
||||
mv "$REAL_PORT" "$HARDWARE_PORT"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[MITM Init] ERROR: Failed to move serial port"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start MITM proxy in background
|
||||
echo "[MITM Init] Starting MITM proxy (run $RUN_NUM)..."
|
||||
$MITM_BINARY $RUN_NUM >/tmp/serial_mitm.log 2>&1 &
|
||||
MITM_PID=$!
|
||||
|
||||
# Save PID
|
||||
echo $MITM_PID > /var/run/mitm.pid
|
||||
|
||||
# Wait for PTY to be created
|
||||
sleep 2
|
||||
|
||||
# Check if virtual port was created
|
||||
if [ ! -L "$VIRTUAL_PORT" ]; then
|
||||
echo "[MITM Init] ERROR: Virtual port $VIRTUAL_PORT not created!"
|
||||
kill $MITM_PID 2>/dev/null
|
||||
mv "$HARDWARE_PORT" "$REAL_PORT" 2>/dev/null
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Create symlink /dev/ttyS3 -> /tmp/ttyS3_tap
|
||||
echo "[MITM Init] Creating symlink $REAL_PORT -> $VIRTUAL_PORT"
|
||||
ln -sf "$VIRTUAL_PORT" "$REAL_PORT"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[MITM Init] ERROR: Failed to create symlink"
|
||||
kill $MITM_PID 2>/dev/null
|
||||
mv "$HARDWARE_PORT" "$REAL_PORT" 2>/dev/null
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "[MITM Init] ✓ MITM setup complete!"
|
||||
echo "[MITM Init] - MITM proxy running (PID: $MITM_PID)"
|
||||
echo "[MITM Init] - Real port: $HARDWARE_PORT"
|
||||
echo "[MITM Init] - Virtual port: $VIRTUAL_PORT (via PTY)"
|
||||
echo "[MITM Init] - Symlink: $REAL_PORT -> $VIRTUAL_PORT"
|
||||
echo "[MITM Init] - Run number: $RUN_NUM"
|
||||
echo ""
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo "[MITM Init] Stopping MITM proxy..."
|
||||
|
||||
# Kill MITM process
|
||||
if [ -f /var/run/mitm.pid ]; then
|
||||
PID=$(cat /var/run/mitm.pid)
|
||||
kill -TERM $PID 2>/dev/null
|
||||
sleep 1
|
||||
kill -9 $PID 2>/dev/null
|
||||
rm -f /var/run/mitm.pid
|
||||
else
|
||||
killall -9 serial_mitm 2>/dev/null
|
||||
fi
|
||||
|
||||
# Remove symlink
|
||||
if [ -L "$REAL_PORT" ]; then
|
||||
rm -f "$REAL_PORT"
|
||||
fi
|
||||
|
||||
# Restore original port
|
||||
if [ -c "$HARDWARE_PORT" ]; then
|
||||
mv "$HARDWARE_PORT" "$REAL_PORT" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Remove virtual port
|
||||
rm -f "$VIRTUAL_PORT"
|
||||
|
||||
echo "[MITM Init] ✓ MITM stopped and cleaned up"
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -73,8 +73,8 @@ LATEST_LOG=$(ls -t "$LOG_DIR"/mitm_capture_run${CURRENT_RUN}_*.log 2>/dev/null |
|
||||
|
||||
if [ -n "$LATEST_LOG" ]; then
|
||||
LOG_SIZE=$(du -h "$LATEST_LOG" | awk '{print $1}')
|
||||
TX_PACKETS=$(grep -c "^\\[.*\\] TX " "$LATEST_LOG" 2>/dev/null || echo "?")
|
||||
RX_PACKETS=$(grep -c "^\\[.*\\] RX " "$LATEST_LOG" 2>/dev/null || echo "?")
|
||||
TX_PACKETS=$(grep -c ",TX," "$LATEST_LOG" 2>/dev/null || echo "?")
|
||||
RX_PACKETS=$(grep -c ",RX," "$LATEST_LOG" 2>/dev/null || echo "?")
|
||||
|
||||
echo ""
|
||||
echo "Capture Statistics:"
|
||||
|
||||
+34
-98
@@ -50,67 +50,12 @@ fn timestamp_micros() -> u128 {
|
||||
.as_micros()
|
||||
}
|
||||
|
||||
fn format_timestamp(micros: u128) -> String {
|
||||
format!("{}.{:06}", micros / 1_000_000, micros % 1_000_000)
|
||||
}
|
||||
|
||||
fn log_packet(log: &mut File, direction: &str, data: &[u8]) -> io::Result<()> {
|
||||
let ts = timestamp_micros();
|
||||
|
||||
writeln!(
|
||||
log,
|
||||
"[{}] {} {} bytes",
|
||||
format_timestamp(ts),
|
||||
direction,
|
||||
data.len()
|
||||
)?;
|
||||
|
||||
// Hex dump
|
||||
write!(log, " HEX: ")?;
|
||||
for byte in data {
|
||||
write!(log, "{:02X} ", byte)?;
|
||||
}
|
||||
writeln!(log)?;
|
||||
|
||||
// Try to decode as GD32 packet (FA FB header)
|
||||
if data.len() >= 4 && data[0] == 0xFA && data[1] == 0xFB {
|
||||
let len = data[2] as usize;
|
||||
let cmd = data[3];
|
||||
writeln!(log, " PKT: CMD=0x{:02X} LEN={}", cmd, len)?;
|
||||
|
||||
// Decode common commands
|
||||
match cmd {
|
||||
0x08 => writeln!(log, " (INIT - Initialization sequence)")?,
|
||||
0x15 => writeln!(log, " (STATUS - Sensor/IMU/Battery data)")?,
|
||||
0x66 => writeln!(log, " (HEARTBEAT - Keepalive packet)")?,
|
||||
0x06 => writeln!(log, " (WAKE - Enable command)")?,
|
||||
0x07 => writeln!(log, " (SETUP - System configuration)")?,
|
||||
0x8D => writeln!(log, " (LED - Button LED state)")?,
|
||||
0x97 => {
|
||||
if data.len() >= 5 {
|
||||
let power_state = data[4];
|
||||
writeln!(
|
||||
log,
|
||||
" (LIDAR_POWER - GPIO 233 = {})",
|
||||
if power_state == 1 { "ON" } else { "OFF" }
|
||||
)?;
|
||||
} else {
|
||||
writeln!(log, " (LIDAR_POWER - Incomplete packet)")?;
|
||||
}
|
||||
}
|
||||
0x71 => {
|
||||
if data.len() >= 8 {
|
||||
let pwm = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||
writeln!(log, " (LIDAR_PWM - Speed = {}%)", pwm)?;
|
||||
} else {
|
||||
writeln!(log, " (LIDAR_PWM - Incomplete packet)")?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(log)?;
|
||||
// Simple CSV format: timestamp_us,direction,hex_bytes
|
||||
let hex_str: String = data.iter().map(|b| format!("{:02X} ", b)).collect();
|
||||
writeln!(log, "{},{},{}", ts, direction, hex_str.trim_end())?;
|
||||
log.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -175,6 +120,17 @@ fn create_pty() -> io::Result<(RawFd, String)> {
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
// Configure slave for raw mode BEFORE closing, so when AuxCtrl opens it,
|
||||
// binary data passes through without escape encoding
|
||||
let mut termios: libc::termios = std::mem::zeroed();
|
||||
if libc::tcgetattr(slave, &mut termios) == 0 {
|
||||
libc::cfmakeraw(&mut termios);
|
||||
// Also set 115200 baud to match expected serial settings
|
||||
libc::cfsetispeed(&mut termios, libc::B115200);
|
||||
libc::cfsetospeed(&mut termios, libc::B115200);
|
||||
libc::tcsetattr(slave, libc::TCSANOW, &termios);
|
||||
}
|
||||
|
||||
libc::close(slave);
|
||||
|
||||
Ok((master, slave_name))
|
||||
@@ -201,15 +157,8 @@ fn read_gpio_233() -> io::Result<u8> {
|
||||
/// Log GPIO state change
|
||||
fn log_gpio_change(log: &mut File, old_state: u8, new_state: u8) -> io::Result<()> {
|
||||
let ts = timestamp_micros();
|
||||
writeln!(
|
||||
log,
|
||||
"[{}] GPIO_233: {} -> {} (Lidar Power {})",
|
||||
format_timestamp(ts),
|
||||
old_state,
|
||||
new_state,
|
||||
if new_state == 1 { "ON" } else { "OFF" }
|
||||
)?;
|
||||
writeln!(log)?;
|
||||
// CSV format: timestamp_us,GPIO,old->new
|
||||
writeln!(log, "{},GPIO,{}->{}", ts, old_state, new_state)?;
|
||||
log.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -223,13 +172,7 @@ fn spawn_gpio_monitor(log_file: Arc<Mutex<File>>) -> std::thread::JoinHandle<()>
|
||||
{
|
||||
let ts = timestamp_micros();
|
||||
if let Ok(mut log) = log_file.lock() {
|
||||
let _ = writeln!(
|
||||
log,
|
||||
"[{}] GPIO_233: Initial state = {}",
|
||||
format_timestamp(ts),
|
||||
last_state
|
||||
);
|
||||
let _ = writeln!(log);
|
||||
let _ = writeln!(log, "{},GPIO_INIT,{}", ts, last_state);
|
||||
let _ = log.flush();
|
||||
}
|
||||
}
|
||||
@@ -289,17 +232,16 @@ fn main() -> io::Result<()> {
|
||||
.open(&log_filename)?,
|
||||
));
|
||||
|
||||
// Write session header
|
||||
// Write session header as comment (CSV compatible)
|
||||
{
|
||||
let mut log = log_file.lock().unwrap();
|
||||
writeln!(log, "=")?;
|
||||
writeln!(log, "=== MITM Session Started ===")?;
|
||||
writeln!(log, "Run Number: {}", run_number)?;
|
||||
writeln!(log, "Timestamp: {}", chrono::Local::now().to_rfc3339())?;
|
||||
writeln!(log, "Real port: {}", REAL_PORT)?;
|
||||
writeln!(log, "Virtual port: {}", VIRTUAL_PORT)?;
|
||||
writeln!(log, "GPIO Monitoring: Enabled (GPIO 233)")?;
|
||||
writeln!(log, "===\n")?;
|
||||
writeln!(
|
||||
log,
|
||||
"# MITM Session Run {} - {}",
|
||||
run_number,
|
||||
chrono::Local::now().to_rfc3339()
|
||||
)?;
|
||||
writeln!(log, "# Format: timestamp_us,direction,data")?;
|
||||
log.flush()?;
|
||||
}
|
||||
|
||||
@@ -319,13 +261,9 @@ fn main() -> io::Result<()> {
|
||||
// Create pseudo-terminal
|
||||
println!("Creating virtual serial port...");
|
||||
let (pty_master, pty_slave_name) = create_pty()?;
|
||||
|
||||
// Configure PTY to raw mode to prevent data escaping
|
||||
configure_serial(pty_master)?;
|
||||
println!(
|
||||
"✓ Virtual port created and configured at: {}",
|
||||
pty_slave_name
|
||||
);
|
||||
// NOTE: Do NOT configure PTY master with termios - leave it in default state
|
||||
// AuxCtrl will configure the slave side as it expects a serial port
|
||||
println!("✓ Virtual port created at: {}", pty_slave_name);
|
||||
|
||||
// Create symlink
|
||||
println!("Creating symlink {} -> {}", VIRTUAL_PORT, pty_slave_name);
|
||||
@@ -413,16 +351,14 @@ fn main() -> io::Result<()> {
|
||||
let _ = gpio_thread.join();
|
||||
println!("✓ GPIO thread stopped");
|
||||
|
||||
// Write session footer with statistics
|
||||
// Write session footer as comment
|
||||
{
|
||||
let mut log = log_file.lock().unwrap();
|
||||
writeln!(log, "\n=== MITM Session Ended ===")?;
|
||||
writeln!(log, "End Timestamp: {}", chrono::Local::now().to_rfc3339())?;
|
||||
writeln!(log, "TX Packets: {}", tx_packets)?;
|
||||
writeln!(log, "RX Packets: {}", rx_packets)?;
|
||||
writeln!(log, "TX Bytes: {}", tx_bytes)?;
|
||||
writeln!(log, "RX Bytes: {}", rx_bytes)?;
|
||||
writeln!(log, "===\n")?;
|
||||
writeln!(
|
||||
log,
|
||||
"# Session ended - TX:{} pkts/{} bytes, RX:{} pkts/{} bytes",
|
||||
tx_packets, tx_bytes, rx_packets, rx_bytes
|
||||
)?;
|
||||
log.flush()?;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ set -e # Exit on error
|
||||
|
||||
ROBOT_HOST="vacuum"
|
||||
ROBOT_PASS="${ROBOT_PASSWORD:-}"
|
||||
SCRIPT_DIR="scripts/robot"
|
||||
SCRIPT_DIR="scripts"
|
||||
TARGET_DIR="/mnt/UDISK"
|
||||
BINARY_DIR="/mnt/UDISK/binary"
|
||||
|
||||
@@ -93,18 +93,18 @@ done
|
||||
echo "✓ All scripts deployed"
|
||||
echo ""
|
||||
|
||||
# Deploy init script
|
||||
echo "Installing MITM init script..."
|
||||
cat "$SCRIPT_DIR/mitm_init.sh" | \
|
||||
sshpass -p "$ROBOT_PASS" ssh root@$ROBOT_HOST \
|
||||
"cat > /etc/init.d/mitmLogger && chmod +x /etc/init.d/mitmLogger"
|
||||
# Check if _root.sh exists and has MITM boot hook
|
||||
echo "Checking _root.sh integration..."
|
||||
HAS_HOOK=$(sshpass -p "$ROBOT_PASS" ssh root@$ROBOT_HOST \
|
||||
"grep -q 'mitm_boot_setup' /mnt/UDISK/_root.sh 2>/dev/null && echo yes || echo no")
|
||||
|
||||
# Create rc.d symlink (S89 runs before Monitor at S91)
|
||||
sshpass -p "$ROBOT_PASS" ssh root@$ROBOT_HOST \
|
||||
"ln -sf /etc/init.d/mitmLogger /etc/rc.d/S89mitmLogger 2>/dev/null || true"
|
||||
|
||||
echo "✓ Init script installed at /etc/init.d/mitmLogger"
|
||||
echo "✓ Auto-start enabled (S89mitmLogger runs before Monitor)"
|
||||
if [ "$HAS_HOOK" = "yes" ]; then
|
||||
echo "✓ MITM boot hook already present in _root.sh"
|
||||
else
|
||||
echo "⚠ _root.sh does not call mitm_boot_setup.sh"
|
||||
echo " Add this line to /mnt/UDISK/_root.sh:"
|
||||
echo " [ -f /mnt/UDISK/mitm_boot_setup.sh ] && /mnt/UDISK/mitm_boot_setup.sh"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "========================================="
|
||||
|
||||
+124
-38
@@ -21,56 +21,119 @@ All commands use the packet format: `[0xFA 0xFB] [LEN] [CMD] [PAYLOAD] [CRC_H] [
|
||||
- **MEDIUM**: Found in reverse-engineered code OR observed in logs, but not fully tested
|
||||
- **LOW**: Single source, purpose inferred from context
|
||||
|
||||
| Hex | Name | Payload | Implemented | R2D Usage | Confidence | Reason |
|
||||
|-----|------|---------|-------------|-----------|------------|--------|
|
||||
| Hex | Name | Payload | Implemented | MITM Usage | Confidence | Reason |
|
||||
|-----|------|---------|-------------|------------|------------|--------|
|
||||
| **Heartbeat/System** |
|
||||
| 0x06 | Heartbeat | None | ✅ | 606x | HIGH | Working in SangamIO, matches AuxCtrl `packetHeartBeat` |
|
||||
| 0x07 | Version Request | None | ✅ | 3x | HIGH | Working in SangamIO, matches AuxCtrl `packetRequireSystemVersion` |
|
||||
| 0x08 | Initialize/IMU Zero | None | ✅ | 13x | HIGH | Working in SangamIO, matches AuxCtrl `packetSetIMUZero`, no CRC |
|
||||
| 0x06 | Heartbeat | None | ✅ | 1264x | HIGH | Working in SangamIO, matches AuxCtrl `packetHeartBeat` |
|
||||
| 0x07 | Version Request | None | ✅ | 2x | HIGH | Working in SangamIO, matches AuxCtrl `packetRequireSystemVersion` |
|
||||
| 0x08 | Initialize/IMU Zero | None | ✅ | 1x | HIGH | Working in SangamIO, matches AuxCtrl `packetSetIMUZero`, no CRC |
|
||||
| 0x04 | MCU Sleep | None | ✅ | 0 | MEDIUM | `ComponentControl { id: "mcu", action: Disable }` |
|
||||
| 0x05 | Wakeup Ack | None | ✅ | 0 | MEDIUM | `ComponentControl { id: "mcu", action: Enable }` |
|
||||
| 0x0A | Reset Error Code | None | ✅ | 1x | HIGH | `ComponentControl { id: "mcu", action: Reset }` |
|
||||
| 0x0D | Request STM32 Data | None | ✅ | 98x | HIGH | Internal driver polling every ~3s in heartbeat loop |
|
||||
| 0x0C | Protocol Sync | 1 byte | ✅ | 1x | HIGH | First cmd at boot, wakes GD32, payload 0x01 |
|
||||
| 0x0D | Request STM32 Data | None | ✅ | 86x | HIGH | Internal driver polling every ~3s in heartbeat loop |
|
||||
| **Motor Control** |
|
||||
| 0x65 | Motor Mode | 1 byte | ✅ | 4x | HIGH | Working in SangamIO, 0x00=idle, 0x02=nav mode |
|
||||
| 0x66 | Motor Velocity | 8 bytes | ✅ | 6627x | HIGH | Working in SangamIO, primary motion control |
|
||||
| 0x67 | Motor Speed | 4 bytes | ✅ | 0 | MEDIUM | Implemented but not used in R2D, direct wheel control |
|
||||
| 0x68 | Air Pump (Vacuum) | 2 bytes | ✅ | 0 | HIGH | Working in SangamIO, matches AuxCtrl `packetBlowerSpeed` |
|
||||
| 0x69 | Side Brush | 1 byte | ✅ | 15x | HIGH | Working in SangamIO, matches AuxCtrl `packetBrushSpeed` |
|
||||
| 0x6A | Main Brush | 1 byte | ✅ | 18x | HIGH | Working in SangamIO, matches AuxCtrl `packetRollingSpeed` |
|
||||
| 0x6B | Unknown Actuator | 1 byte | ❌ | 2x | LOW | Only observed in R2D log with payload 0x00, purpose unknown |
|
||||
| 0x65 | Motor Mode | 1 byte | ✅ | 5x | HIGH | Working in SangamIO, 0x00=idle, 0x02=nav mode |
|
||||
| 0x66 | Motor Velocity | 8 bytes | ✅ | 11473x | HIGH | Working in SangamIO, primary motion control |
|
||||
| 0x67 | Motor Speed | 4 bytes | ✅ | 0 | MEDIUM | Implemented but not used, direct wheel control |
|
||||
| 0x68 | Air Pump (Vacuum) | 2 bytes | ✅ | 1x | HIGH | Working in SangamIO, matches AuxCtrl `packetBlowerSpeed` |
|
||||
| 0x69 | Side Brush | 1 byte | ✅ | 32x | HIGH | Working in SangamIO, matches AuxCtrl `packetBrushSpeed` |
|
||||
| 0x6A | Main Brush | 1 byte | ✅ | 31x | HIGH | Working in SangamIO, matches AuxCtrl `packetRollingSpeed` |
|
||||
| 0x6B | Motor Controller Init | 1 byte | ❌ | 95x | HIGH | Boot handshake: 0x00=off, 0x64=ready. Pulsed for 2s at startup |
|
||||
| **Lidar Control** |
|
||||
| 0x71 | Lidar PWM | 4 bytes | ✅ | 5073x | HIGH | Working in SangamIO, matches AuxCtrl `controlLidarPwm` |
|
||||
| 0x97 | Lidar Power | 1 byte | ✅ | 6x | HIGH | Working in SangamIO, matches AuxCtrl `packetLidarPower` |
|
||||
| 0x17 | Lidar Config | 4 bytes | ❌ | 1x | LOW | Init: `[0x01, 0xF0, 0xDF, 0xFA]`, response: `[0x01]` |
|
||||
| 0x18 | Lidar Query | None/8 bytes | ❌ | 1x | LOW | Request: none, Response: `[0x04, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00]` |
|
||||
| 0x19 | Lidar Enable | 1 byte | ❌ | 1x | LOW | Init sequence, payload 0x01 |
|
||||
| 0x71 | Lidar PWM | 4 bytes | ✅ | 6971x | HIGH | Working in SangamIO, matches AuxCtrl `controlLidarPwm` |
|
||||
| 0x7C | Unknown Lidar | 3 bytes | ❌ | 1x | LOW | Init: `[0x00, 0xFF, 0xFF]`, purpose unknown |
|
||||
| 0x97 | Lidar Power | 1 byte | ✅ | 3x | HIGH | Working in SangamIO, matches AuxCtrl `packetLidarPower` |
|
||||
| **Sensor Control** |
|
||||
| 0x78 | Cliff IR Control | 1 byte | ✅ | 0 | MEDIUM | `ComponentControl { id: "cliff_ir", action: Enable/Disable }` |
|
||||
| 0x79 | Cliff IR Direction | 1 byte | ✅ | 0 | MEDIUM | `ComponentControl { id: "cliff_ir", action: Configure { direction } }` |
|
||||
| 0x86 | Unknown Sensor | 1 byte | ❌ | 2x | LOW | Only in R2D log, payload 0x10→0x00, possibly dock IR |
|
||||
| 0x9D | Unknown | 1 byte | ❌ | 1x | LOW | Only in R2D log, payload 0x01, purpose unknown |
|
||||
| 0x78 | Cliff IR Control | 1 byte | ✅ | 1x | HIGH | `ComponentControl { id: "cliff_ir", action: Enable/Disable }` |
|
||||
| 0x79 | Cliff IR Direction | 1 byte | ✅ | 1x | MEDIUM | `ComponentControl { id: "cliff_ir", action: Configure { direction } }` |
|
||||
| 0x86 | Dock IR Sensor | 1 byte | ❌ | 3x | MEDIUM | Payloads: 0x00=off, 0x41=on, dock detection sensor |
|
||||
| 0x9D | Unknown Sensor | 1 byte | ❌ | 2x | LOW | Payload 0x01, sent during init |
|
||||
| **LED/UI** |
|
||||
| 0x8D | Button LED State | 1 byte | ✅ | 7x | HIGH | Working in SangamIO, 19 modes discovered (see LED section) |
|
||||
| 0x8D | Button LED State | 1 byte | ✅ | 20x | HIGH | Working in SangamIO, 19 modes discovered (see LED section) |
|
||||
| **Power Management** |
|
||||
| 0x99 | Main Board Power | 1 byte | ✅ | 0 | MEDIUM | `ComponentControl { id: "main_board", action: Enable/Disable }` |
|
||||
| 0x9A | Main Board Restart | None | ✅ | 0 | MEDIUM | `ComponentControl { id: "main_board", action: Reset }` |
|
||||
| 0x9B | Charger Power | 1 byte | ✅ | 0 | MEDIUM | `ComponentControl { id: "charger", action: Enable/Disable }` |
|
||||
| **Calibration** |
|
||||
| 0xA1 | IMU Factory Calibrate | None | ✅ | 0 | MEDIUM | `ComponentControl { id: "imu", action: Reset }` |
|
||||
| 0xA2 | IMU Calibrate State | 0 or 4 bytes | ✅ | 2x | HIGH | `ComponentControl { id: "imu", action: Enable }` |
|
||||
| 0xA2 | IMU Calibrate State | 0 or 4 bytes | ✅ | 3x | HIGH | `ComponentControl { id: "imu", action: Enable }` |
|
||||
| 0xA3 | Compass Calibrate | None | ✅ | 0 | MEDIUM | `ComponentControl { id: "compass", action: Reset }` |
|
||||
| 0xA4 | Compass Cal State | None | ✅ | 0 | MEDIUM | `ComponentControl { id: "compass", action: Enable }` |
|
||||
|
||||
### Response Commands (GD32 → Host)
|
||||
|
||||
| Hex | Name | Payload | Confidence | Reason |
|
||||
|-----|------|---------|------------|--------|
|
||||
| 0x15 | Status Packet | 96 bytes | HIGH | Continuous sensor data, fully decoded in SangamIO |
|
||||
| 0x07 | Version Response | Variable | HIGH | Response to version request |
|
||||
| Hex | Name | Payload | MITM Count | Confidence | Reason |
|
||||
|-----|------|---------|------------|------------|--------|
|
||||
| 0x06 | Heartbeat Ack | None | 130x | HIGH | Echo acknowledgment |
|
||||
| 0x07 | Version Response | Variable | 2x | HIGH | Response to version request |
|
||||
| 0x08 | Init Ack | None | 1x | HIGH | Echo acknowledgment |
|
||||
| 0x0C | Protocol Sync Ack | 1 byte | 1x | HIGH | Echo of 0x0C command (~270ms delay) |
|
||||
| 0x15 | Status Packet | 96 bytes | 12993x | HIGH | Continuous sensor data @ ~500Hz |
|
||||
| 0x17 | Lidar Config Ack | 1 byte | 1x | LOW | Response `[0x01]` |
|
||||
| 0x18 | Lidar Query Response | 8 bytes | 1x | LOW | Lidar state data |
|
||||
| 0xA2 | IMU Cal Response | 1 byte | 3x | HIGH | Response `[0x01]` |
|
||||
|
||||
---
|
||||
|
||||
## Return-to-Dock Command Sequence
|
||||
## AuxCtrl Initialization Sequence (MITM Captured)
|
||||
|
||||
From captured ~160 second operation:
|
||||
From MITM capture run10 - boot sequence analyzed in 5 phases:
|
||||
|
||||
### Phase 1: System Init (immediate)
|
||||
```
|
||||
Step CMD Packet Purpose
|
||||
──── ──── ────────────────────────────────── ─────────────────────────
|
||||
1 0x0C FA FB 04 0C 01 0C 01 System init / protocol sync
|
||||
2 0x07 FA FB 03 07 00 07 Request version
|
||||
3 0x8D FA FB 04 8D 01 8D 01 LED state 0x01 (standby blue)
|
||||
4 0x0A FA FB 03 0A 00 0A Reset error codes
|
||||
```
|
||||
|
||||
### Phase 2: Hardware Config (within 50ms)
|
||||
```
|
||||
5 0x07 FA FB 03 07 00 07 Request version (retry)
|
||||
6 0x17 FA FB 07 17 01 F0 DF FA 07 1A Lidar motor config
|
||||
7 0x18 FA FB 03 18 00 18 Lidar query
|
||||
8 0x97 FA FB 04 97 00 97 00 Lidar power OFF
|
||||
9 0x9D FA FB 04 9D 01 9D 01 Unknown sensor enable
|
||||
10 0x71 FA FB 07 71 00 00 00 00 71 00 Lidar PWM = 0
|
||||
```
|
||||
|
||||
### Phase 3: Actuator Init (immediate)
|
||||
```
|
||||
11 0x65 FA FB 04 65 00 65 00 Motor mode 0x00 (idle)
|
||||
12 0x68 FA FB 05 68 00 00 68 00 Vacuum 0%
|
||||
13 0x69 FA FB 04 69 00 69 00 Side brush 0%
|
||||
14 0x6A FA FB 04 6A 00 6A 00 Main brush 0%
|
||||
15 0x6B FA FB 04 6B 00 6B 00 Motor controller init = OFF
|
||||
16 0x08 FA FB 03 08 00 08 Initialize/IMU zero
|
||||
17 0x19 FA FB 04 19 01 19 01 Lidar enable
|
||||
18 0x7C FA FB 06 7C 00 FF FF 7B FF Unknown lidar config
|
||||
```
|
||||
|
||||
### Phase 4: Motor Controller Handshake (~2 seconds)
|
||||
```
|
||||
19 0x8D FA FB 04 8D 01 8D 01 LED state refresh
|
||||
... 0x6B FA FB 04 6B 64 6B 64 Motor ctrl init = 100% (every ~22ms)
|
||||
... 0x66 FA FB 0B 66 00 00 00 00 ... Velocity = 0 (interleaved)
|
||||
```
|
||||
**Note:** GD32 requires 2 seconds of 0x6B [64] pulses before accepting motion commands.
|
||||
Robot does NOT move during this phase (encoders unchanged).
|
||||
|
||||
### Phase 5: Normal Operation
|
||||
```
|
||||
... 0x66 FA FB 0B 66 00 00 00 00 ... Motor velocity (heartbeat continues)
|
||||
... 0x71 FA FB 07 71 ... Lidar PWM control
|
||||
```
|
||||
**Note:** 0x6B stops after handshake. Only 0x66 continues as heartbeat.
|
||||
|
||||
## Return-to-Dock Command Sequence (Legacy)
|
||||
|
||||
From earlier captured ~160 second operation:
|
||||
|
||||
```
|
||||
Phase 1: Startup
|
||||
@@ -89,14 +152,14 @@ Phase 2: Navigation Active
|
||||
0x69/0x6A → Brush activation (brief)
|
||||
|
||||
Phase 3: Dock Approach
|
||||
0x86 → Unknown (0x10)
|
||||
0x6B → Unknown (0x00)
|
||||
0x86 → Dock IR (0x10)
|
||||
0x6B → Mop motor (0x00)
|
||||
0x9D → Unknown (0x01)
|
||||
|
||||
Phase 4: Shutdown
|
||||
0x65 → Motor mode 0x00 (idle)
|
||||
0x97 → Lidar power off
|
||||
0x86 → Unknown (0x00)
|
||||
0x86 → Dock IR off (0x00)
|
||||
0x0A → Reset error codes
|
||||
```
|
||||
|
||||
@@ -123,19 +186,32 @@ Phase 4: Shutdown
|
||||
|
||||
| Status | Count | Commands |
|
||||
|--------|-------|----------|
|
||||
| ✅ Implemented | 25 | 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0D, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x71, 0x78, 0x79, 0x8D, 0x97, 0x99, 0x9A, 0x9B, 0xA1, 0xA2, 0xA3, 0xA4 |
|
||||
| ❌ Not Implemented | 3 | 0x6B, 0x86, 0x9D |
|
||||
| ✅ Implemented | 26 | 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x71, 0x78, 0x79, 0x8D, 0x97, 0x99, 0x9A, 0x9B, 0xA1, 0xA2, 0xA3, 0xA4 |
|
||||
| ❌ Not Implemented | 9 | 0x17, 0x18, 0x19, 0x6B, 0x7C, 0x86, 0x9D |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
### Medium Priority (Used in R2D, low confidence)
|
||||
| Hex | Name | Reason |
|
||||
|-----|------|--------|
|
||||
| 0x86 | Unknown Sensor | Used during dock approach, may be dock IR |
|
||||
| 0x6B | Unknown Actuator | Brief use, may be related to docking |
|
||||
| 0x9D | Unknown | Single use, purpose unclear |
|
||||
### High Priority (Boot sequence, well understood)
|
||||
| Hex | Name | MITM Usage | Reason |
|
||||
|-----|------|------------|--------|
|
||||
| 0x0C | Protocol Sync | 1x | ✅ DONE - First boot command, wakes GD32 |
|
||||
| 0x6B | Motor Controller Init | 95x | 2-second boot handshake required before motion commands work |
|
||||
|
||||
### Medium Priority (Init sequence, partially understood)
|
||||
| Hex | Name | MITM Usage | Reason |
|
||||
|-----|------|------------|--------|
|
||||
| 0x86 | Dock IR Sensor | 3x | Dock detection, 0x00=off, 0x41=on |
|
||||
| 0x17 | Lidar Config | 1x | Lidar init, payload `[0x01, 0xF0, 0xDF, 0xFA]` |
|
||||
| 0x18 | Lidar Query | 1x | Lidar state query, response has 8 bytes |
|
||||
| 0x19 | Lidar Enable | 1x | Lidar init, payload 0x01 |
|
||||
|
||||
### Low Priority (Single use, unknown purpose)
|
||||
| Hex | Name | MITM Usage | Reason |
|
||||
|-----|------|------------|--------|
|
||||
| 0x7C | Unknown Lidar | 1x | Init, payload `[0x00, 0xFF, 0xFF]` |
|
||||
| 0x9D | Unknown Sensor | 2x | Init, payload 0x01 |
|
||||
|
||||
|
||||
---
|
||||
@@ -328,3 +404,13 @@ The `drive` component supports two configuration modes:
|
||||
```json
|
||||
{"type": "ComponentControl", "id": "mcu", "action": {"type": "Reset"}}
|
||||
```
|
||||
|
||||
### Protocol Commands
|
||||
|
||||
These are standalone commands not using ComponentControl:
|
||||
|
||||
**Protocol Sync (wake GD32 at boot):**
|
||||
```json
|
||||
{"type": "ProtocolSync"}
|
||||
```
|
||||
This is a fire-and-forget command. GD32 echoes it back after ~270ms. Typically sent once at boot before any other commands.
|
||||
|
||||
@@ -156,6 +156,13 @@ pub enum Command {
|
||||
action: ComponentAction,
|
||||
},
|
||||
|
||||
// Protocol Commands
|
||||
/// Protocol sync - first command to wake GD32 and synchronize protocol
|
||||
///
|
||||
/// This is a fire-and-forget command. GD32 echoes it back after ~270ms.
|
||||
/// Typically sent once at boot before any other commands.
|
||||
ProtocolSync,
|
||||
|
||||
// System Lifecycle
|
||||
/// Graceful daemon shutdown
|
||||
Shutdown,
|
||||
|
||||
@@ -42,6 +42,7 @@ pub const CMD_REQUEST_STM32_DATA: u8 = 0x0D; // Request STM32 sensor data (polle
|
||||
pub const CMD_MCU_SLEEP: u8 = 0x04; // Put GD32 MCU to sleep
|
||||
pub const CMD_WAKEUP_ACK: u8 = 0x05; // Acknowledge wakeup from sleep
|
||||
pub const CMD_RESET_ERROR_CODE: u8 = 0x0A; // Reset/clear error codes
|
||||
pub const CMD_PROTOCOL_SYNC: u8 = 0x0C; // Protocol sync - first command at boot, wakes GD32
|
||||
|
||||
// Power management commands
|
||||
pub const CMD_MAIN_BOARD_POWER: u8 = 0x99; // Main board (A33) power control
|
||||
|
||||
@@ -28,7 +28,8 @@ use super::protocol::{
|
||||
cmd_compass_calibrate, cmd_compass_calibration_state, cmd_imu_calibrate_state,
|
||||
cmd_imu_factory_calibrate, cmd_led_state, cmd_lidar_power, cmd_lidar_pwm, cmd_main_board_power,
|
||||
cmd_main_board_restart, cmd_main_brush, cmd_mcu_sleep, cmd_motor_mode, cmd_motor_speed,
|
||||
cmd_motor_velocity, cmd_reset_error_code, cmd_side_brush, cmd_wakeup_ack, Packet,
|
||||
cmd_motor_velocity, cmd_protocol_sync, cmd_reset_error_code, cmd_side_brush, cmd_wakeup_ack,
|
||||
Packet,
|
||||
};
|
||||
use super::state::ComponentState;
|
||||
use crate::core::types::{Command, ComponentAction, SensorValue};
|
||||
@@ -179,6 +180,12 @@ pub(super) fn send_command(
|
||||
handle_component_control(port, component_state, id, action)
|
||||
}
|
||||
|
||||
// Protocol Commands
|
||||
Command::ProtocolSync => {
|
||||
log::info!("Protocol sync (0x0C)");
|
||||
send_packet(port, &cmd_protocol_sync())
|
||||
}
|
||||
|
||||
// System Lifecycle
|
||||
Command::Shutdown => {
|
||||
shutdown.store(true, Ordering::Relaxed);
|
||||
|
||||
@@ -6,9 +6,9 @@ use crate::devices::crl200s::constants::{
|
||||
CMD_COMPASS_CALIBRATE, CMD_COMPASS_CALIBRATION_STATE, CMD_HEARTBEAT, CMD_IMU_CALIBRATE_STATE,
|
||||
CMD_IMU_FACTORY_CALIBRATE, CMD_INITIALIZE, CMD_LIDAR_POWER, CMD_LIDAR_PWM,
|
||||
CMD_MAIN_BOARD_POWER, CMD_MAIN_BOARD_RESTART, CMD_MAIN_BRUSH, CMD_MCU_SLEEP, CMD_MOTOR_MODE,
|
||||
CMD_MOTOR_SPEED, CMD_MOTOR_VELOCITY, CMD_REQUEST_STM32_DATA, CMD_RESET_ERROR_CODE,
|
||||
CMD_SIDE_BRUSH, CMD_VERSION, CMD_WAKEUP_ACK, MAX_BUFFER_SIZE, MIN_PACKET_SIZE, SYNC_BYTE_1,
|
||||
SYNC_BYTE_2,
|
||||
CMD_MOTOR_SPEED, CMD_MOTOR_VELOCITY, CMD_PROTOCOL_SYNC, CMD_REQUEST_STM32_DATA,
|
||||
CMD_RESET_ERROR_CODE, CMD_SIDE_BRUSH, CMD_VERSION, CMD_WAKEUP_ACK, MAX_BUFFER_SIZE,
|
||||
MIN_PACKET_SIZE, SYNC_BYTE_1, SYNC_BYTE_2,
|
||||
};
|
||||
use crate::error::{Error, Result};
|
||||
use std::io::Read;
|
||||
@@ -392,6 +392,17 @@ pub fn cmd_reset_error_code() -> Packet {
|
||||
Packet::new(CMD_RESET_ERROR_CODE, vec![])
|
||||
}
|
||||
|
||||
/// Protocol sync command (0x0C)
|
||||
///
|
||||
/// First command sent at boot to wake GD32 and synchronize protocol.
|
||||
/// This is a fire-and-forget command - AuxCtrl does not wait for ACK.
|
||||
/// GD32 echoes this command back after ~270ms.
|
||||
///
|
||||
/// Payload: `[0x01]` = enable/sync
|
||||
pub fn cmd_protocol_sync() -> Packet {
|
||||
Packet::new(CMD_PROTOCOL_SYNC, vec![0x01])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
use super::protocol::{cmd_version_request, PacketReader};
|
||||
use crate::core::types::{SensorGroupData, SensorValue};
|
||||
use crate::devices::crl200s::constants::{
|
||||
BATTERY_VOLTAGE_MAX, BATTERY_VOLTAGE_MIN, CMD_STATUS, CMD_VERSION, FLAG_BUMPER_LEFT,
|
||||
FLAG_BUMPER_RIGHT, FLAG_CHARGING, FLAG_CLIFF_LEFT_FRONT, FLAG_CLIFF_LEFT_SIDE,
|
||||
FLAG_CLIFF_RIGHT_FRONT, FLAG_CLIFF_RIGHT_SIDE, FLAG_DOCK_CONNECTED, FLAG_DUSTBOX_ATTACHED,
|
||||
OFFSET_BATTERY_VOLTAGE_RAW, OFFSET_BUMPER_FLAGS, OFFSET_CHARGING_FLAGS, OFFSET_CLIFF_FLAGS,
|
||||
OFFSET_DOCK_BUTTON, OFFSET_DUSTBOX_FLAGS, OFFSET_START_BUTTON, OFFSET_WHEEL_LEFT_ENCODER,
|
||||
OFFSET_WHEEL_RIGHT_ENCODER, STATUS_PAYLOAD_MIN_SIZE,
|
||||
BATTERY_VOLTAGE_MAX, BATTERY_VOLTAGE_MIN, CMD_PROTOCOL_SYNC, CMD_STATUS, CMD_VERSION,
|
||||
FLAG_BUMPER_LEFT, FLAG_BUMPER_RIGHT, FLAG_CHARGING, FLAG_CLIFF_LEFT_FRONT,
|
||||
FLAG_CLIFF_LEFT_SIDE, FLAG_CLIFF_RIGHT_FRONT, FLAG_CLIFF_RIGHT_SIDE, FLAG_DOCK_CONNECTED,
|
||||
FLAG_DUSTBOX_ATTACHED, OFFSET_BATTERY_VOLTAGE_RAW, OFFSET_BUMPER_FLAGS, OFFSET_CHARGING_FLAGS,
|
||||
OFFSET_CLIFF_FLAGS, OFFSET_DOCK_BUTTON, OFFSET_DUSTBOX_FLAGS, OFFSET_START_BUTTON,
|
||||
OFFSET_WHEEL_LEFT_ENCODER, OFFSET_WHEEL_RIGHT_ENCODER, STATUS_PAYLOAD_MIN_SIZE,
|
||||
};
|
||||
use serialport::SerialPort;
|
||||
use std::io::Write;
|
||||
@@ -52,6 +52,7 @@ pub(super) fn reader_loop(
|
||||
let mut reader = PacketReader::new();
|
||||
let mut version_requested = false;
|
||||
let mut version_received = false;
|
||||
let mut protocol_sync_received = false;
|
||||
|
||||
while !shutdown.load(Ordering::Relaxed) {
|
||||
let packet_result = {
|
||||
@@ -93,6 +94,15 @@ pub(super) fn reader_loop(
|
||||
handle_version_packet(&packet, &version_data, &mut version_received);
|
||||
}
|
||||
|
||||
// Handle protocol sync ACK (0x0C echoed back from GD32)
|
||||
if packet.cmd == CMD_PROTOCOL_SYNC && !protocol_sync_received {
|
||||
protocol_sync_received = true;
|
||||
log::info!(
|
||||
"Protocol sync ACK received (payload: {:02X?})",
|
||||
packet.payload
|
||||
);
|
||||
}
|
||||
|
||||
// Handle sensor status data
|
||||
if packet.cmd == CMD_STATUS && packet.payload.len() >= STATUS_PAYLOAD_MIN_SIZE {
|
||||
handle_status_packet(&packet, &sensor_data);
|
||||
|
||||
Reference in New Issue
Block a user