use embedded_charts::{
chart::{
line::{LineChart, MarkerShape, MarkerStyle},
traits::{Chart, ChartBuilder, ChartConfig, Margins},
},
data::{
OverflowMode, Point2D, PointRingBuffer, RingBuffer, RingBufferConfig, RingBufferEvent,
StaticDataSeries,
},
};
use embedded_graphics::{
pixelcolor::Rgb565,
prelude::*,
primitives::{PrimitiveStyleBuilder, Rectangle},
};
use embedded_graphics_simulator::{
OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
};
use std::{thread, time::Duration};
#[path = "../common/capture.rs"]
mod capture;
use capture::GifCapture;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut display = SimulatorDisplay::<Rgb565>::new(Size::new(800, 480));
let output_settings = OutputSettingsBuilder::new().build();
let mut window = Window::new("Ring Buffer Real-Time Demo", &output_settings);
let _data_buffer: PointRingBuffer<100> = PointRingBuffer::new();
let config = RingBufferConfig {
overflow_mode: OverflowMode::Overwrite,
enable_events: true,
track_bounds: true,
..Default::default()
};
let mut streaming_buffer: RingBuffer<Point2D, 100> = RingBuffer::with_config(config);
streaming_buffer.set_event_handler(|event| match event {
RingBufferEvent::BufferFull => println!("Buffer is now full!"),
RingBufferEvent::BoundsChanged => println!("Data bounds have changed"),
_ => {}
});
let chart_config = ChartConfig {
margins: Margins::default(),
show_grid: true,
..Default::default()
};
let line_chart = LineChart::builder()
.line_color(Rgb565::CYAN)
.line_width(2)
.with_markers(MarkerStyle {
shape: MarkerShape::Circle,
size: 4,
color: Rgb565::YELLOW,
visible: true,
})
.build()?;
let mut time = 0.0f32;
let mut frame_count = 0u32;
let mut last_fps_time = std::time::Instant::now();
let mut fps = 0.0f32;
let mut gif_capture = GifCapture::new(50); let mut capture_frame_count = 0;
let max_capture_frames = 60; let capture_enabled = std::env::var("CAPTURE_GIF").is_ok();
if capture_enabled {
println!("GIF capture enabled! Will save to docs/assets/ring_buffer_demo.gif");
}
println!("Ring Buffer Demo Started!");
println!("- Buffer capacity: 100 points");
println!("- Overflow mode: Overwrite oldest");
println!("- Real-time bounds tracking enabled");
println!();
'running: loop {
window.update(&display);
for event in window.events() {
if event == SimulatorEvent::Quit {
break 'running;
}
}
let value = generate_sensor_data(time);
let point = Point2D::new(time, value);
streaming_buffer.push_point(point)?;
if frame_count % 5 == 0 {
display.clear(Rgb565::BLACK)?;
let mut chart_data = StaticDataSeries::<Point2D, 256>::new();
for point in streaming_buffer.iter_chronological() {
chart_data.push(*point)?;
}
let main_viewport = Rectangle::new(Point::new(10, 40), Size::new(580, 340));
line_chart.draw(&chart_data, &chart_config, main_viewport, &mut display)?;
draw_stats_panel(&mut display, &streaming_buffer, fps)?;
if let Some(avg) = streaming_buffer.moving_average(20) {
draw_indicator(&mut display, "Moving Avg (20)", avg.y, Rgb565::GREEN, 650)?;
}
if let Some(rate) = streaming_buffer.rate_of_change() {
draw_indicator(&mut display, "Rate of Change", rate, Rgb565::MAGENTA, 680)?;
}
draw_buffer_visualization(&mut display, &streaming_buffer)?;
if capture_enabled && capture_frame_count < max_capture_frames {
gif_capture.add_frame(&display);
capture_frame_count += 1;
println!("Captured frame {capture_frame_count}/{max_capture_frames}");
if capture_frame_count >= max_capture_frames {
println!("Saving GIF...");
gif_capture.save_gif("docs/assets/ring_buffer_demo.gif")?;
println!("GIF saved successfully!");
break 'running;
}
}
}
time += 0.05;
frame_count += 1;
if frame_count % 30 == 0 {
let elapsed = last_fps_time.elapsed();
fps = 30.0 / elapsed.as_secs_f32();
last_fps_time = std::time::Instant::now();
}
thread::sleep(Duration::from_millis(5)); }
Ok(())
}
fn generate_sensor_data(time: f32) -> f32 {
let base = (time * 0.5).sin() * 30.0 + 50.0;
let noise = (time * 10.0).sin() * 5.0;
let spike = if (time as i32) % 20 == 0 { 15.0 } else { 0.0 };
base + noise + spike
}
fn draw_stats_panel(
display: &mut SimulatorDisplay<Rgb565>,
buffer: &RingBuffer<Point2D, 100>,
fps: f32,
) -> Result<(), Box<dyn std::error::Error>> {
use embedded_graphics::{
mono_font::{ascii::FONT_6X10, MonoTextStyle},
text::Text,
};
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
let stats = buffer.stats();
let stats_box = Rectangle::new(Point::new(600, 40), Size::new(190, 140));
let box_style = PrimitiveStyleBuilder::new()
.stroke_color(Rgb565::CYAN)
.stroke_width(1)
.fill_color(Rgb565::new(0, 0, 10))
.build();
stats_box.into_styled(box_style).draw(display)?;
Text::new("Ring Buffer Stats", Point::new(610, 55), text_style).draw(display)?;
let mut y = 75;
Text::new(
&format!("Size: {}/{}", buffer.len(), buffer.capacity()),
Point::new(610, y),
text_style,
)
.draw(display)?;
y += 15;
Text::new(
&format!("Writes: {}", stats.total_writes),
Point::new(610, y),
text_style,
)
.draw(display)?;
y += 15;
Text::new(
&format!("Overflows: {}", stats.overflow_count),
Point::new(610, y),
text_style,
)
.draw(display)?;
y += 15;
Text::new(
&format!("Peak: {} pts", stats.peak_usage),
Point::new(610, y),
text_style,
)
.draw(display)?;
y += 15;
Text::new(&format!("FPS: {fps:.1}"), Point::new(610, y), text_style).draw(display)?;
if let Some(bounds) = buffer.bounds() {
let bounds_box = Rectangle::new(Point::new(600, 190), Size::new(190, 80));
bounds_box.into_styled(box_style).draw(display)?;
Text::new("Data Bounds", Point::new(610, 205), text_style).draw(display)?;
Text::new(
&format!("X: {:.1} - {:.1}", bounds.min_x, bounds.max_x),
Point::new(610, 225),
text_style,
)
.draw(display)?;
Text::new(
&format!("Y: {:.1} - {:.1}", bounds.min_y, bounds.max_y),
Point::new(610, 245),
text_style,
)
.draw(display)?;
}
Ok(())
}
fn draw_indicator(
display: &mut SimulatorDisplay<Rgb565>,
label: &str,
value: f32,
color: Rgb565,
x: i32,
) -> Result<(), Box<dyn std::error::Error>> {
use embedded_graphics::{
mono_font::{ascii::FONT_6X10, MonoTextStyle},
text::Text,
};
let text_style = MonoTextStyle::new(&FONT_6X10, color);
Text::new(
&format!("{label}: {value:.2}"),
Point::new(x, 300),
text_style,
)
.draw(display)?;
Ok(())
}
fn draw_buffer_visualization(
display: &mut SimulatorDisplay<Rgb565>,
buffer: &RingBuffer<Point2D, 100>,
) -> Result<(), Box<dyn std::error::Error>> {
let viz_area = Rectangle::new(Point::new(10, 400), Size::new(780, 60));
let box_style = PrimitiveStyleBuilder::new()
.stroke_color(Rgb565::new(20, 20, 20))
.stroke_width(1)
.fill_color(Rgb565::BLACK)
.build();
viz_area.into_styled(box_style).draw(display)?;
let slot_width = 780.0 / buffer.capacity() as f32;
let filled_ratio = buffer.len() as f32 / buffer.capacity() as f32;
for i in 0..buffer.capacity() {
let x = 10 + (i as f32 * slot_width) as i32;
let filled = i < buffer.len();
let color = if filled {
let hue = (i as f32 / buffer.len() as f32) * 120.0; let r = ((120.0 - hue) * 2.0).min(255.0) as u8;
let g = (hue * 2.0).min(255.0) as u8;
Rgb565::new(r >> 3, g >> 2, 0)
} else {
Rgb565::new(5, 5, 5)
};
let slot = Rectangle::new(Point::new(x, 410), Size::new(slot_width as u32 - 1, 40));
let slot_style = PrimitiveStyleBuilder::new().fill_color(color).build();
slot.into_styled(slot_style).draw(display)?;
}
use embedded_graphics::{
mono_font::{ascii::FONT_6X10, MonoTextStyle},
text::Text,
};
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
Text::new(
&format!("Buffer Fill: {:.0}%", filled_ratio * 100.0),
Point::new(350, 395),
text_style,
)
.draw(display)?;
Ok(())
}