Add episode recording functionality and enhance serial communication handling

- Introduced record_episode.py for capturing ESP32 IMU data and RealSense images.
- Added SwitchChangeDetector and SwitchLevelController for managing recording triggers.
- Enhanced ESP32Bridge with new methods for reading samples and latest packets.
- Updated verify.py and visualise.py to improve serial communication stability.
- Modified .gitignore to include dataset, csv, and png directories.
This commit is contained in:
Brunsmeier
2026-07-03 10:53:49 +08:00
parent be7c498270
commit fd286e6a9e
5 changed files with 1096 additions and 7 deletions

View File

@ -3,6 +3,7 @@ import time
import serial
SYNC = b"\xA5\x5A"
PACKET_FORMAT = "<2sBBBI10hB"
PACKET_SIZE = struct.calcsize(PACKET_FORMAT)
@ -35,8 +36,8 @@ class ESP32Bridge:
port,
baud=115200,
timeout=0.05,
startup_wait=1.0,
safe_stop_on_close=True,
startup_wait=4.0,
safe_stop_on_close=False,
reset_input_on_open=True,
reset_output_on_open=True,
):
@ -206,4 +207,99 @@ class ESP32Bridge:
else:
yield self.read_packet()
count += 1
count += 1
def read_samples(self, seconds=None, max_samples=None, max_gap_ms=1000):
"""
Read multiple packets and return flattened samples.
Suitable for visualise.py / verify.py.
"""
self._require_open()
samples = []
bad_delta_count = 0
elapsed_s = 0.0
start = time.monotonic()
while True:
if seconds is not None and time.monotonic() - start >= seconds:
break
if max_samples is not None and len(samples) >= max_samples:
break
data = self.read_packet()
if self.last_t_ms is None:
freq_hz = 0.0
else:
delta = (data["t_ms"] - self.last_t_ms) & 0xFFFFFFFF
if 0 < delta <= max_gap_ms:
elapsed_s += delta / 1000.0
freq_hz = 1000.0 / delta
else:
bad_delta_count += 1
freq_hz = 0.0
self.last_t_ms = data["t_ms"]
sample = flatten_packet(data, elapsed_s, freq_hz)
samples.append(sample)
return samples, bad_delta_count
def read_latest_packet(self, max_drain=200):
"""
Return the newest decoded packet currently available.
It drains old buffered packets so the PC side follows the latest DIN/DOUT state.
Useful when camera/image saving is slower than ESP32 50Hz output.
"""
self._require_open()
latest = self.read_packet()
drained = 0
while drained < max_drain:
# Pull all currently available bytes from OS serial buffer.
waiting = self.ser.in_waiting
if waiting <= 0 and len(self.buffer) < PACKET_SIZE:
break
if waiting > 0:
chunk = self.ser.read(waiting)
if chunk:
self.buffer.extend(chunk)
packet = self._try_pop_packet()
if packet is None:
break
latest = decode_packet(packet)
drained += 1
return latest, drained
def flatten_packet(data, t_s, freq_hz):
return {
"t_s": t_s,
"acc_x_g": data["acc_g"][0],
"acc_y_g": data["acc_g"][1],
"acc_z_g": data["acc_g"][2],
"gyro_x_dps": data["gyro_dps"][0],
"gyro_y_dps": data["gyro_dps"][1],
"gyro_z_dps": data["gyro_dps"][2],
"angle_x_deg": data["angle_deg"][0],
"angle_y_deg": data["angle_deg"][1],
"angle_z_deg": data["angle_deg"][2],
"temp_c": data["temp_c"],
"freq_hz": freq_hz,
"din0": data["din"][0],
"din1": data["din"][1],
"din2": data["din"][2],
"din3": data["din"][3],
"dout0": data["dout"][0],
"dout1": data["dout"][1],
}