Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ repository = "https://github.com/StefanBossbaly/rustic-pixel-display"
authors = ["Stefan Bossbaly <sbossb@gmail.com>"]
license = "GPL"


[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-util = "0.7.8"
Expand All @@ -31,7 +30,7 @@ rustic_pixel_display = { path = "rustic-pixel-display", features = ["http_server
rustic_pixel_display_macros = { path = "rustic-pixel-display/macros" }
home-assistant-rest = "0.2.0"
septa-api = "0.3.4"
amtrak-api = { git = "https://github.com/StefanBossbaly/amtrak-api.git", branch = "master" }
amtrak-api = "0.1.0"
geoutils = "0.5.1"
clap = { version= "4.4", features = ["derive"] }
serde_json = "1.0.105"
Expand All @@ -52,3 +51,8 @@ name = "rpi"

[[bin]]
name = "rpi_http"

[profile.release]
debug = 1
[rust]
debuginfo-level = 1
92 changes: 92 additions & 0 deletions rustic-pixel-display/src/render/cached_canvas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::convert::Infallible;

use embedded_graphics::{
pixelcolor::Rgb888,
prelude::{DrawTarget, OriginDimensions, Point, RgbColor, Size},
Pixel,
};

use super::Render;

#[derive(Debug)]
pub struct CachedCanvas {
size: Size,
pixels: Box<[Rgb888]>,
}

impl CachedCanvas {
pub fn new(size: Size) -> CachedCanvas {
let num_of_pixels = size.width as usize * size.height as usize;

Self {
size,
pixels: vec![Rgb888::BLACK; num_of_pixels].into_boxed_slice(),
}
}

fn convert_point_to_offset(&self, point: Point) -> Option<usize> {
let (x, y) = (point.x as u32, point.y as u32);
if x < self.size.width && y < self.size.height {
Some((x + y * self.size.width) as usize)
} else {
None
}
}

fn convert_offset_to_point(&self, offset: usize) -> Option<Point> {
let x = offset as u32 % self.size.width;
let y = offset as u32 / self.size.width;

if x < self.size.width && y < self.size.height {
Some(Point {
x: x as i32,
y: y as i32,
})
} else {
None
}
}
}

impl OriginDimensions for CachedCanvas {
fn size(&self) -> Size {
self.size
}
}

impl DrawTarget for CachedCanvas {
type Color = Rgb888;
type Error = Infallible;

fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels.into_iter() {
if let Some(index) = self.convert_point_to_offset(point) {
self.pixels[index] = color;
}
}

Ok(())
}
}

impl<D> Render<D> for CachedCanvas
where
D: DrawTarget<Color = Rgb888, Error = Infallible>,
{
fn render(&self, canvas: &mut D) -> Result<(), D::Error> {
let pixels = self
.pixels
.clone()
.iter()
.enumerate()
.map(|(offset, color)| Pixel(self.convert_offset_to_point(offset).unwrap(), *color))
.collect::<Vec<_>>();

canvas.draw_iter(pixels)?;

Ok(())
}
}
2 changes: 2 additions & 0 deletions rustic-pixel-display/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use anyhow::Result;
use embedded_graphics::{pixelcolor::Rgb888, prelude::DrawTarget};
use std::{convert::Infallible, io::Read};

mod cached_canvas;
mod sub_canvas;

pub use cached_canvas::CachedCanvas;
pub use sub_canvas::SubCanvas;

pub trait Render<D>
Expand Down
9 changes: 6 additions & 3 deletions src/bin/rpi_http.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use anyhow::Result;
use embedded_graphics::{pixelcolor::Rgb888, prelude::DrawTarget};
use embedded_graphics::{
pixelcolor::Rgb888,
prelude::{DrawTarget, OriginDimensions},
};
use parking_lot::Mutex;
use rustic_pixel_display::{
config::{HardwareConfig, HardwareMapping, LedSequence, RowAddressSetterType},
Expand All @@ -14,7 +17,7 @@ use rustic_pixel_examples::renders::{
use std::{convert::Infallible, sync::Arc, vec};

#[derive(RenderFactories)]
enum RenderFactoryEntries<D: DrawTarget<Color = Rgb888, Error = Infallible>> {
enum RenderFactoryEntries<D: DrawTarget<Color = Rgb888, Error = Infallible> + OriginDimensions> {
TransitTracker(TransitTrackerFactory<D>),
UpcomingArrivals(UpcomingArrivalsFactory<D>),
Weather(WeatherFactory<D>),
Expand Down Expand Up @@ -51,7 +54,7 @@ async fn main() -> Result<()> {
interlaced: false,
dither_bits: 0,
chain_length: 2,
parallel: 1,
parallel: 2,
panel_type: None,
multiplexing: None,
row_setter: RowAddressSetterType::Direct,
Expand Down
4 changes: 2 additions & 2 deletions src/bin/simulator_http.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::Result;
use embedded_graphics::{
pixelcolor::Rgb888,
prelude::{DrawTarget, Point, RgbColor, Size},
prelude::{DrawTarget, OriginDimensions, Point, RgbColor, Size},
primitives::Rectangle,
};
use embedded_graphics_simulator::{
Expand Down Expand Up @@ -30,7 +30,7 @@ const DISPLAY_SIZE: Size = Size {
};

#[derive(RenderFactories)]
enum RenderFactoryEntries<D: DrawTarget<Color = Rgb888, Error = Infallible>> {
enum RenderFactoryEntries<D: DrawTarget<Color = Rgb888, Error = Infallible> + OriginDimensions> {
TransitTracker(TransitTrackerFactory<D>),
UpcomingArrivals(UpcomingArrivalsFactory<D>),
Weather(WeatherFactory<D>),
Expand Down
83 changes: 52 additions & 31 deletions src/renders/upcoming_arrivals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use embedded_graphics::{
image::Image,
mono_font::{self, MonoTextStyle},
pixelcolor::Rgb888,
prelude::{DrawTarget, ImageDrawable, PixelColor, Point, RgbColor},
prelude::{DrawTarget, ImageDrawable, OriginDimensions, PixelColor, Point, RgbColor},
text::Text,
Drawable,
};
Expand All @@ -19,7 +19,7 @@ use embedded_layout::{layout::linear::spacing, prelude::Link};
use embedded_layout_macros::ViewGroup;
use log::error;
use parking_lot::Mutex;
use rustic_pixel_display::render::{Render, RenderFactory};
use rustic_pixel_display::render::{CachedCanvas, Render, RenderFactory};
use septa_api::types::RegionalRailStop;
use serde::Deserialize;
use std::{convert::Infallible, io::Read, marker::PhantomData, sync::Arc, time::Duration};
Expand Down Expand Up @@ -63,6 +63,9 @@ struct UpcomingTrainsState {
amtrak_arrivals: Vec<UpcomingTrain>,

combined_arrivals: Vec<UpcomingTrain>,

/// Cached canvas
cached_canvas: Option<CachedCanvas>,
}

#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -163,6 +166,7 @@ impl UpcomingArrivals {
arrivals.sort_by(|a, b| a.schedule_arrival.cmp(&b.schedule_arrival));

state_unlocked.combined_arrivals = arrivals;
state_unlocked.cached_canvas = None;
} // drop(state_unlocked)

select! {
Expand Down Expand Up @@ -225,13 +229,22 @@ enum LayoutView<'a, C: PixelColor> {

impl<D> Render<D> for UpcomingArrivals
where
D: DrawTarget<Color = Rgb888, Error = Infallible>,
D: DrawTarget<Color = Rgb888, Error = Infallible> + OriginDimensions,
{
fn render(&self, canvas: &mut D) -> Result<(), D::Error> {
let canvas_bounding_box = canvas.bounding_box();
let mut remaining_height = canvas_bounding_box.size.height;

{
let state_unlocked = self.state.lock();
if let Some(cached_canvas) = &state_unlocked.cached_canvas {
cached_canvas.render(canvas)?;
return Ok(());
}
} //drop(state_unlocked)

// Figure out which logos to display
let mut cached_canvas = CachedCanvas::new(canvas.size());
let mut title_views = Vec::new();
if self.is_septa_stop {
title_views.push(TitleView::LogoView(Image::new(&*SEPTA_BMP, Point::zero())));
Expand All @@ -254,8 +267,6 @@ where

remaining_height -= title_layout.bounds().size.height;

let mut arrival_layouts = Vec::new();

let display_items = self
.state
.lock()
Expand Down Expand Up @@ -283,20 +294,10 @@ where
})
.collect::<Vec<_>>();

if display_items.is_empty() {
arrival_layouts.push(LayoutView::NoArrival(
LinearLayout::horizontal(Chain::new(Text::new(
"No upcoming arrivals",
Point::zero(),
MonoTextStyle::new(&mono_font::ascii::FONT_6X9, Rgb888::WHITE),
)))
.with_alignment(vertical::Center)
.with_spacing(spacing::FixedMargin(6))
.arrange(),
));
} else {
for display_item in &display_items {
let (time, train_id, destination_name, status, status_color) = display_item;
let mut arrival_layouts = display_items
.iter()
.map_while(|display_item| {
let (time, train_id, destination_name, status, status_color) = &display_item;

let chain = Chain::new(Text::new(
time,
Expand All @@ -322,18 +323,31 @@ where
let chain_height = chain.bounds().size.height;

if remaining_height < chain_height {
break;
None
} else {
remaining_height -= chain.bounds().size.height;

Some(LayoutView::UpcomingArrival(
LinearLayout::horizontal(chain)
.with_alignment(vertical::Center)
.with_spacing(spacing::FixedMargin(6))
.arrange(),
))
}
})
.collect::<Vec<_>>();

remaining_height -= chain.bounds().size.height;

arrival_layouts.push(LayoutView::UpcomingArrival(
LinearLayout::horizontal(chain)
.with_alignment(vertical::Center)
.with_spacing(spacing::FixedMargin(6))
.arrange(),
));
}
if arrival_layouts.is_empty() {
arrival_layouts.push(LayoutView::NoArrival(
LinearLayout::horizontal(Chain::new(Text::new(
"No upcoming arrivals",
Point::zero(),
MonoTextStyle::new(&mono_font::ascii::FONT_6X9, Rgb888::WHITE),
)))
.with_alignment(vertical::Center)
.with_spacing(spacing::FixedMargin(6))
.arrange(),
));
}

LinearLayout::vertical(
Expand All @@ -345,7 +359,14 @@ where
)
.with_spacing(spacing::FixedMargin(2))
.arrange()
.draw(canvas)?;
.draw(&mut cached_canvas)?;

cached_canvas.render(canvas)?;

{
let mut state_unlocked = self.state.lock();
state_unlocked.cached_canvas = Some(cached_canvas);
} //drop(state_unlocked)

Ok(())
}
Expand Down Expand Up @@ -381,7 +402,7 @@ where

impl<D> RenderFactory<D> for UpcomingArrivalsFactory<D>
where
D: DrawTarget<Color = Rgb888, Error = Infallible>,
D: DrawTarget<Color = Rgb888, Error = Infallible> + OriginDimensions,
{
fn render_name(&self) -> &'static str {
"UpcomingArrivals"
Expand Down