simulation: rewrite the storage and simulation stack

This commit is contained in:
2026-03-27 22:44:15 +01:00
parent 8a3b2303a2
commit 4977746af1
9 changed files with 465 additions and 170 deletions

View File

@@ -51,6 +51,10 @@ fn gpio_interrupt_handler() {
INTERRUPTS.try_get().unwrap().process_interrupts();
}
static MOTION_BUS: ConstStaticCell<Channel<CriticalSectionRawMutex,Measurement,5> > = ConstStaticCell::new(Channel::new());
static RECORDING_BUS: ConstStaticCell<PubSubChannel<CriticalSectionRawMutex,Measurement,10, 4, 1> > = ConstStaticCell::new(PubSubChannel::new());
static PREDICTIONS: ConstStaticCell<PubSubChannel<NoopRawMutex, Prediction, 30, 7, 1>> = ConstStaticCell::new(PubSubChannel::new());
#[esp_rtos::main]
async fn main(spawner: Spawner) {
// If we aren't using the second CPU, we can use the bootloader space for the heap instead
@@ -72,11 +76,8 @@ async fn main(spawner: Spawner) {
esp_rtos::start(sys_timer.alarm0);
info!("Embassy initialized!");
static MOTION_BUS: StaticCell<Channel<CriticalSectionRawMutex,Measurement,5> > = StaticCell::new();
let motion_bus = MOTION_BUS.init_with(|| { Channel::new() });
static RECORDING_BUS: StaticCell<PubSubChannel<CriticalSectionRawMutex,Measurement,1, 2, 1> > = StaticCell::new();
let recording_bus = RECORDING_BUS.init_with(|| { PubSubChannel::new() });
let motion_bus = MOTION_BUS.take();
let recording_bus = RECORDING_BUS.take();
info!("Setting up rendering pipeline");
let mut surfaces = UiSurfacePool::default();
@@ -160,32 +161,6 @@ async fn main(spawner: Spawner) {
spawner.must_spawn(renderbug_bike::tasks::oled_render::oled_render(output, oled_surfaces, oled_uniforms));
}
let mut storage = renderbug_bike::storage::SharedFlash::new(esp_storage::FlashStorage::new());
let mut partition_buf = [8; 1024];
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut partition_buf).unwrap();
#[cfg(feature="simulation")]
{
use renderbug_bike::tasks::simulation::SimDataTable;
for sim_data in SimDataTable::open(storage, partitions).expect("Could not find sim data!") {
let srcid = sim_data.srcid();
info!("Found simulation data for {srcid:?}");
if spawner.spawn(renderbug_bike::tasks::simulation::simulation_task(sim_data, motion_bus.dyn_sender())).is_err() {
error!("Unable to spawn simulation task for {srcid:?}! Increase the task pool size.");
}
}
}
#[cfg(not(feature="simulation"))]
{
use renderbug_bike::storage::SimDataRecorder;
let recorder = SimDataRecorder::open(storage, partitions).expect("Unable to open sim data partition for writing");
//spawner.spawn(record_telemetry(recording_bus.dyn_receiver(), recorder)).unwrap();
}
spawner.spawn(print_sensor_readings(recording_bus.dyn_subscriber().unwrap())).unwrap();
#[cfg(feature="radio")]
let (wifi, network_device, ble) = {
info!("Configuring wifi");
@@ -211,9 +186,7 @@ async fn main(spawner: Spawner) {
let core2_main = |spawner: Spawner| {
info!("Starting application tasks");
static PREDICTIONS: StaticCell<PubSubChannel<NoopRawMutex, Prediction, 15, 6, 1>> = StaticCell::new();
let predictions = PREDICTIONS.init(PubSubChannel::new());
let predictions = PREDICTIONS.take();
#[cfg(not(feature="demo"))]
{
@@ -251,24 +224,72 @@ async fn main(spawner: Spawner) {
info!("Starting connectivity task");
spawner.must_spawn(renderbug_bike::tasks::wifi::wifi_connect_task(wifi, motion_bus.dyn_sender()));
info!("Starting location sampler");
static SAMPLER: ConstStaticCell<Watch<NoopRawMutex, Prediction, 1>> = ConstStaticCell::new(Watch::new());
let sampler = SAMPLER.take();
spawner.must_spawn(renderbug_bike::tasks::wifi::location_sampler(predictions.dyn_subscriber().unwrap(), sampler.dyn_sender()));
info!("Launching HTTP telemetry");
spawner.must_spawn(renderbug_bike::tasks::wifi::http_telemetry_task(predictions.dyn_subscriber().unwrap(), stack, motion_bus.dyn_sender()));
spawner.must_spawn(renderbug_bike::tasks::wifi::http_telemetry_task(sampler.dyn_receiver().unwrap(), stack, motion_bus.dyn_sender()));
info!("Starting BLE services");
spawner.must_spawn(renderbug_bike::tasks::ble::ble_task(ble, predictions.dyn_subscriber().unwrap(), spawner));
}
#[cfg(feature="dual-core")]
spawner.must_spawn(print_sensor_status(predictions.dyn_subscriber().unwrap()));
let mut storage = SharedFlash::new(esp_storage::FlashStorage::new(peripherals.FLASH).multicore_auto_park());
let mut partition_buf = [8; 1024];
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut partition_buf).unwrap();
#[cfg(any(feature="flash-recording", feature="simulation"))]
{
info!("Launching core 2 watchdog");
let timer1 = TimerGroup::new(peripherals.TIMG1);
let mut ui_wdt = timer1.wdt;
ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(60));
ui_wdt.enable();
spawner.must_spawn(wdt_task(ui_wdt));
use renderbug_bike::tasks::simulation::{SimDataStream, SimDataTable};
let sim_partition = SimDataTable::find_partition(storage, partitions).expect("Could not find sim data partition!");
info!("Got partition: {sim_partition:?}");
let mut sim_table = match SimDataTable::open(sim_partition.clone()) {
Ok(table) => table,
Err(SimDataError::StreamIndexMissing) => {
info!("Sim data partition not formatted, creating new stream table with {sim_partition:?}");
SimDataTable::create(sim_partition).expect("Unable to create sim data stream table in partition!")
},
Err(e) => panic!("Error opening sim data stream table: {e:?}")
};
loop {
match sim_table.next() {
Some((header, reader)) => {
let srcid = header.id;
info!("Found simulation data for {srcid:?} at {:#x}", reader.abs_start());
let stream = SimDataStream::open(reader, srcid);
if cfg!(feature="simulation") {
if spawner.spawn(renderbug_bike::tasks::simulation::simulation_task(stream, motion_bus.dyn_sender())).is_err() {
error!("Unable to spawn simulation task for {srcid:?}! Increase the task pool size.");
}
} else if cfg!(feature="flash-recording") && srcid == StreamType::Bundle {
info!("Continuing recording stream");
spawner.spawn(record_telemetry(recording_bus.dyn_subscriber().unwrap(), stream, motion_bus.dyn_sender())).unwrap();
break;
}
},
None if cfg!(feature="flash-recording") => {
// If we already found a recording stream, we break; out of the loop above
let reader = sim_table.append_new_stream(StreamType::Bundle).expect("Unable to create a new recording stream in the sim data partition! Is there enough free space?");
warn!("Starting new recording stream at {:#x}", reader.abs_start());
let recorder = SimDataStream::create(reader, StreamType::Bundle);
spawner.spawn(record_telemetry(recording_bus.dyn_subscriber().unwrap(), recorder, motion_bus.dyn_sender())).unwrap();
break;
},
_ => ()
}
}
}
spawner.must_spawn(print_sensor_status(predictions.dyn_subscriber().unwrap()));
spawner.must_spawn(wdt_task(rtc, predictions.dyn_subscriber().unwrap(), oled_controls, display_controls));
//info!("Final memory stats: {}", esp_alloc::HEAP.stats());
info!("Ready to rock and roll in {}ms", Instant::now().as_millis());
};
@@ -316,9 +337,17 @@ async fn wdt_task(mut wdt: Wdt<esp_hal::peripherals::TIMG1<'static>>) {
}
#[embassy_executor::task]
async fn record_telemetry(firehose: DynamicReceiver<'static, Measurement>, mut storage: SimDataRecorder<StorageRange<SharedFlash<FlashStorage>>>) {
async fn record_telemetry(mut firehose: DynSubscriber<'static, Measurement>, mut storage: SimDataStream<StorageRange<SharedFlash<FlashStorage<'static>>>>, motion: DynamicSender<'static, Measurement>) {
let mut skipped_events = 0;
while let Ok(Some((_, evt))) = storage.read_next() {
trace!("Skipping event {evt:?}");
skipped_events += 1;
}
info!("Skipped {} events to catch up to the end of the recording stream", skipped_events);
motion.send(Measurement::SensorHardwareStatus(SensorSource::FlashRecording, SensorState::Online)).await;
storage.write_next(AnnotationReading { buf: *b"Telemetry recording started " }).unwrap();
loop {
match firehose.receive().await {
match firehose.next_message_pure().await {
Measurement::IMU { accel, gyro } => {
let reading = IMUReading {
accel_x: accel.x as f64,
@@ -328,24 +357,28 @@ async fn record_telemetry(firehose: DynamicReceiver<'static, Measurement>, mut s
gyro_y: gyro.y as f64,
gyro_z: gyro.z as f64
};
storage.write_next(reading).unwrap();
info!("Wrote IMU to flash");
//storage.write_next(reading).unwrap();
trace!("Wrote IMU to flash");
},
_ => ()
}
}
}
#[embassy_executor::task]
async fn print_sensor_readings(mut events: DynSubscriber<'static, Measurement>) {
loop {
match events.next_message_pure().await {
Measurement::IMU { accel, gyro } => {
esp_println::println!("accel=({},{},{}) gyro=({},{},{})", accel.x, accel.y, accel.z, gyro.x, gyro.y, gyro.z);
},
Measurement::GPS(gps) => {
esp_println::println!("gps={gps:?}");
Measurement::GPS(Some(pos)) => {
storage.write_next(GPSReading {
lat: pos.x,
lon: pos.y,
}).unwrap();
trace!("Wrote GPS to flash");
},
Measurement::SensorHardwareStatus(sensor, status) => {
let annotation = alloc::format!("{:?}={:?}", sensor, status);
let mut buf = [0; 32];
let bytes = annotation.as_bytes();
let copy_len = bytes.len().min(buf.len());
buf[..copy_len].copy_from_slice(&bytes[..copy_len]);
if copy_len < buf.len() {
buf[copy_len..].fill(b' ');
}
storage.write_next(AnnotationReading { buf }).unwrap();
trace!("Wrote sensor status update to flash");
}
_ => ()
}
}

59
src/bin/playback.rs Normal file
View File

@@ -0,0 +1,59 @@
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use esp_hal::clock::CpuClock;
use esp_hal::timer::systimer::SystemTimer;
use log::*;
use esp_backtrace as _;
use renderbug_bike::logging::RenderbugLogger;
use renderbug_bike::tasks::simulation::{SimDataStream, SimDataTable};
use renderbug_bike::simdata::*;
esp_bootloader_esp_idf::esp_app_desc!();
#[esp_rtos::main]
async fn main(spawner: Spawner) {
esp_alloc::heap_allocator!(size: 100000);
RenderbugLogger::init_logger();
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
let sys_timer = SystemTimer::new(peripherals.SYSTIMER);
esp_rtos::start(sys_timer.alarm0);
let mut storage = renderbug_bike::storage::SharedFlash::new(esp_storage::FlashStorage::new(peripherals.FLASH).multicore_auto_park());
let mut partition_buf = [8; 1024];
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut partition_buf).unwrap();
let sim_partition = SimDataTable::find_partition(storage, partitions).expect("Could not find sim data partition!");
info!("Got partition: {sim_partition:?}");
let sim_table = SimDataTable::open(sim_partition).expect("Sim data partition not found");
for (header, reader) in sim_table {
info!("Found stream {header:?}");
if header.id == StreamType::Bundle {
let mut stream = SimDataStream::open(reader, header.id);
let mut timestamp = 0;
loop {
match stream.read_next() {
Ok(Some((timecode, next_evt))) => {
timestamp += timecode.as_millis();
esp_println::println!("{timestamp} {next_evt:?}");
},
Ok(None) => {
warn!("End of simulation data stream");
break
},
Err(err) => {
warn!("Error during sensor stream: {err:?}");
break
}
}
}
}
}
}

36
src/bin/reset.rs Normal file
View File

@@ -0,0 +1,36 @@
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use esp_hal::clock::CpuClock;
use esp_hal::timer::systimer::SystemTimer;
use log::*;
use esp_backtrace as _;
use renderbug_bike::logging::RenderbugLogger;
use renderbug_bike::tasks::simulation::{SimDataStream, SimDataTable};
use renderbug_bike::simdata::*;
esp_bootloader_esp_idf::esp_app_desc!();
#[esp_rtos::main]
async fn main(spawner: Spawner) {
esp_alloc::heap_allocator!(size: 100000);
RenderbugLogger::init_logger();
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
let sys_timer = SystemTimer::new(peripherals.SYSTIMER);
esp_rtos::start(sys_timer.alarm0);
let mut storage = renderbug_bike::storage::SharedFlash::new(esp_storage::FlashStorage::new(peripherals.FLASH));
let mut partition_buf = [8; 1024];
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut partition_buf).unwrap();
let sim_partition = SimDataTable::find_partition(storage, partitions).expect("Could not find sim data partition!");
info!("Got partition: {sim_partition:?}");
SimDataTable::create(sim_partition).expect("Could not write empty sim partition");
error!("Overwrote sim data partition with a blank stream table");
}