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:
102
esp_bridge.py
102
esp_bridge.py
@ -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],
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user