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:
codetiger
2025-11-27 15:55:56 +05:30
parent b9e72db8c2
commit 7dfe9fa48b
13 changed files with 327 additions and 374 deletions
+3 -1
View File
@@ -53,4 +53,6 @@ Thumbs.db
*.dylib
analysis/
.claude/
.claude/
*.log
+19 -17
View File
@@ -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
+87 -59
View File
@@ -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
-137
View File
@@ -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
}
+2 -2
View File
@@ -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
View File
@@ -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()?;
}
+12 -12
View File
@@ -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
View File
@@ -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.
+7
View File
@@ -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);
+14 -3
View File
@@ -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::*;
+16 -6
View File
@@ -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);